Странности перенаправления ввода сценариев оболочки - PullRequest
18 голосов
/ 05 августа 2008

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

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

Ответы [ 9 ]

11 голосов
/ 19 сентября 2008
#!/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 голосов
/ 26 июля 2012

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

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

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

hello
world
10 голосов
/ 15 августа 2008

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

8 голосов
/ 17 сентября 2008
read var1 var2 < <(echo "hello world")
5 голосов
/ 04 марта 2009

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

read var1 var2 <<< "hello world"
echo $var1 $var2
5 голосов
/ 14 сентября 2008

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

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

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

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

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

5 голосов
/ 06 августа 2008

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

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

var1=$(echo "Hello")
echo var1

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

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

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

4 голосов
/ 06 августа 2008

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

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

$ echo $$;cat | read a
10637

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

    |                       |-bash(10637)-+-bash(10786)
    |                       |             `-cat(10785)
3 голосов
/ 17 сентября 2008

Попробуйте:

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
...