How does spacing impact the globbing operator when used with a let construct?

Viewed 56

Note: Generally it is advised to use (( ... )) over let. let is subject to word splitting and glob expansion, which is something you rarely want in arithmetic evaluation. Sources: link link.

Regardless, I would love to better understand how globbing works within the following bash let-constructs:

$ set -o xtrace  # used to figure out what is happening


# This section shows that globbing is performed
$ b=2
$ c=4

# watch out: '*' is the globbing operator here, so * expands 
# to the files in the current directory 
$ let a=b * c # This line will expand to: 
              #     let a=b bcc declaration.sh let-basics.sh c
              # Only 'b' will be evaluated because the remainder
              # after 'b' causes a syntax error.
$ echo "${a}" 
2             # Only b was evaluated so 2 is expected


# In contrast, it seems as if no globbing happens 
# here, even though there is a match on the 'bcc' file 
# in this directory.
$ let a=b*c
$ echo "${a}" 
8

Why isn't the * evaluated in let a=b*c as the globbing operator?


I used the following bash version:

$ bash --version
GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
2 Answers

how globbing works

Filename expansion replaces a word that is a pattern by a list of files matching that pattern. You can learn about it in bash manual. See also man 7 glob.

within the following bash let-constructs:

Just like with any other commands. let is not special in any way.

# here no globbing happens, even though there is a match on 
# the 'bcc' file

I do not see how a=b*c could be replaced by a file named bcc. What happened to a=? = is a normal character. a=b*c does not match bcc. For example, a file named a=bcc could match a=b*c.

Why isn't the * evaluated in let a=b*c as the globbing operator?

It is, you just have no files to match that glob.

Thank you @Renaud, @KamilCuk, @Ogus, and @user1934428 for your comments and answers. Your lively discussion showed me exactly what my thinking error was.

Allow me to explain... My incorrect thinking was that the globbing only happens to the right-hand side of the = character.

Incorrect

So I was incorrectly thinking the following happened:

$ b=2
$ c=4

# 1. We first apply path expansion to `b*c` (INCORRECT)
# 2. I have the file 'bcc' in my current directory
# 3. So `b*c` expands to 'bcc' and we have `a=bcc`
# 4. I have no variable 'bcc' so 'a' should become 0
# 5. Then I got surprised because the result was 8 and 
#    after some probing, I posted this question on SE
$ let a=b*c
$ echo "${a}" 
8 

Step '1.' in the above code is wrong because the path expansion happens to a=b*c NOT to b*c. And, as @KamilCuk pointed out: a=b*c does not match bcc.

Correct

So the following happens:

$ b=2
$ c=4

# 1. Path expansion is applied to `a=b*c` (CORRECT)
# 2. There is no match with `a=b*c` in the 
#    current directory so `a=b*c` is kept 
# 3. `a=b*c` undergoes arithmetic evaluation to a=8
$ let a=b*c
$ echo "${a}"
8

To confirm, I placed the oddly named file a=b-matchglob-c in my directory.
Now, with this file in my directory, a=b*c will expand to a=b-matchglob-c.

$ b=2
$ c=4

# 1. Path expansion turns a=b*c into 
#    let a=b-matchglob-c
# 2. Arithmetic evaluation will turn that into
#    let a=2-0-4
# 3. And 2-0-4=-2
$ let a=b*c
$ echo "${a}"
-2
Related