Нужны объяснения поведения команд встроенной в Linux bash для Linux - PullRequest
36 голосов
/ 29 марта 2012

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

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

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

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

Этот выполненный, я получил это:

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

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

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

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

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

Мой вопрос:

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

Спасибо.

РЕДАКТИРОВАТЬ: После ответа @torek мне становится еще сложнее объяснить поведение:

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

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

1 Ответ

39 голосов
/ 29 марта 2012

В данном конкретном случае у вас есть 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.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...