2

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)
melvio
  • 762
  • 6
  • 24
  • 1
    `a=b*c` passes word splitting unmodified. As arithmetic expansion happens before path expansion `let a=b*c` does what you want. While in `let a=b * c`, after word splitting, you pass 3 arguments to `let`: `a=b`, `*` and `c`. Each is evaluated as an independent arithmetic expression. `*` passes this unmodified and finally reaches path expansion... If you try, let's say, `let a=b 2*3 c` there will be no error because `2*3` is a valid arithmetic expression. – Renaud Pacalet Aug 10 '21 at 06:56
  • 1
    @melvio : `let` is - like `((....)))` and `[[ .... ]]` a _syntactic construct_ and not a builtin like, say, `export`. Therefore, the usual expansion rules don't apply in the exactly same way. In particular, `let` imposes arithmetic context on its argument. – user1934428 Aug 10 '21 at 07:03
  • Note that `let "a= b * c"` would work as expected (and as `(( a= b * c ))` because the single argument is evaluated as an arithmetic expression, not the 3 sub-strings of it. – Renaud Pacalet Aug 10 '21 at 07:05
  • @RenaudPacalet wrong. there is no arithmetic expansion there. – oguz ismail Aug 10 '21 at 09:33
  • @oguzismail Well, just give it a try: `unset a b c; b=2; c=3; let "a= b * c"; echo $a`. – Renaud Pacalet Aug 10 '21 at 09:39
  • 1
    @RenaudPacalet Yes, there's no arithmetic expansion there. There is arithmetic **evaluation** though, carried out by `let`, but that takes place **after** all expansions are done. – oguz ismail Aug 10 '21 at 09:41
  • @oguzismail So what? What difference do you make between "_evaluated as an arithmetic expression_" (what I wrote) and "_arithmetic evaluation_" (what you wrote)? – Renaud Pacalet Aug 10 '21 at 09:44
  • 2
    @oguzismail Oh, I see, you were referring to my first comment, not the one just above yours. OK, you're right, there is a typo, it should be arithmetic evaluation and not arithmetic expansion. – Renaud Pacalet Aug 10 '21 at 09:48
  • 1
    @RenaudPacalet You said *As arithmetic expansion happens before path expansion `let a=b*c` does what you want.*, and I'm saying that is wrong, as nothing in that command qualifies as a token that can be subjected to arithmetic expansion. – oguz ismail Aug 10 '21 at 09:48

2 Answers2

2

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.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • You should probably read again the question. @melvio never pretended that `a=b*c` could be replaced by a file named `bcc`. What they wrote is that `let a=b * c` (spaces) does. – Renaud Pacalet Aug 10 '21 at 09:42
  • 2
    Hm... `In constrast, here no globbing happens, even though there is a match on # the 'bcc' file in this directory. let a=b*c` The "in contrast", is in contrast to `a=b * c`, the "here.....there is a match on bcc" - I think "here" is `a=b*c`. – KamilCuk Aug 10 '21 at 10:15
  • 2
    You're right. My understanding was different but after reading again I see that yours is the correct one. Sorry. – Renaud Pacalet Aug 10 '21 at 10:44
0

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
E_net4
  • 27,810
  • 13
  • 101
  • 139
melvio
  • 762
  • 6
  • 24