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

StackOverflow https://stackoverflow.com/questions/2732

Вопрос

Может ли кто-нибудь объяснить такое поведение?Бег:

#!/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

Другие советы

#!/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, и вы можете видеть, что они отображаются.Вне брекетов их больше не существует.Весь код в фигурных скобках был запущен в подоболочке. потому что это один из компонентов конвейера.

Вы можете помещать произвольные объемы кода в фигурные скобки, поэтому вы можете использовать эту конструкцию «переход в блок» всякий раз, когда вам нужно запустить блок сценария оболочки, который анализирует выходные данные чего-то еще.

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

read var1 var2 < <(echo "hello world")

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

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

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

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

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

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

read var1 var2 <<< "hello world"
echo $var1 $var2

Ладно, я понял!

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

var1=$(echo "Hello")
echo var1

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

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

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

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

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

$ echo $$;cat | read a
10637

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

    |                       |-bash(10637)-+-bash(10786)
    |                       |             `-cat(10785)

Пытаться:

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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top