Как читать из файла или стандартного ввода в Bash? - PullRequest
210 голосов
/ 08 августа 2011

В Perl следующий код будет считываться из файла, указанного в аргументах командной строки или из стандартного ввода:

while (<>) {
   print($_);
}

Это очень удобно. Я просто хочу знать, как проще всего читать из файла или стандартного ввода в bash.

Ответы [ 14 ]

347 голосов
/ 12 августа 2011

Следующее решение читает файл, если скрипт вызывается с именем файла в качестве первого параметра $1 в противном случае из стандартного ввода.

while read line
do
  echo "$line"
done < "${1:-/dev/stdin}"

Подстановка ${1:-...} принимает $1, если определено иначе, используется имя файла стандартного ввода собственного процесса.

95 голосов
/ 11 мая 2013

Возможно, самое простое решение - перенаправить стандартный ввод с помощью оператора перенаправления слиянием:

#!/bin/bash
less <&0

Стандартный ввод - нулевой дескриптор файла.Вышеприведенное отправляет входные данные в ваш bash-скрипт в stdin less.

Подробнее о перенаправлении файловых дескрипторов .

64 голосов
/ 28 февраля 2015

Вот самый простой способ:

#!/bin/sh
cat -

Использование:

$ echo test | sh my_script.sh
test

Чтобы присвоить stdin переменной, вы можете использовать: STDIN=$(cat -) илипросто STDIN=$(cat) как оператор не требуется (согласно @ mklement0 comment ).


Чтобы проанализировать каждую строку из стандартного ввода , попробуйтеследующий скрипт:

#!/bin/bash
while IFS= read -r line; do
  printf '%s\n' "$line"
done

Для чтения из файла или stdin (если аргумент отсутствует), вы можете расширить его до:

#!/bin/bash
file=${1--} # POSIX-compliant; ${1:--} can be used either.
while IFS= read -r line; do
  printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")

Примечания:

- read -r - Не обрабатывать символ обратной косой черты каким-либо особым образом.Считайте, что каждый обратный слеш является частью строки ввода.

- Без установки IFS, по умолчанию последовательности Пробел и Tab в начале и конце строки игнорируются (обрезаются).

- используйте printf вместо echo, чтобы избежать печати пустых строк, когда строка состоит из одного -e, -n или -E.Однако есть обходной путь с использованием env POSIXLY_CORRECT=1 echo "$line", который выполняет ваш external GNU echo, который его поддерживает.См .: Как мне отобразить "-e"?

См .: Как читать stdin, когда аргументы не передаются? в stackoverflow SE

14 голосов
/ 26 марта 2014

Я думаю, что это прямой путь:

$ cat reader.sh
#!/bin/bash
while read line; do
  echo "reading: ${line}"
done < /dev/stdin

-

$ cat writer.sh
#!/bin/bash
for i in {0..5}; do
  echo "line ${i}"
done

-

$ ./writer.sh | ./reader.sh
reading: line 0
reading: line 1
reading: line 2
reading: line 3
reading: line 4
reading: line 5
13 голосов
/ 20 августа 2011

Решение echo добавляет новые строки всякий раз, когда IFS прерывает входной поток. @ fgm's answer можно немного изменить:

cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
7 голосов
/ 27 октября 2013

Цикл Perl в вопросе читает из все аргументы имени файла в командной строке или из стандартного ввода, если файлы не указаны. Все ответы, которые я вижу, обрабатывают один файл или стандартный ввод, если файл не указан.

Хотя часто выводится точно как UUOC (Бесполезное использование cat), бывают случаи, когда cat является лучшим инструментом для работы, и можно утверждать, что это один из них:

cat "$@" |
while read -r line
do
    echo "$line"
done

Единственным недостатком этого является то, что он создает конвейер, работающий в под-оболочке, поэтому такие вещи, как присвоение переменных в цикле while, недоступны вне конвейера. bash Обходной путь - это Замена процесса :

while read -r line
do
    echo "$line"
done < <(cat "$@")

Это оставляет цикл while работающим в основной оболочке, поэтому переменные, установленные в цикле, доступны вне цикла.

4 голосов
/ 01 марта 2015

Поведение Perl с кодом, заданным в OP, может не принимать ни одного, ни нескольких аргументов, и если аргумент является единственным дефисом -, это понимается как stdin.Кроме того, всегда можно иметь имя файла с $ARGV.Ни один из ответов, данных до сих пор, в действительности не подражает поведению Perl в этих отношениях.Вот чистая возможность Bash.Хитрость заключается в том, чтобы использовать exec надлежащим образом.

#!/bin/bash

(($#)) || set -- -
while (($#)); do
   { [[ $1 = - ]] || exec < "$1"; } &&
   while read -r; do
      printf '%s\n' "$REPLY"
   done
   shift
done

Имя файла доступно в $1.

Если аргументы не заданы, мы искусственно устанавливаем - в качестве первого позиционного параметра.Затем мы зациклились на параметрах.Если параметр не -, мы перенаправляем стандартный ввод из имени файла с помощью exec.Если это перенаправление выполнено успешно, мы выполняем цикл while.Я использую стандартную переменную REPLY, и в этом случае вам не нужно сбрасывать IFS.Если вам нужно другое имя, вы должны сбросить IFS следующим образом (если, конечно, вы не хотите этого и не знаете, что делаете):

while IFS= read -r line; do
    printf '%s\n' "$line"
done
2 голосов
/ 08 августа 2011

Точнее ...

while IFS= read -r line ; do
    printf "%s\n" "$line"
done < file
2 голосов
/ 08 августа 2011

Пожалуйста, попробуйте следующий код:

while IFS= read -r line; do
    echo "$line"
done < file
1 голос
/ 05 марта 2018

Я не считаю ни один из этих ответов приемлемым.В частности, принятый ответ обрабатывает только первый параметр командной строки и игнорирует остальные.Программа Perl, которую она пытается эмулировать, обрабатывает все параметры командной строки.Таким образом, принятый ответ даже не отвечает на вопрос.Другие ответы используют расширения bash, добавляют ненужные команды 'cat', работают только для простого случая повторения ввода в вывод или просто излишне сложны.

Однако я должен отдать им должное, потому что они дали мненекоторые идеи.Вот полный ответ:

#!/bin/sh

if [ $# = 0 ]
then
        DEFAULT_INPUT_FILE=/dev/stdin
else
        DEFAULT_INPUT_FILE=
fi

# Iterates over all parameters or /dev/stdin
for FILE in "$@" $DEFAULT_INPUT_FILE
do
        while IFS= read -r LINE
        do
                # Do whatever you want with LINE here.
                echo $LINE
        done < "$FILE"
done
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...