Приоритет Pipe (|) и логического и (&&) в bash

17

Классический сценарий с приоритетом оператора, у вас есть такая строка:

(cd ~/screenshots/ && ls screenshot* | head -n 5)

И вы не знаете, если это проанализировано ((A && B) | C)или (A && B | C)...

Почти официальная документация нашла здесь не перечисляет трубку в списке , так что я не могу просто проверить в таблице.

Кроме того, в bash он (предназначен не только для изменения порядка операций, но и создает подоболочку , поэтому я не уверен на 100%, что эти строки эквивалентны предыдущей строке:

((cd ~/screenshots/ && ls screenshot*) | head -n 5)

В общем, как узнать AST линии bash? В Python у меня есть функция, которая дает мне дерево, чтобы я мог легко проверить порядок операций.

Роберт Ванден Эйнд
источник
1
Это может помочь узнать, что |это просто разъем, который подходит для стандартного вывода LHS и стандартного подключения RHS. Таким образом, если cdкоманда в вашем примере завершится неудачно, она не будет отправлять выходные данные head, но headна самом деле все равно будет executeанализировать ничего и не будет возвращать никаких результатов.
DopeGhoti
1
bashиспользует парсер yacc не какую-то специальную вещь; если вы пройдете через yacc -vэто, вы получите y.outputкрасивую грамматику, которая показывает это &&и ||объединяет списки, а списки в конечном итоге состоят из конвейеров (а не наоборот); ТЛ; др; A && B | Cтак же A && { B | C; }, как и ожидалось. Не принимайте никакой порядок исполнения между Bи C; Команды в конвейере выполняются параллельно .
Мосви
1
Обратите внимание, что та «почти официальная документация», на которую вы указываете, совершенно не имеет значения, поскольку речь идет об операторах, используемых внутри [...]тестов и $((...))арифметических оценок; в частности, ||и &&при использовании со списком команд в языке оболочки имеют тот же приоритет , в отличие от соответствующих операторов в Cили в арифметической оценке (где &&связывается более тесно, чем ||).
Мосви
@mosvy, этот документ даже не говорит о том, в каком контексте применяется таблица, поэтому в этом смысле он тоже кажется менее чем полезным ...
ilkkachu

Ответы:

21
cd ~/screenshots/ && ls screenshot* | head -n 5

Это эквивалентно

cd ~/screenshots && { ls screenshot* | head -n 5 ; }

( брекеты группируют команды без подоболочки ). Таким образом, приоритет |выше (связывается теснее), чем &&и ||. То есть,

A && B | C

и

A || B | C

всегда означает , что выход только Б должно быть дано C . Вы можете использовать (...)или { ... ; }объединять команды как единый объект для устранения неоднозначности, если необходимо:

{ A && B ; } | C
A && { B | C ; } # This is the default, but you might sometimes want to be explicit

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

echo hello && echo world | tr a-z A-Z

тогда вы получите

hello
WORLD

назад: tr a-z A-Zзаглавные буквы его ввода , и вы можете видеть, что только echo worldбыл передан в него, пока echo helloпрошел сам по себе.


Это будет определено в грамматике оболочки , хотя и не очень четко: на and_orпроизводство (для &&/ ||) определяется , чтобы иметь аа pipelineв своем теле, в то время как pipelineсодержит только command, который не содержит and_or- толькоcomplete_command производство может достигатьand_or , и он существует только на верхний уровень и внутри тел структурных конструкций, таких как функции и петли.

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

Грамматика оболочки имеет множество особых случаев, определенных лишь полуформально, и это может быть довольно сложной задачей, чтобы получить право. Даже сам Баш иногда ошибался , поэтому практичность и идеал могут быть разными.

Существуют внешние парсеры, которые пытаются сопоставить синтаксис и создать дерево, и я буду широко рекомендовать Morbig , который пытается быть наиболее надежным.

Майкл Гомер
источник
7

TL; DR : разделители списка, такие как ;. &, &&И|| определять порядок разбора.

Руководство по bash говорит нам:

Списки AND и OR являются последовательностями одного или нескольких конвейеров, разделенных символами && и || управляющие операторы соответственно.

Или как вкратце выразить вики Bash Hacker

<PIPELINE1> && <PIPELINE2>

Таким образом, cd ~/screenshots/ && ls screenshot* | head -n 5 есть один конвейер - ls screenshot* | head -n 5и одна простая команда cd ~/screenshots/. Обратите внимание, что в соответствии с руководством

Каждая команда в конвейере выполняется как отдельный процесс (т. Е. В подоболочке).

С другой стороны, (cd ~/screenshots/ && ls screenshot*) | head -n 5все по-другому - у вас есть один конвейер: слева есть подоболочка, а справа - вы head -n 5. В этом случае, используя запись OP, это будет(A && B) | C


Давайте возьмем другой пример:

$ echo foo | false &&  echo 123 | tr 2 5
$

Здесь у нас есть один список <pipeline1> && <pipeline2>. Поскольку мы знаем, что состояние выхода из конвейера такое же, как и в последней команде, и falseвозвращает отрицательное состояние, то есть сбой, &&правая сторона не будет выполняться.

$ echo foo | true &&  echo 123 | tr 2 5
153

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


Обратите внимание, что грамматика оболочки не подразумевает фактического порядка выполнения. Процитирую один из ответов Жиля :

Команды по конвейеру запускаются одновременно. Когда вы запускаете PS | grep…, это удача ничьей (или вопрос деталей работы оболочки в сочетании с тонкой настройкой планировщика глубоко в недрах ядра) относительно того, запускается ли ps или grep первым, и в любом случае они продолжаются выполнять одновременно.

И из руководства Bash:

Списки AND и OR выполняются с левой ассоциативностью.

Основываясь на том, что cd ~/screenshots/ && ls screenshot* | head -n 5команда cd ~/screenshots/будет выполнена первой, ls screenshot* | head -n 5если предыдущая команда выполнена успешно, но head -n 5может быть первым порожденным процессом, а не lsпотому что они находятся в конвейере.

Сергей Колодяжный
источник
1
Я не вижу, где вопрос задает что-либо о том, в каком порядке появляются вещи.
Майкл Гомер
"нет приоритета, который появляется первым, cd ~/screenshots/ && ls screenshot*или head -n 5" Ну, да, это так ((cd ~/screenshots/ && ls screenshot*) | head -n 5). Но это ничего не говорит о неоднозначном случае, cd ~/screenshots/ && ls screenshot* | head -n 5который, кажется, является предметом вопроса (с или без круглых скобок).
ilkkachu
@ilkkachu Правильно, но следующие предложения касаются этого. cd ~/screenshots/ && ls screenshot* сначала нужно будет обработать, потому что &&списки выше по порядку старшинства (по крайней мере, на основании ответа l0b0). Как вы думаете, где я должен улучшить ответ?
Сергей Колодяжный
@SergiyKolodyazhnyy, ну, учитывая, что у вопроса есть и то, (cd && ls | head)и ((cd && ls) | head), возможно, было бы хорошо четко указать, какой из них вы имеете в виду.
ilkkachu
@ilkkachu Ладно, сейчас я удалю это, а тем временем отредактирую
Сергей Колодяжный,
3

Вот где это указано в bash(1):

SHELL GRAMMAR
[...]
   Pipelines
       A  pipeline  is  a sequence of one or more commands separated by one of
       the control operators | or |&.
[...]
   Lists
       A list is a sequence of one or more pipelines separated by one  of  the
       operators ;, &, &&, or ||, and optionally terminated by one of ;, &, or
       <newline>.

Итак, &&разделяет трубопроводы.

Йол
источник
2

Вы можете просто попробовать echo hello && echo world | less. Вы увидите, что |имеет более высокий приоритет (конвейер команд является командой). Там для вашего 2-го примера это не то же самое. Однако, поскольку cdне имеет выхода, вы не увидите никакой разницы, эфирный путь.

Ctrl-Alt-Делор
источник