Order of Command substitution and Arithmetic expansion in bash

115 Views Asked by At

The book “Learning the bash Shell” 3rd ed. by Cameron Newham and Bill Rosenblatt has at page 181 a picture describing the command-line processing of bash shell. That picture shows that Command substitution is performed before Arithmetic substitution (sic). The bash man page, though, reports a different order: “The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution (done in a left-to-right fashion); word splitting; and pathname expansion.” Is the book wrong? If it is, can you please provide an example demonstrating it?

2

There are 2 best solutions below

0
Uberhumus On

1st of all, according to the official bash documentation The order is as follows:

Expansion is performed on the command line after it has been split into tokens. There are seven kinds of expansion performed:

  1. brace expansion
  2. tilde expansion
  3. parameter and variable expansion
  4. command substitution
  5. arithmetic expansion
  6. word splitting
  7. filename expansion

The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution (done in a left-to-right fashion); word splitting; and filename expansion.

As @socowi points out in his answer, there is a difference between ; and , when it comes to the order of operations, so kinds 2 - 5 are done simultaneously, left to right.

So as you've suspected your book is wrong. Now, I initially suspected that if command substitution would happen after arithmetic expansion we could wind up with situations like:

uberhumus@homepc:~$ kuki=$(echo "if command substitution happens before arithmetic substitution, this $((2+3)) is 5 else it's 2+3") 
uberhumus@homepc:~$ echo $kuki 
if command substitution happens before arithmetic substitution, this $((2+3)) is 5 else it's $((2+3))

This was wrong. As (echo "if command substitution happens before arithmetic substitution, this $((2+3)) is 5 else it's 2+3") is a command on its own that means that the expansion order occurs within it too so that situation is impossible.

In fact if you run set -vx before the above experiment you can see that, as the innermost expansion, $((2+3)) is performed immediately.

uberhumus@homepc:~$ set -vx
uberhumus@homepc:~$ kuki=$(echo "if command substitution happens before arithmetic substitution, this $((2+3)) is 5 else it's 2+3")
kuki=$(echo "if command substitution happens before arithmetic substitution, this $((2+3)) is 5 else it's 2+3")
++ echo 'if command substitution happens before arithmetic substitution, this 5 is 5 else it'\''s 2+3'
+ kuki='if command substitution happens before arithmetic substitution, this 5 is 5 else it'\''s 2+3'

uberhumus@homepc:~$ echo $kuki 
echo $kuki 
+ echo if command substitution happens before arithmetic substitution, this 5 is 5 else 'it'\''s' 2+3
if command substitution happens before arithmetic substitution, this 5 is 5 else it's 2+3
1
Socowi On

The official documentation is right (but mind the subtle difference between "," and ";" in there):

Command substitution $()/`` and arithmetic substitution $(()) have the same precedence. Whatever comes first when reading left to right, is expanded first.

You can confirm this with a test. Here, we use the built-in variable $SECONDS, which contains the time in seconds since bash started.

#! /bin/bash
echo "$(sleep 1; echo "command $SECONDS"; sleep 1), arithmetic $((SECONDS))"
echo "arithmetic $((SECONDS)), $(sleep 1; echo "command $SECONDS"; sleep 1)"

This prints

command 1, arithmetic 2
arithmetic 2, command 3