Відповіді:
Наступне рішення зчитується з файлу, якщо сценарій викликається з ім'ям файлу як першим параметром, $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 | 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будуть розміщені в командному рядку. Командний рядок має максимальний розмір. Також це буде читати не рядок за рядком, а слово за словом.