Ответы:
Следующее решение читает файл, если скрипт вызывается с именем файла в качестве первого параметра, в $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 | txtx < file.cpptx 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будет помещен в командную строку. Командная строка имеет максимальный размер. Также это будет читать не построчно, а слово за словом.