1

Если я выполню тест [[ ! (-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

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

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

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 $?

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

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .