В данном конкретном случае у вас есть exec
в конвейере.Чтобы выполнить серию конвейерных команд, оболочка должна быть изначально разветвленной, создавая под-оболочку.(В частности, он должен создать трубу, затем форк, чтобы все, что выполняется «слева» от трубы, могло отправлять свои выходные данные в то, что находится «справа» от трубы.)
Чтобы увидетьчто это на самом деле то, что происходит, сравните:
{ ls; echo this too; } | cat
с:
{ exec ls; echo this too; } | cat
Первый запускает ls
, не выходя из под-оболочки, так что эта под-оболочкапоэтому все еще рядом, чтобы запустить echo
.Последний запускает ls
, оставляя субоболочку, которая поэтому больше не может выполнять echo
, а this too
не печатается.
(Использование фигурных скобок { cmd1; cmd2; }
обычно подавляет действие разветвления вложенной оболочки, которое вы получаете с круглыми скобками (cmd1; cmd2)
, но в случае канала ответвление как бы "принудительное".)
Перенаправление текущей оболочки происходит толькоесли после слова exec
«бежать нечего».Таким образом, например, exec >stdout 4<input 5>>append
изменяет текущую оболочку, но exec foo >stdout 4<input 5>>append
пытается выполнить команду foo
.[Примечание: это не совсем точно;см. приложение.]
Интересно, что в интерактивной оболочке после сбоя exec foo >output
из-за отсутствия команды foo
оболочка остается, но стандартный вывод остается перенаправленным в файл output
.(Вы можете восстановить с помощью exec >/dev/tty
. В сценарии ошибка exec foo
завершает сценарий.)
С подсказкой @ Pumbaa80, вот что еще более наглядно:
#! /bin/bash
shopt -s execfail
exec ls | cat -E
echo this goes to stdout
echo this goes to stderr 1>&2
(примечание: cat -E
упрощено по сравнению с моим обычным cat -vET
, который является моим удобным способом "позвольте мне видеть непечатаемые символы узнаваемым образом").При запуске этого сценария к выводу из ls
применяется cat -E
(в Linux это делает конец строки видимым в виде знака $), но вывод, отправляемый в stdout и stderr (в оставшихся двух строках), является не перенаправлено.Измените | cat -E
на > out
и после запуска скрипта просмотрите содержимое файла out
: там нет двух последних echo
.
Теперь измените ls
наfoo
(или какая-то другая команда, которая не будет найдена) и снова запустите скрипт.На этот раз вывод:
$ ./demo.sh
./demo.sh: line 3: exec: foo: not found
this goes to stderr
, а файл out
теперь содержит содержимое, созданное первой строкой echo
.
Это делает то, что exec
"действительно делает"настолько очевидным, насколько это возможно (но не более очевидным, поскольку Альберт Эйнштейн не выразил это :-)).
Обычно, когда оболочка выполняет «простую команду» (точное определение см. на странице руководства)., но это, в частности, исключает команды в «конвейере»), он подготавливает любые операции перенаправления ввода-вывода, указанные в <
, >
и т. д., открывая необходимые файлы.Затем оболочка вызывает fork
(или некоторый эквивалентный, но более эффективный вариант, такой как vfork
или clone
в зависимости от базовой ОС, конфигурации и т. Д.), И, в дочернем процессе, переупорядочивает дескрипторы открытого файла (используя * 1065)* вызывает или эквивалентный) для достижения желаемых окончательных договоренностей: > out
перемещает открытый дескриптор в fd 1 - stdout - в то время как 6> out
перемещает открытый дескриптор в fd 6.
Если вы указали exec
Однако, ключевое слово, оболочка подавляет шаг fork
.Он выполняет все действия по открытию файла и перестановке дескриптора файла, но на этот раз влияет на все последующие команды .Наконец, выполнив все перенаправления, оболочка пытается execve()
(в смысле системного вызова) выполнить команду, если она есть.Если команды нет или если вызов execve()
завершился неудачно и , предполагается, что оболочка продолжит работу (является интерактивной или вы установили execfail
), включите оболочку.Если execve()
завершается успешно, оболочка больше не существует, ее заменила новая команда.Если execfail
не установлено и оболочка не является интерактивной, оболочка завершается.
(Существует также дополнительное усложнение функции оболочки command_not_found_handle
: похоже, что bash's exec
подавляет ее запуск, основываясь на результатах теста. Ключевое слово exec
вообще заставляет оболочку не смотреть на свои собственные функции, т.е. если у вас есть функция оболочки f, выполнение f
в виде простой команды запускает функцию оболочки, как и (f)
, которая запускает ее в под-оболочке, но выполнение (exec f)
пропускает ее.)
Что касается того, почему
ls>out1 ls>out2
создает два файла (с
exec
или без него), это достаточно просто: оболочка открывает каждое перенаправление, а затем использует
dup2
для перемещения дескрипторов файлов. Если у вас есть два обычных перенаправления
>
, оболочка открывает оба, перемещает первое в fd 1 (стандартный вывод), затем перемещает второе в fd 1 (снова стандартный вывод), закрывая первое в процессе. Наконец, он запускает
ls ls
, потому что это то, что осталось после удаления
>out1 >out2
. Пока нет файла с именем
ls
, команда
ls
жалуется на stderr и ничего не записывает в stdout.