Интерактивные, неинтерактивные оболочки и расширения

361
Glyph

Если я выполню тест в [[ ! (-z "") ]]интерактивном режиме или не в интерактивном режиме bash -c '[[ ! (-z "") ]]', я получу тот же результат, что и echo $?мне 1.

Но если я забуду пробел в приведенном выше выражении, давая [[ !(-z "") ]], я больше не получу тот же результат. Точнее,

set -x; [[ !(-z "") ]]; echo $? 

дает мне

+ [[ -n !(-z ) ]] + echo 0 0 

в то время как

bash -c 'set -x; [[ !(-z "") ]]; echo $?' 

дает мне

+ [[ -z '' ]] + echo 1 1 

Итак, мой вопрос: почему интерактивные и неинтерактивные оболочки не дают одинакового расширения, и поэтому одинаковый результат в этом случае (или других похожих)?

1

1 ответ на вопрос

0
Kamil Maciorowski

Это довольно сложно.

Сначала обратите внимание, что внутри не работает сглаживание [[ ]]. Если вы запускаете что-то вроде

set -x; [[ / == /* ]]; echo $? 

тогда вы увидите, /*что не расширяется, как в echo /*.

Теперь есть extglobвариант оболочки. Похоже, в вашем случае он включен в интерактивных оболочках, отключен в неинтерактивных. (Сравните. Где в моей интерактивной оболочке включен «shopt extglob»? )

Если extglobопция оболочки включена с помощью shoptвстроенной функции, распознаются несколько расширенных операторов сопоставления с образцом. [...]

!(pattern-list)
Совпадает с чем угодно, кроме одного из заданных шаблонов.

( источник )

Внутри [[ ]]оператор распознается, но не расширяется. Откуда мне знать? Я могу заставить оболочку не узнавать это:

shopt -u extglob set -x; [[ !(-z "") ]]; echo $? 

Теперь я получаю -bash: !: event not found. Это указывает на !другое значение:

!
Запустите подстановку истории, за исключением случаев, когда за ней следует пробел, табуляция, конец строки =или ((когда extglobопция оболочки включена с помощью shoptвстроенной функции).

( источник )

Это отличается между интерактивными и неинтерактивными оболочками:

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

[…]
7. История команд […] и расширение истории […] включены по умолчанию.

( источник )

Следующий шаг - отключить историю:

shopt -u extglob set +o history set -x; [[ !(-z "") ]]; echo $? 

И вот, результат, как в вашей неинтерактивной оболочке.


Можем ли мы сделать это наоборот? Можем ли мы заставить неинтерактивную оболочку вести себя как интерактивная?

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

bash -ic 'set -x; [[ !(-z "") ]]; echo $?' 

Он ведет себя, как и ожидалось, как ваша интерактивная оболочка. Теперь давайте включим extglobнеинтерактивную оболочку:

bash -c 'shopt -s extglob; set -x; [[ !(-z "") ]]; echo $?' 

И это не работает! Он по-прежнему ведет себя как ваша неинтерактивная оболочка с extglobотключенным. Объяснение простое, но не очевидное: глобализация выполняется для всей строки, прежде чем shoptона выполнит свою работу. Когда опция меняется, уже слишком поздно. Давайте разделим команду на две строки:

bash -c 'shopt -s extglob set -x; [[ !(-z "") ]]; echo $?' 

Теперь он ведет себя как ваша интерактивная оболочка.


Последняя загадка заключается в следующем:

+ [[ -n !(-z ) ]] 

Откуда -nвзялась ваша интерактивная оболочка? Моя гипотеза основана на наблюдении, [[ "random-string" ]]которое рассматривается как [[ -n "random-string" ]](проверьте его с помощью set -x). Я предполагаю, что когда вы запускаете [[ !(-z "") ]]и extglobвключаете, тот факт, что !( )оператор распознается, но не раскрывается, заставляет всю !(-z "")строку оставаться там как одно слово, т.е.

set -x; [[ "!(-z )" ]]; echo $? 

Эта команда (в интерактивной или неинтерактивной оболочке) ведет себя так же, как та, которую вы запускаете в интерактивной оболочке.

Похожие вопросы