1

I have the following script :

#!/bin/bash

# initialisation of the script
mkdir -p test_dir
touch test_dir/test{1..15}
touch test_dir/test{a..e}

# enabling etended glob
shopt -s extglob

# we count the number of files which name is touchNUMBER
for f in test_dir/test+([0-9]); do ((count++)); done; echo $count

It works just fine and prints 15.

However, when I try to concatenate this script to a one-liner, it returns an error :

#!/bin/bash

mkdir -p test_dir
touch test_dir/test{1..15}
touch test_dir/test{a..e}

shopt -s extglob; for f in test_dir/test+([0-9]); do ((count++)); done; echo $count

Output :

./test.sh: line 7: syntax error near unexpected token `('

It seems bash doesn't evaluate the shopt -s extglob before determining the correctness of the syntax of this line.

EDIT:

Interestingly enough, replacing the incriminated line with :

shopt -s extglob; sleep 10;for f in test_dir/test+([0-9]); do ((count++)); done; echo $count

Displays the same error message instantly, thus confirming the error message is raised before the execution of the line.

Why is that ? Is there a way around ?

2 Answers 2

4

bash processes the script line by line. In the first case, shopt -s extglob has been executed by the time the for loop is parsed. In the error case, you have a single line that after parsing will be recognized as two commands separated by ;. However, this means shopt -x extglob has not yet been executed when bash needs to recognize the extended pattern +([0-9]).

There is no reason to make this a one-liner in a script. One-liners are meant to reduce typing for frequently executed interactive commands; there is no need to do so in a script, where readability should be prioritized.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for confirming my doubts about the behaviour of bash. Regarding the remark on one-liner, I know, but this script is an example. In fine, my goal is to use this one liner interactively
shopt -s extglob actually changes the grammar of bash, so it simply must be executed in a line prior to the line whose parsing would be affected by it.
what if I want to execute the shopt + for instructions in a subshell ? I guess I'll be forced to turn my one-liner in a two-liner ....
It's probably safe enough to just turn extended patterns on in your .bashrc or otherwise at the beginning of your shell session. They change the grammar just enough to risk breaking (in theory) an existing shell script, but they aren't likely to cause significant problems in typical interactive use. (Note that newer versions of bash do assume the use of extended patterns for the right-hand argument to = and != inside [[ ... ]] regardles of the setting of extglob.) Don't think of it as a command that must be executed immediately prior to the command that requires it.
0

Bash reads scripts or input line by line. Then it parses the whole line dividing it into tokens using metacharacters (| & ; ( ) < > space tab newline) while it also recognize quotes and expansions.
And, it is only the whole line has been characterized that each part starts being executed.

In the simple case, each command should be placed in its own separate line.
This works:

$ shopt -u extglob
$ shopt -s extglob
$ echo "test_dir/test"+([0-9])
test_dir/test01 test_dir/test11 test_dir/test22

While this does not work:

$ shopt -u extglob
$ shopt -s extglob ; echo "test_dir/test"+([0-9])
bash: syntax error near unexpected token `('

But that is not the whole history. If we manage to delay the evaluation with quotes or with an expansion, the line will work:

$ shopt -u extglob
$ shopt -s extglob ; echo $(echo "test_dir/test"+([0-9]))
test_dir/test01 test_dir/test11 test_dir/test22

Or:

$ shopt -u extglob
$ shopt -s extglob ; a=$(echo test_dir/test+([0-9])); echo "$a"
test_dir/test01 test_dir/test11 test_dir/test22

The reason is that a sub-shell $(…) inherits the condition of the parent shell at the point of evaluation. The parent shell can not expand what will be inside a sub-shell until said sub-shell is actually started.

However, as correctly said by @chepner:

There is no reason to make this a one-liner in a script. One-liners are meant to reduce typing for frequently executed interactive commands; there is no need to do so in a script, where readability should be prioritized.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.