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