Как я могу определить, что я нахожусь в подоболочке?

24

Я пытаюсь написать функцию, чтобы заменить функциональность exitвстроенной, чтобы предотвратить выход из терминала.

Я попытался использовать SHLVLпеременную окружения, но она не меняется внутри подоболочек:

$ echo $SHLVL
1
$ ( echo $SHLVL )
1
$ bash -c 'echo $SHLVL'
2

Моя функция заключается в следующем:

exit () {
    if [[ $SHLVL -eq 1 ]]; then
        printf '%s\n' "Nice try!" >&2
    else
        command exit
    fi
}

Это не позволит мне использовать exitвнутри подоболочек:

$ exit
Nice try!
$ (exit)
Nice try!

Что такое хороший метод, чтобы определить, нахожусь ли я в подоболочке?

Jesse_b
источник
1
Это из-за этого . $ SHLVL равен 1, потому что вы все еще находитесь на уровне оболочки 1, хотя команда echo $ SHLVL выполняется в «подоболочке». Согласно этому посту, подоболочки, порожденные круглыми скобками, (...)наследуют все свойства родительского процесса. Предоставленные ответы являются более надежными решениями для определения уровня вашей оболочки.
Кемотеп
1
Возможный дубликат Как я могу получить pid subshell?
Мосви
5
@mosvy Я чувствую, что это другой вопрос. например, BASH_SUBSHELLответ (даже если спорный) не будет применяться к этому вопросу.
Sparhawk
2
Видел заголовок на HNQ и думал, что это вопрос квантовой механики ...
Мердад

Ответы:

43

В Bash вы можете сравнить $BASHPIDс$$

$ ( if [ "$$" -eq "$BASHPID" ]; then echo not subshell; else echo subshell; fi )
subshell
$   if [ "$$" -eq "$BASHPID" ]; then echo not subshell; else echo subshell; fi
not subshell

Если вы не в bash, то $$должны оставаться такими же в подоболочке, поэтому вам потребуется другой способ получения вашего фактического идентификатора процесса.

Один из способов получить ваш настоящий пид это sh -c 'echo $PPID'. Если вы просто поместите это на равнине, ( … )это может не сработать, поскольку ваша оболочка оптимизировала ответвление. Попробуйте дополнительные команды no-op, ( : ; sh -c 'echo $PPID'; : )чтобы заставить его думать, что подоболочка слишком сложна для оптимизации. Заслуга John1024 на переполнение стека для этого подхода.

derobert
источник
Возможно, вы захотите изменить это на  (sh -c 'echo $PPID'; : )- см. Мой комментарий к ответу John1024 .
G-Man говорит «Восстановить Монику»
@ G-Man Хорошо, это было только для проверки (поскольку при реальном использовании это было бы несколько сложнее) ... но да, было бы лучше, если бы тест работал во всех оболочках. Так что я поставил no-op как до, так и после, который, надеюсь, справится со всем.
Дероберт
38

Как насчет BASH_SUBSHELL?

BASH_SUBSHELL
      Увеличивается на единицу в каждой среде подоболочек или подоболочек, когда оболочка
      начинает выполняться в этой среде. Начальное значение 0.

$ echo $BASH_SUBSHELL
0
$ (echo $BASH_SUBSHELL)
1
Фредди
источник
16
Это была бы удобная команда в фильме «Начало».
Эрик Думинил
В начале это, вероятно, $ SHLVL
Бабушка
19

[это должен был быть комментарий, но мои комментарии, как правило, удаляются модераторами, так что это останется ответом, который я мог бы использовать в качестве ссылки, даже если удален]

Использование BASH_SUBSHELLсовершенно ненадежно, поскольку в некоторых подоболочках его можно установить только на 1 , а не во всех.

$ (echo $BASH_SUBSHELL)
1
$ echo $BASH_SUBSHELL | cat
0

Прежде чем утверждать, что подпроцесс, в котором запускается конвейерная команда, не является действительно реальной подоболочкой, рассмотрите этот man bashфрагмент:

Каждая команда в конвейере выполняется как отдельный процесс (т. Е. В подоболочке).

и практические последствия - важно, является ли фрагмент сценария выполнением подпроцесса или нет, что не важно, а не какая-то терминологическая ошибка.

Единственное решение, как уже объяснялось в ответах на этот вопрос, состоит в том, чтобы проверить, является ли он $BASHPIDравным $$или, по-видимому, гораздо менее эффективным:

if [ "$(exec sh -c 'echo "$PPID"')" != "$$" ]; then
    echo you\'re in a subshell
fi
mosvy
источник
11
Nit: BASH_SUBSHELLустановлен довольно надежно, но получить правильное значение неправильно. Обратите внимание на то, что говорят документы : «Увеличивается на единицу в каждой среде подоболочек или подоболочек, когда оболочка начинает выполняться в этой среде». Я думаю, что в примере с конвейером bash еще не начал выполняться в этой подоболочке, когда раскрывается переменная. Вы можете сравнить echo $BASH_VERSIONс declare -p BASH_VERSION- последний должен надежно вывести 1 с трубами, фоновыми заданиями и т. Д.
Muru
6
Даже скажем, eval 'echo $BASH_SUBSHELL $BASHPID' | catбудет выводить 1 для BASH_SUBSHELL, потому что переменная раскрывается после начала выполнения.
Муру
4
все эти аргументы также должны применяться к подстановке процессов и команд, процессам bg, но отличаются только конвейеры. Глядя на код, инкремент subshell_levelдействительно откладывается в случае конвейеров переднего плана , что, вероятно, имеет какую-то причину, но я не могу разобрать ;-)
mosvy
2
Вы правы. Кажется, Чет явно намеревается именно так. lists.gnu.org/archive/html/bug-bash/2015-06/msg00050.html : «BASH_SUBSHELL измеряет (...) подоболочки, а не элементы конвейера». lists.gnu.org/archive/html/bug-bash/2015-06/msg00054.html : «Я собираюсь подумать о том, должен ли я задокументировать статус-кво или расширить определение« subshell », которое отражает $ BASH_SUBSHELL. "
Муру
2
@JoL вы не правы, расширение происходит в отдельном процессе, пожалуйста, прочитайте ссылки и примеры из этого обсуждения выше; или просто попробуйте echo $$ $BASHPID $BASH_SUBSHELL | cat.
Мосви