Я привык к bash
встроенной read
функции в циклах while, например:
echo "0 1
1 1
1 2
2 3" |\
while read A B; do
echo $A + $B | bc;
done
Я работал над каким-то make
проектом, и стало разумным разбить файлы и сохранить промежуточные результаты. В результате я часто разрываю отдельные строки на переменные. Хотя следующий пример работает довольно хорошо,
head -n1 somefile | while read A B C D E FOO; do [... use vars here ...]; done
это как-то глупо, потому что цикл while никогда не будет выполняться больше одного раза. Но без while
,
head -n1 somefile | read A B C D E FOO; [... use vars here ...]
Переменные чтения всегда пусты, когда я их использую. Я никогда не замечал такого поведения read
, потому что обычно я использую циклы while для обработки множества похожих строк. Как я могу использовать встроенную bash
функцию read
без цикла while? Или есть другой (или даже лучший) способ прочитать одну строку в несколько (!) переменных?
Заключение
Ответы учат нас, что это проблема масштаба. Заявление
cmd0; cmd1; cmd2 | cmd3; cmd4
интерпретируется таким образом, что команды cmd0
, cmd1
и cmd4
выполняются в одной и той же области, в то время как команды cmd2
и cmd3
каждой из них присваивается своя собственная подоболочка и, следовательно, разные области действия. Исходная оболочка является родительской для обеих подоболочек.
решение1
Это потому, что часть, где вы используете vars, является новым набором команд. Используйте это вместо этого:
head somefile | { read A B C D E FOO; echo $A $B $C $D $E $FOO; }
Обратите внимание, что в этом синтаксисе после должен быть пробел, {
а ;
перед }
. -n1
Это также не обязательно; read
считывается только первая строка.
Для лучшего понимания вам может помочь следующее; это делает то же самое, что и выше:
read A B C D E FOO < <(head somefile); echo $A $B $C $D $E $FOO
Редактировать:
Часто говорят, что следующие два утверждения делают одно и то же:
head somefile | read A B C D E FOO
read A B C D E FOO < <(head somefile)
Ну, не совсем. Первый — это канал от head
к bash
встроенному read
. Stdout одного процесса к stdin другого процесса.
Второе выражение — перенаправление и замена процесса. Оно обрабатывается само по bash
себе. Оно создает FIFO (именованный канал, <(...)
), head
к которому подключен выход , и перенаправляет ( <
) его в read
процесс.
Пока что они кажутся эквивалентными. Но при работе с переменными это может иметь значение. В первом случае переменные не устанавливаются после выполнения. Во втором случае они доступны в текущей среде.
У каждой оболочки свое поведение в этой ситуации. Смотритеэта ссылкадля которых они есть. В bash
вы можете обойти это поведение с помощью группировки команд {}
, подстановки процессов ( < <()
) или строк Here ( <<<
).
решение2
Цитата из очень полезной статьиwiki.bash-hackers.org:
Это происходит потому, что команды конвейера выполняются в подоболочках, которые не могут модифицировать родительскую оболочку. В результате переменные родительской оболочки не модифицируются (см. статью:Bash и дерево процессов).
Поскольку ответ уже был дан несколько раз, альтернативный способ (с использованием не встроенных команд...) таков:
$ eval `echo 0 1 | awk '{print "A="$1";B="$2}'`;echo $B $A
$ 1 0
решение3
Как вы отметили, проблема была в том, что канал to read
был запущен в подоболочке.
Один из ответов — использоватьхередок:
numbers="01 02"
read first second <<INPUT
$numbers
INPUT
echo $first
echo $second
Этот метод хорош тем, что он будет вести себя одинаково в любой POSIX-подобной оболочке.