Странности перенаправления ввода сценариев оболочки

Кто-нибудь может объяснить это поведение? Бег:

#!/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 и получил одинаковое поведение от них обоих.

5.08.2008 19:26:00
9 ОТВЕТОВ
РЕШЕНИЕ

Недавним дополнением bashявляется lastpipeопция, которая позволяет последней команде в конвейере запускаться в текущей оболочке, а не в подоболочке, когда управление заданиями деактивировано.

#!/bin/bash
set +m      # Deactiveate job control
shopt -s lastpipe
echo "hello world" | read var1 var2
echo $var1
echo $var2

будет действительно выводить

hello
world
11
26.07.2012 12:10:13

Это связано с тем, что версия канала создает подоболочку, которая считывает переменную в локальное пространство, которое затем уничтожается при выходе из подоболочки.

Выполнить эту команду

$ echo $$;cat | read a
10637

и используйте pstree -p для просмотра запущенных процессов, вы увидите дополнительную оболочку, свисающую с вашей основной оболочки.

    |                       |-bash(10637)-+-bash(10786)
    |                       |             `-cat(10785)
4
5.08.2008 20:00:43

Хорошо, я понял это!

Это сложная ошибка, которую нужно уловить, но это результат того, как оболочка обрабатывает каналы. Каждый элемент конвейера работает в отдельном процессе. Когда команда чтения устанавливает var1 и var2, она устанавливает их в свою собственную подоболочку, а не в родительскую оболочку. Поэтому при выходе из подоболочки значения var1 и var2 теряются. Вы можете, однако, попробовать сделать

var1=$(echo "Hello")
echo var1

который возвращает ожидаемый ответ. К сожалению, это работает только для отдельных переменных, вы не можете установить много одновременно. Чтобы установить несколько переменных одновременно, вы должны либо прочитать одну переменную и разбить ее на несколько переменных, либо использовать что-то вроде этого:

set -- $(echo "Hello World")
var1="$1" var2="$2"
echo $var1
echo $var2

Хотя я признаю, что это не так элегантно, как использование трубы, это работает. Конечно, вы должны иметь в виду, что чтение предназначалось для чтения из файлов в переменные, поэтому сделать его чтение из стандартного ввода должно быть немного сложнее.

5
4.08.2012 20:13:29
Это зависит от выбора оболочки. Ksh93 был разработан для ограничения накладных расходов процесса. Он также запускает последний элемент конвейера внутри вызывающего процесса оболочки, тем самым сохраняя состояние.
Henk Langeveld 26.07.2012 09:49:52
Bash 4.2 представил возможность сделать то же самое. Отключите управление заданиями ( set +m) и установите lastpipeпараметр ( shopt -s lastpipe).
chepner 26.07.2012 12:10:02

На этот вопрос уже был дан правильный ответ, но решение еще не было заявлено. Используйте 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 - лучшая интерактивная оболочка.)

10
15.08.2008 15:52:01

На этот пост был дан правильный ответ, но я хотел бы предложить альтернативный вкладыш, который, возможно, мог бы быть полезным.

Для присвоения разделенных пробелами значений из echo (или stdout в этом отношении) переменным оболочки, вы можете рассмотреть возможность использования массивов оболочки:

$ var=( $( echo 'hello world' ) )
$ echo ${var[0]}
hello
$ echo ${var[1]}
world

В этом примере var - это массив, доступ к содержимому которого можно получить с помощью конструкции $ {var [index]}, где index - индекс массива (начинается с 0).

Таким образом, вы можете иметь столько параметров, сколько вы хотите назначить для соответствующего индекса массива.

5
14.09.2008 17:00:04
Хорошее решение Он отлично работает в bash, но не работает в dash.
Ryan Ahearn 15.09.2008 12:47:39
read var1 var2 < <(echo "hello world")
8
28.06.2011 20:09:10

Пытаться:

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
3
17.09.2008 02:15:03
Вы можете сократить это, вставив $result(но это трудно показать в комментарии StackOverflow).
dolmen 23.07.2018 12:34:25
#!/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 всякий раз, когда вам нужно запустить блок сценария оболочки, который анализирует вывод чего-то другого.

11
19.09.2008 02:20:35

Мой взгляд на эту проблему (используя Bash):

read var1 var2 <<< "hello world"
echo $var1 $var2
5
28.06.2011 20:08:40