1

If I execute the test [[ ! (-z "") ]] interactively, or non interactively by bash -c '[[ ! (-z "") ]]', I obtain the same result, that is echo $? gives me 1.

But if I forget a space in the above expression, giving [[ !(-z "") ]], I don't obtain the same result anymore. More precisely,

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

gives me

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

while

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

gives me

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

So my question is: why interactive and non interactive shells don't give the same expansion, and so the same result in this case (or other similar ones)?

0

It's quite complicated.

At first please note globbing doesn't work inside [[ ]]. If you run something like

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

then you will see /* doesn't expand there like in echo /*.

Now there is extglob shell option. It looks like in your case it's enabled in interactive shells, disabled in non-interactive ones. (Compare Where is bash's “shopt extglob” turned on for my interactive shell?)

If the extglob shell option is enabled using the shopt builtin, several extended pattern matching operators are recognized. […]

!(pattern-list)
Matches anything except one of the given patterns.

(source)

Inside [[ ]] the operator is recognized but it doesn't expand. How do I know? I can make the shell not recognize it:

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

Now I get -bash: !: event not found. This indicates ! has different meaning:

!
Start a history substitution, except when followed by a space, tab, the end of the line, = or ( (when the extglob shell option is enabled using the shopt builtin).

(source)

This differs between interactive and non-interactive shells:

When the shell is running interactively, it changes its behavior in several ways.

[…]
7. Command history […] and history expansion […] are enabled by default.

(source)

Next step is to disable history:

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

And here you go, the result is like in your non-interactive shell.


Can we do this the other way around? Can we make a non-interactive shell behave like an interactive one?

At first let's check what happens when a new shell is forced to be interactive:

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

It behaves as expected, like your interactive shell. Now let's enable extglob for non-interactive shell:

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

And it doesn't work! It still behaves like your non-interactive shell with extglob disabled. The explanation is simple, yet not obvious: globbing is performed for the entire line before shopt does its job. When the option gets changed it's already too late. Let's split the command into two lines then:

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

Now it behaves like your interactive shell.


The last mystery is this:

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

Where did -n come from in your interactive shell? My hypothesis is based on the observation that [[ "random-string" ]] is treated as [[ -n "random-string" ]] (check it with set -x). I guess when you run [[ !(-z "") ]] and extglob is enabled, the fact that the !( ) operator is recognized but not expanded makes the entire !(-z "") string stay there as one word, i.e. your command becomes something like

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

This command (in interactive or non-interactive shell) behaves like the one you run in the interactive shell.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.