Aserre
Aserre

Reputation: 5062

When is syntaxic verification executed in a bash script?

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 ?

Upvotes: 1

Views: 57

Answers (2)

user8017719
user8017719

Reputation:

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.

Upvotes: 0

chepner
chepner

Reputation: 530990

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.

Upvotes: 4

Related Questions