Это довольно сложно.
Сначала обратите внимание, что внутри [[ ]]
не работает сглаживание. Если вы запускаете что-то вроде
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 $?
Эта команда (в интерактивной или неинтерактивной оболочке) ведет себя так же, как та, которую вы запускаете в интерактивной оболочке.