Как читать из файла или STDIN в Bash?
Следующий скрипт Perl (my.pl
) может читать либо из файла в аргументах командной строки, либо из STDIN:
while (<>) {
print($_);
}
perl my.pl
будет читать из STDIN, а perl my.pl a.txt
будет читать из a.txt
. Это очень удобно.
Хотите знать, есть ли эквивалент в Bash?
Ответы
Ответ 1
Следующее решение читается из файла, если вызывается script
с именем файла в качестве первого параметра $1
в противном случае от стандартного ввода.
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
Подстановка ${1:-...}
принимает $1
, если определено иначе
используется имя файла стандартного ввода собственного процесса.
Ответ 2
Возможно, самым простым решением является перенаправление stdin с помощью оператора перенаправления слияния:
#!/bin/bash
less <&0
Stdin - это дескриптор файла. Вышеуказанное отправляет входной канал на ваш bash script на менее стандартный.
Подробнее о перенастройке дескриптора файла.
Ответ 3
Вот простейший способ:
#!/bin/sh
cat -
Использование:
$ echo test | sh my_script.sh
test
Чтобы назначить stdin для переменной, вы можете использовать: STDIN=$(cat -)
или просто просто STDIN=$(cat)
, поскольку оператор не нужен (согласно @mklement0 comment).
Чтобы проанализировать каждую строку со стандартного ввода, попробуйте выполнить script:
#!/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
по умолчанию последовательности Space и Tab в начале и конце строк игнорируются (обрезаются).
- используйте printf
вместо echo
, чтобы избежать печати пустых строк, когда строка состоит из одного -e
, -n
или -e
. Однако есть временное решение, используя env POSIXLY_CORRECT=1 echo "$line"
, который выполняет внешний GNU echo
, который его поддерживает. Смотрите: Как эхо "-e " ?
Смотрите: Как читать stdin, когда аргументы не передаются? в stackoverflow SE
Ответ 4
Я думаю, что это прямолинейный путь:
$ 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
Ответ 5
Решение echo
добавляет новые строки всякий раз, когда IFS
прерывает входной поток. @fgm answer можно немного изменить:
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
Ответ 6
В цикле Perl в вопросе читаются все аргументы имени файла в командной строке или со стандартного ввода, если файлы не указаны. Ответы, которые я вижу, кажутся обработкой одного файла или стандартного ввода, если файл не указан.
Хотя часто высмеивается как UUOC (бесполезное использование cat
), бывают случаи, когда cat
- лучший инструмент для работа, и можно утверждать, что это один из них:
cat "[email protected]" |
while read -r line
do
echo "$line"
done
Единственным недостатком этого является то, что он создает конвейер, работающий в под-оболочке, поэтому такие вещи, как присвоения переменных в цикле while
, недоступны вне конвейера. bash
способ Замена процесса:
while read -r line
do
echo "$line"
done < <(cat "[email protected]")
Это оставляет цикл while
, запущенный в основной оболочке, поэтому переменные, установленные в цикле, доступны вне цикла.
Ответ 7
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
, и в этом случае вам не нужно reset IFS
. Если вы хотите другое имя, вы должны reset IFS
как это сделать (если, конечно, вы этого не хотите и знаете, что делаете):
while IFS= read -r line; do
printf '%s\n' "$line"
done
Ответ 8
Пожалуйста, попробуйте следующий код:
while IFS= read -r line; do
echo "$line"
done < file
Ответ 9
Более точно...
while IFS= read -r line ; do
printf "%s\n" "$line"
done < file
Ответ 10
Код ${1:-/dev/stdin}
будет просто понимать первый аргумент, поэтому, как насчет этого.
ARGS='$*'
if [ -z "$*" ]; then
ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
echo "$line"
done
Ответ 11
Я не считаю, что любые из этих ответов приемлемы. В частности, принятый ответ обрабатывает только первый параметр командной строки и игнорирует остальные. Программа 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 "[email protected]" $DEFAULT_INPUT_FILE
do
while IFS= read -r LINE
do
# Do whatever you want with LINE here.
echo $LINE
done < "$FILE"
done
Ответ 12
Следующее работает со стандартным sh
(протестировано с dash
на Debian) и вполне читаемо, но это вопрос вкуса:
if [ -n "$1" ]; then
cat "$1"
else
cat
fi | commands_and_transformations
Подробности: если первый параметр не пуст, то cat
этот файл, иначе cat
стандартный ввод. Затем вывод целого оператора if
обрабатывается commands_and_transformations
.
Ответ 13
Я объединил все вышеперечисленные ответы и создал функцию оболочки, которая бы соответствовала моим потребностям. Это от терминала cygwin моих двух машин Windows10, где у меня была общая папка между ними. Мне нужно иметь возможность обрабатывать следующее:
-
cat file.cpp | tx
-
tx < file.cpp
-
tx file.cpp
Если указано конкретное имя файла, мне нужно использовать одно и то же имя файла во время копирования. Когда поток входных данных был передан через канал, мне нужно создать временное имя файла, имеющее час и секунды. В общей папке есть подпапки дней недели. Это для организационных целей.
Вот, окончательный сценарий для моих нужд:
tx ()
{
if [ $# -eq 0 ]; then
local TMP=/tmp/tx.$(date +'%H%M%S')
while IFS= read -r line; do
echo "$line"
done < /dev/stdin > $TMP
cp $TMP //$OTHER/stargate/$(date +'%a')/
rm -f $TMP
else
[ -r $1 ] && cp $1 //$OTHER/stargate/$(date +'%a')/ || echo "cannot read file"
fi
}
Если есть какой-то способ, который вы можете увидеть, чтобы еще больше оптимизировать это, я хотел бы знать.
Ответ 14
Как насчет
for line in `cat`; do
something($line);
done