Подтвердить что ты не робот

Нужны объяснения для Linux bash встроенное поведение команды exec

Из Bash Справочное руководство Я получаю следующее о exec bash встроенной команде:

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

Теперь у меня есть следующий bash script:

#!/bin/bash
exec ls;
echo 123;
exit 0

Это выполнено, я получил следующее:

cleanup.sh  ex1.bash  file.bash  file.bash~  output.log
(files from the current directory)

Теперь, если у меня есть этот script:

#!/bin/bash
exec ls | cat
echo 123
exit 0

Я получаю следующий вывод:

cleanup.sh
ex1.bash
file.bash
file.bash~
output.log
123

Мой вопрос:

Если при вызове exec он заменяет оболочку, не создавая новый процесс, почему, когда put | cat, печатается echo 123, но без него это не так. Поэтому я был бы счастлив, если бы кто-нибудь мог объяснить, что такое логика этого поведения.

Спасибо.

EDIT: После ответа @torek мне становится еще труднее объяснить поведение:

1. exec ls>out команда создает файл out и вставляет в него результат команды ls;

2. exec ls>out1 ls>out2 создает только файлы, но не помещается внутри какого-либо результата. Если команда работает так, как было предложено, я думаю, что команда номер 2 должна иметь тот же результат, что и команда номер 1 (даже больше, я думаю, что он не должен был создать файл out2).

4b9b3361

Ответ 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.