Кто-нибудь может объяснить это поведение? Бег:
#!/bin/sh
echo "hello world" | read var1 var2
echo $var1
echo $var2
в результате ничего не выводится, а:
#!/bin/sh
echo "hello world" > test.file
read var1 var2 < test.file
echo $var1
echo $var2
производит ожидаемый результат:
hello
world
Разве канал не должен делать за один шаг то же, что и перенаправление на test.file во втором примере? Я попробовал один и тот же код с обоими dash и bash и получил одинаковое поведение от них обоих.
Недавним дополнением bash
является lastpipe
опция, которая позволяет последней команде в конвейере запускаться в текущей оболочке, а не в подоболочке, когда управление заданиями деактивировано.
#!/bin/bash
set +m # Deactiveate job control
shopt -s lastpipe
echo "hello world" | read var1 var2
echo $var1
echo $var2
будет действительно выводить
hello
world
Это связано с тем, что версия канала создает подоболочку, которая считывает переменную в локальное пространство, которое затем уничтожается при выходе из подоболочки.
Выполнить эту команду
$ echo $$;cat | read a
10637
и используйте pstree -p для просмотра запущенных процессов, вы увидите дополнительную оболочку, свисающую с вашей основной оболочки.
| |-bash(10637)-+-bash(10786)
| | `-cat(10785)
Хорошо, я понял это!
Это сложная ошибка, которую нужно уловить, но это результат того, как оболочка обрабатывает каналы. Каждый элемент конвейера работает в отдельном процессе. Когда команда чтения устанавливает var1 и var2, она устанавливает их в свою собственную подоболочку, а не в родительскую оболочку. Поэтому при выходе из подоболочки значения var1 и var2 теряются. Вы можете, однако, попробовать сделать
var1=$(echo "Hello")
echo var1
который возвращает ожидаемый ответ. К сожалению, это работает только для отдельных переменных, вы не можете установить много одновременно. Чтобы установить несколько переменных одновременно, вы должны либо прочитать одну переменную и разбить ее на несколько переменных, либо использовать что-то вроде этого:
set -- $(echo "Hello World")
var1="$1" var2="$2"
echo $var1
echo $var2
Хотя я признаю, что это не так элегантно, как использование трубы, это работает. Конечно, вы должны иметь в виду, что чтение предназначалось для чтения из файлов в переменные, поэтому сделать его чтение из стандартного ввода должно быть немного сложнее.
На этот вопрос уже был дан правильный ответ, но решение еще не было заявлено. Используйте ksh, а не bash. Для сравнения:
$ echo 'echo "hello world" | read var1 var2
echo $var1
echo $var2' | bash -s
Для того, чтобы:
$ echo 'echo "hello world" | read var1 var2
echo $var1
echo $var2' | ksh -s
hello
world
ksh - превосходная программная оболочка из-за таких маленьких тонкостей. (на мой взгляд, bash - лучшая интерактивная оболочка.)
На этот пост был дан правильный ответ, но я хотел бы предложить альтернативный вкладыш, который, возможно, мог бы быть полезным.
Для присвоения разделенных пробелами значений из echo (или stdout в этом отношении) переменным оболочки, вы можете рассмотреть возможность использования массивов оболочки:
$ var=( $( echo 'hello world' ) )
$ echo ${var[0]}
hello
$ echo ${var[1]}
world
В этом примере var - это массив, доступ к содержимому которого можно получить с помощью конструкции $ {var [index]}, где index - индекс массива (начинается с 0).
Таким образом, вы можете иметь столько параметров, сколько вы хотите назначить для соответствующего индекса массива.
read var1 var2 < <(echo "hello world")
Пытаться:
echo "hello world" | (read var1 var2 ; echo $var1 ; echo $var2 )
Проблема, как заявили несколько человек, заключается в том, что var1 и var2 создаются в среде подоболочек, которая уничтожается при выходе из этой подоболочки. Вышеприведенное позволяет избежать разрушения подоболочки, пока результат не будет отражен. Другое решение:
result=`echo "hello world"`
read var1 var2 <<EOF
$result
EOF
echo $var1
echo $var2
$result
(но это трудно показать в комментарии StackOverflow). #!/bin/sh
echo "hello world" | read var1 var2
echo $var1
echo $var2
не выводит, потому что конвейеры запускают каждый из своих компонентов в подоболочке. Подоболочки наследуют копии переменных родительской оболочки, а не разделяют их. Попробуй это:
#!/bin/sh
foo="contents of shell variable foo"
echo $foo
(
echo $foo
foo="foo contents modified"
echo $foo
)
echo $foo
Скобки определяют область кода, которая запускается в подоболочке, и $ foo сохраняет свое первоначальное значение после изменения внутри них.
Теперь попробуйте это:
#!/bin/sh
foo="contents of shell variable foo"
echo $foo
{
echo $foo
foo="foo contents modified"
echo $foo
}
echo $foo
Скобки предназначены исключительно для группировки, подоболочка не создается, а $ foo, модифицированный внутри скобок, является тем же $ foo, модифицированным вне их.
Теперь попробуйте это:
#!/bin/sh
echo "hello world" | {
read var1 var2
echo $var1
echo $var2
}
echo $var1
echo $var2
Внутри фигурных скобок встроенное чтение создает $ var1 и $ var2 правильно, и вы можете видеть, что они отражаются. За пределами фигурных скобок они больше не существуют. Весь код в фигурных скобках был выполнен в подоболочке, потому что это один из компонентов конвейера .
Вы можете помещать произвольные объемы кода между фигурными скобками, так что вы можете использовать эту конструкцию piping-in-block всякий раз, когда вам нужно запустить блок сценария оболочки, который анализирует вывод чего-то другого.
Мой взгляд на эту проблему (используя Bash):
read var1 var2 <<< "hello world"
echo $var1 $var2
set +m
) и установитеlastpipe
параметр (shopt -s lastpipe
).