Відповіді:
Наступне рішення зчитується з файлу, якщо сценарій викликається з ім'ям файлу як першим параметром, $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
?
Мабуть, найпростішим рішенням є перенаправлення stdin за допомогою оператора перенаправлення, що об'єднується:
#!/bin/bash
less <&0
Stdin - дескриптор файлу нульовий. Вищезгадане надсилає вхід, поданий у ваш bash-скрипт, у stdin менше.
<&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
Щоб прочитати з файлу або 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
який його підтримує. Див.: Як я лунаю "-е"?
Див.: Як читати stdin, коли не передаються аргументи? при stackoverflow SE
[ "$1" ] && FILE=$1 || FILE="-"
це FILE=${1:--}
. (Quibble: краще уникати змінних оболонок верхнього регістру, щоб уникнути зіткнень імен зі змінними середовища .)
${1:--}
є POSIX-сумісним, тому він повинен працювати у всіх POSIX-подібних оболонок. У всіх таких оболонках не буде працювати процес заміщення ( <(...)
); Наприклад, він буде працювати в bash, ksh, zsh, але не в тирі. Крім того, краще додати -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
прапора. "Утиліта ехо записує будь-які задані операнди, розділені одним порожнім (` `) символом і слідом за новим рядком (` \ 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 з кодом, наведеним в ОП, може приймати жоден або кілька аргументів, і якщо аргумент є одним дефісом, -
це розуміється як stdin. Крім того, завжди можна мати ім'я файлу $ARGV
. Жодна з наведених відповідей поки що наслідує поведінку Перла в цьому відношенні. Ось чиста можливість 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
позначення. IMO, 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 двох моїх машин 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
будуть розміщені в командному рядку. Командний рядок має максимальний розмір. Також це буде читати не рядок за рядком, а слово за словом.