Search code examples
linuxbashglobbrace-expansion

Strange output from bash one liner


While in the course of learning bash, I often tweak an existing thing and see it's output.

~$ for i in {1..19}; do echo "Everything in UNIX is a file."; sleep 1; done

I had this, and out of curiosity I tweaked the above one into the following:-

~$ for i in {1..19 * 2}; do echo "Everything in UNIX is a file."; echo "The value of i is ${i}";  sleep 1; done

Now to my surprise I started getting the following output :-

Everything in UNIX is a file.
The value of i is OneDrive
Everything in UNIX is a file.
The value of i is opera autoupdate
Everything in UNIX is a file.
The value of i is Personal_Workspace
Everything in UNIX is a file.
The value of i is Pictures
Everything in UNIX is a file.
The value of i is PrintHood
Everything in UNIX is a file.
The value of i is Recent
Everything in UNIX is a file.
The value of i is Roaming
Everything in UNIX is a file.
The value of i is Saved Games
Everything in UNIX is a file.
The value of i is Searches

Some of the values of i are the names of files and directories in my home directory, I am in home directory, while executing this script.

What I was expecting that the i values would range from 1 to 19*2 = 38, so i would take values from 1,2,3...30...38. But obviously it did n't Why?


Solution

  • Yes in bash, range expansion happens before everything else. You were expecting arithmetic expansion to happen which did not happen as expected because of the order of expansion bash shell. Your code ended up interpreting {1..19, * and 2} as literal strings.

    Since * has a special meaning in shell which is a glob expansion listing all files/directories in current folder. Also you could see one entry stating the other two strings interpreted literally.

    From the man bash(1) page under section Expansion

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

    You are much better off using a for loop with a ((..)) construct if you are targeting scripts for bourne again shell

    for ((i=1; i<=38; i++)); do