На Ubuntu 12.04 с GNU bash версии 4.2.25(1)-release (x86_64-pc-linux-gnu) я попробовал следующую команду:
$ bash -c 'pstree -s $$'
init───sshd───sshd───sshd───bash───pstree
$ bash -c 'pstree -s $$;echo'
init───sshd───sshd───sshd───bash───bash───pstree
Думаю, второй вариант соответствует моим ожиданиям: в первом bash
случае я выполнил эти команды; во втором bash
— то, с чего я начал bash -c ...
; а затем второй bash запустит подпроцесс с именем pstree
.
Однако мне интересно, что случилось с первым. Почему второй bash
исчез и pstree
стал подпроцессом оригинала bash
? И почему ответ на предыдущий вопрос не применим ко второму bash -c ...
?
решение1
Я бы сказал, что это простоХвост Вызов Оптимизация, но на самом деле (как указывает последняя ссылка) bash
не оптимизирует хвостовые вызовы. Кажется, что он оптимизирует только случай, когда команда, которая должна быть выполнена, является простой командой (то есть не составной командой), хотя не тогда, когда есть перенаправления. Кажется, он также оптимизирует, когда команда является последней в списке and-or (как в true && true && cmd
or false && true || cmd
), но не тогда, когда есть trap
s на месте (как в trap uname EXIT && cmd
), поскольку это было бы неправильно.
Вторая команда не является хвостовым вызовом pstree
, поскольку pstree
не является последней в командной строке. (Это хвостовой вызов echo
, но echo
обычно он встроен, поэтому для него не будет создан подпроцесс.)
Чтобы не читать все эти ссылки (хотя я надеюсь, что они будут интересными), идея в том, что если вы знаете, что функция/программа/что угодно вернет управление сразу после вызова какой-то другой функции/программы/чего угодно, и что возвращаемое значение будет значением, возвращаемым вызванной функцией/программой/чем угодно, то вы можете просто повторно использовать текущий стековый фрейм (или процесс, в случае скрипта оболочки) вместо того, чтобы помещать новый стековый фрейм (создавать новый процесс), вызывать функцию (запускать скрипт) и затем возвращаться. В скрипте оболочки вы можете сделать это вручную, используя exec
для последней команды, но оболочка могла бы сделать это автоматически.
zsh
и ksh
оба, похоже, способны это сделать, но не bash
:
$ zsh -c 'if [[ -n foo ]]; then pstree -s $$; else echo hello; fi'
init───lightdm───lightdm───init───konsole───bash───pstree
$ ksh -c 'if [[ -n foo ]]; then pstree -s $$; else echo hello; fi'
init───lightdm───lightdm───init───konsole───bash───pstree
$ bash -c 'if [[ -n foo ]]; then pstree -s $$; else echo hello; fi'
init───lightdm───lightdm───init───konsole───bash───bash───pstree
Но это всего лишь наблюдение, основанное на версиях тех оболочек, которые у меня случайно установлены, так что YMMV:
$ zsh --version
zsh 5.0.2 (x86_64-pc-linux-gnu)
$ ksh --version
version sh (AT&T Research) 93u+ 2012-08-01
$ bash --version
GNU bash, version 4.2.45(1)-release (x86_64-pc-linux-gnu)