Хорошо, я знаю, что в Bash (по умолчанию, без включенной опции bash 'lastpipe') каждая переменная, назначенная после конвейера, действительно выполняется в подоболочке, а сама переменная умирает после выполнения подоболочки. Она не остается доступной родительскому процессу. Но проведя несколько тестов, я пришел к такому поведению:
A) Вторая команда (a=2) присваивает значение и возвращает:
[root@centos01]# a=1; a=2; a=10 | echo $a
2
Б) Третья команда (a=10) присваивает значение и возвращает:
[root@centos01]# a=1; a=2; a=10; a=20 | echo $a
10
C) Четвертая команда (a=20) присваивает значение и возвращает:
[root@centos01]# a=1; a=2; a=10; a=20; touch fileA.txt | echo $a
20
Так:
Почему последнее присваивание переменной в последовательности команд на самом деле не выполняется? (или если выполняется, почему оно не перехватывается подоболочкой и не возвращается обратно командой echo?)
В тесте C команда 'touch' фактически создала файл 'fileA.txt' в каталоге. Так почему же последняя команда в последовательности для назначения переменных, сделанная на шаге A и задании B, не сработала? Кто-нибудь знает техническое объяснение этого?
решение1
Во-первых, чтобы согласовать несколько названий, вот как оболочка интерпретирует ваш ввод:
$ a=1; a=2; a=10 | echo $a
^^^ ^^^ ^^^^^^^^^^^^^^
\ \ \_ Pipeline
\ \_ Simple command
\_ Simple command
Конвейер состоит из двух простых команд:
$ a=10 | echo $a
^^^^ ^^^^^^^
\ \_ Simple command
\_ Simple command
(Обратите внимание, что, хотя это может быть не указано четко в руководстве Bash,Грамматика оболочки POSIXпозволяет создать простую команду, состоящую из простого присвоения переменной).
a=1;
и a=2;
не являются частью какого-либо трубопровода. A ;
завершит трубопровод, за исключением случаев, когда он появляется как частьсоставная команда. Как, например, в:
{ a=1; a=2; a=10; } | echo $a
В вашем примере a=10
и echo $a
выполняются вдва разных, независимые среды подоболочек 1 , обе созданы как копии основной среды. Подоболочки не должны изменять родительскую среду выполнения 2 . Цитируя соответствующиеРаздел POSIX:
Среда подоболочки должна быть создана как дубликат среды оболочки [...] Изменения, вносимые в среду подоболочки, не должны влиять на среду оболочки.
и
Кроме того, каждая команда многокомандного конвейера находится в среде подоболочки; однако, как расширение, любая или все команды в конвейере могут быть выполнены в текущей среде. Все остальные команды должны быть выполнены в текущей среде оболочки.
Таким образом, хотя все команды в ваших примерах фактически выполняются, назначения в левой части ваших конвейеров не имеют видимого эффекта: они только изменяют копии в a
соответствующих им средах подоболочек, которые теряются, как только подоболочки завершаются.
Единственный способ, которым подоболочки на двух концах канала могут напрямую взаимодействовать друг с другом, — это посредством самого канала — стандартного вывода левой стороны, подключенного к стандартному вводу правой стороны. Поскольку a=10
не отправляет ничего по каналу, нет способа, которым он может повлиять на echo $a
.
1 Если lastpipe
опция установлена (по умолчанию она отключена и может быть включена с помощью shopt
встроенной функции), Bash может выполнить последнюю команду конвейера в текущей оболочке. См.Трубопроводыв руководстве Bash. Однако это не имеет значения в контексте вашего вопроса.
2 Более подробную информацию с практической/исторической точки зрения об U&L вы можете найти, например, вэтот ответкПочему последняя функция, выполненная в конвейере сценария оболочки POSIX, не сохраняет значения переменных?
решение2
Извините, пожалуйста, за мой английский, я его еще учу. Я также начинающий изучать Bash, поэтому, пожалуйста, исправьте любые ошибки, которые я допустил в своем ответе, спасибо.
Сначала укажу на ошибку.
В
a=10 | echo $a
вы передаете по конвейеру (используя оператор конвейера;|
)a=10
команду echo. Конвейер соединитstdout
команду a сstdin
командой command2, т.е.command | command2
.a=10
является присвоением переменной, я предполагаю, что для него нетstdout
, так как это не команда. Если вы выполняете подстановку команды в значение присвоения переменной, это не сработает. Если я попробую следующее:user@host$ a=$(b=10); echo $a
не
echo $a
возвращает значение10
. Когда я изменил его наuser@host$ a=$(b=10; echo $b)
звонок
$ echo $a
вернул
10
. Так что, я, вероятно, прав, предполагая, что присваивание переменной не является командой (даже по определению руководства bash это не команда).
Во-вторых, echo
команда не принимает входные данные stdin
, а выводит свои аргументы.
user@host$ echo "I love linux" | echo
ничего не вернет. Вы можете использовать xargs
команду, чтобы обойти это:
user@host$ echo "I love linux" | xargs echo
вернет I love linux
. Таким образом, конвейеризация не работает напрямую по echo
команде, поскольку она выводит свои аргументы, а не stdin
.
Теперь перейдем к тестам.
В вашем распоряжении
user@host$ a=1; a=2; a=10 | echo $a
переменной изначально
a
присваивается значение1
, затем значение переменной изменяется на2
, как в текущей среде оболочки. Команды обычно запускаются в подоболочке.a=10 | echo $a
— это список, т. е. эквивалент ,(a=10 | echo $a)
который запускается в подоболочке, но он не работает, так какecho
не принимаетstdin
, а только выводит свой аргумент. Здесь аргумент — это$a
, значение переменнойa
в подоболочке, которое равно2
.Более того,
a=10
не производит никаких выходных данных, поскольку это присваивание переменной. Таким образом, по сути,echo $a
печатает значение своего аргумента, которое равно ,2
и ничего не берет изa=10 | < ... >
. Таким образом, оператор конвейера здесь не следует использовать; вместо этого вы можете разделить имя команды и присваивание переменной с помощью терминатора (, точка с запятой), и это будет работать нормально, как в(a=10; echo $a)
.
Чтобы лучше понять это, вы можете попробовать следующие примеры с включенной опцией отладки bash:
user@host$ a=1; a=2; a=10; echo $a;
В приведенной выше командной строке
echo $a
создается10
.Если я изменю это на
user@host$ a=1; a=2; (a=10; echo $a)
первое и второе назначение переменных задается в текущей оболочке, третье назначение переменных и
echo
команда выполняются в подоболочке. Таким образом, значениеa
находится10
в подоболочке, в которойecho
также выполняется команда, поэтому оно возвращает10
. После получения приглашения, если вы введете командуecho $a
, она вернется2
, поскольку назначения переменных из подоболочки не переносятся обратно в родительскую оболочку.- Еще один момент, на который следует обратить внимание:
;
разделитель команд выполняет команды последовательно.
В тестовых случаях "A" и "B" последнее назначение переменной ( a=10
в тесте A и a=20
в тесте B) фактически выполняется, но оно выполняется после echo $a
команды, поэтому вы получаете результат предыдущего значения переменной, a
которое находится в среде подоболочки, после чего выполняются последние назначения переменных. В конвейере операторы stdin
и stdout
двух команд соединяются до того, как команда будет выполнена, также назначения переменных ничего не производят в stdout.
вкратце: Назначение переменных не следует использовать в конвейере. echo
не работает напрямую в конвейере.
решение3
Здесь,
a=20 | echo $a
конвейер только добавляет путаницы. Присвоение слева ничего не печатает в stdout и echo
ничего не считывает из stdin, поэтому никакие данные не перемещаются по конвейеру. Аргумент to echo
просто расширяется из того, что было установлено ранее.
Как вы сказали, части конвейера работают в отдельных подоболочках, поэтому назначение в a
левой части не влияет на расширение в правой части, и такое взаимодействие не произойдет в обратном случае.
Вместо этого, если вы сделаете это:
{ a=999; echo a=$a; } | cat
трубка имела бы больше смысла, и веревка a=999
была бы продета через нее.
В touch fileA.txt
последнем примере работает, потому что он влияет на систему за пределами оболочки. Аналогично вы можете писать в stderr из команд в конвейере и видеть результат, появляющийся на терминале:
$ echo a >&2 | echo b >&2 | echo c >&2
b
c
a
(Именно такой порядок вывода я и получил при использовании Bash в своей системе.)