Ответы:
Следующее решение читает файл, если скрипт вызывается с именем файла в качестве первого параметра, в $1
противном случае из стандартного ввода.
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
Подстановка ${1:-...}
происходит, $1
если определено иначе, используется имя файла стандартного ввода собственного процесса.
/proc/$$/fd/0
и /dev/stdin
? Я заметил, что последнее кажется более распространенным и выглядит более простым.
-r
к своей read
команде, чтобы она случайно не съела \
символы; использовать while IFS= read -r line
для сохранения начальных и конечных пробелов.
/bin/sh
- вы используете оболочку, отличную от bash
или sh
?
Возможно, самое простое решение - перенаправить стандартный ввод с помощью оператора перенаправления слиянием:
#!/bin/bash
less <&0
Stdin - это нулевой дескриптор файла. Вышеприведенное посылает входные данные в ваш bash-скрипт в stdin less.
<&0
в этой ситуации не имеет смысла - ваш пример будет работать одинаково с ним или без него - по-видимому, инструменты, которые вы вызываете из скрипта bash по умолчанию, видят тот же stdin, что и сам скрипт (если скрипт не использует его первым).
Вот самый простой способ:
#!/bin/sh
cat -
Использование:
$ echo test | sh my_script.sh
test
Чтобы назначить переменную stdin , вы можете использовать: STDIN=$(cat -)
или просто, так STDIN=$(cat)
как оператор не требуется (согласно комментарию @ mklement0 ).
Чтобы проанализировать каждую строку из стандартного ввода , попробуйте следующий скрипт:
#!/bin/bash
while IFS= read -r line; do
printf '%s\n' "$line"
done
Для чтения из файла или стандартного ввода (если аргумент отсутствует), вы можете расширить его до:
#!/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
который его поддерживает. Смотри: как мне эхо "-е"?
Смотрите: Как читать stdin, когда аргументы не передаются? на стеке потока
[ "$1" ] && FILE=$1 || FILE="-"
до FILE=${1:--}
. (Каламбур: лучше , чтобы избежать всех заглавных оболочки переменных в конфликт имен избежать с окружающей средой переменных.)
${1:--}
это POSIX-совместимый, поэтому он должен работать во всех POSIX-подобных оболочках. То, что не будет работать во всех таких оболочках, это подстановка процесса ( <(...)
); это будет работать в bash, ksh, zsh, но не в dash, например. Кроме того, лучше добавить -r
к вашей read
команде, чтобы она не случайно съела \
символы; готовьтесь IFS=
сохранить ведущие и конечные пробелы.
echo
: если строка состоит из -e
, -n
или -E
, оно не будет показано. Чтобы исправить это, вы должны использовать printf
: printf '%s\n' "$line"
. Я не включал его в свое предыдущее редактирование… слишком часто мои исправления отменяются, когда я исправляю эту ошибку :(
.
--
бесполезно, если первый аргумент'%s\n'
IFS=
с read
и printf
вместо echo
. :)
,
Я думаю, что это прямой путь:
$ 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
read
читает из стандартного ввода по умолчанию , так что нет никакой необходимости в < /dev/stdin
.
echo
Решение добавляет новые строки всякий раз , когда IFS
разбивает входной поток. Ответ @ fgm можно немного изменить:
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
read
«s поведение: в то время как read
это потенциально разделить на несколько лексем со стороны символов. содержащийся в нем $IFS
, он возвращает только один токен, если вы указываете только одно имя переменной (но по умолчанию обрезает начальные и конечные пробелы).
read
и $IFS
- echo
сам добавляет новые строки без -n
флага. «Утилита echo записывает все указанные операнды, разделенные одиночными пустыми (` ') символами и сопровождаемые символом новой строки (`\ n'), в стандартный вывод."
\n
добавить трейлинг echo
: Perl $_
включает в себя строку, заканчивающуюся \n
прочитанной строкой, а bash - read
нет. (Однако, как указывает @gniourf_gniourf в другом месте, более надежный подход - использовать printf '%s\n'
вместо echo
).
Цикл 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
цикл работающим в основной оболочке, поэтому переменные, установленные в цикле, доступны вне цикла.
>>EOF\n$(cat "$@")\nEOF
. Наконец, придира: while IFS= read -r line
лучшее приближение к тому, что while (<>)
делает в Perl (сохраняет начальные и конечные пробелы - хотя Perl также сохраняет конечные \n
).
Поведение 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
Точнее...
while IFS= read -r line ; do
printf "%s\n" "$line"
done < file
IFS=
и -r
к read
команде гарантирует, что каждая строка читается без изменений (включая начальные и конечные пробелы).
Пожалуйста, попробуйте следующий код:
while IFS= read -r line; do
echo "$line"
done < file
read
без IFS=
и -r
, и бедного $line
без здоровых цитат.
read -r
обозначения. ИМО, POSIX понял это неправильно; эта опция должна включать специальное значение для конечных обратных слешей, а не отключать его, чтобы существующие скрипты (до появления POSIX) не ломались, потому что -r
был опущен. Однако я замечаю, что это было частью IEEE 1003.2 1992 года, который был самой ранней версией стандарта оболочки и утилит POSIX, но даже тогда он был отмечен как дополнение, так что это вызывает недовольство давно ушедшими возможностями. Я никогда не сталкивался с неприятностями, потому что мой код не использует -r
; Мне должно быть повезло. Не обращай на это внимания.
-r
должно быть стандартным. Я согласен, что это вряд ли произойдет в тех случаях, когда неиспользование этого приводит к проблемам. Тем не менее, неработающий код - это неработающий код. Мое редактирование было сначала вызвано этой плохой $line
переменной, которая сильно пропустила свои кавычки. Я исправил, read
пока я был на этом. Я не исправил, echo
потому что это вид редактирования, который откатывается. :(
,
Код ${1:-/dev/stdin}
просто поймет первый аргумент, так что, как на счет этого?
ARGS='$*'
if [ -z "$*" ]; then
ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
echo "$line"
done
Я не считаю ни один из этих ответов приемлемым. В частности, принятый ответ обрабатывает только первый параметр командной строки и игнорирует остальные. Программа 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
Я объединил все вышеперечисленные ответы и создал функцию оболочки, которая бы соответствовала моим потребностям. Это с терминала cygwin моих 2-х компьютеров с 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
}
Если есть какой-то способ, который вы можете увидеть для дальнейшей оптимизации, я хотел бы знать.
Следующее работает со стандартным sh
(протестировано dash
на Debian) и вполне читабельно, но это дело вкуса:
if [ -n "$1" ]; then
cat "$1"
else
cat
fi | commands_and_transformations
Подробности: если первый параметр не пустой, то cat
этот файл, иначе cat
стандартный ввод. Затем вывод всего if
оператора обрабатывается commands_and_transformations
.
cat "${1:--}" | any_command
. Чтение переменных оболочки и их отображение могут работать для небольших файлов, но не так хорошо масштабируются.
[ -n "$1" ]
Может быть упрощена [ "$1" ]
.
Как насчет
for line in `cat`; do
something($line);
done
cat
будет помещен в командную строку. Командная строка имеет максимальный размер. Также это будет читать не построчно, а слово за словом.