Множественные команды и выполнение подоболочки после конвейера

Множественные команды и выполнение подоболочки после конвейера

Хорошо, я знаю, что в 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 в своей системе.)

Связанный контент