Ubuntu – the difference between ls [13] and ls {1,3}

bashcommand linescripts

I created three files in my directory using the command touch inv{1..3}.txt. Now I used two commands and their output is pretty much the same. Those two commands are ls inv[13].txt and ls {1,3}.txt.

So how are these two different? How are square brackets different compared to braces? Also I was googling for "linux [..]", but couldn't find anything, so could I know as to what these things are called in linux terminology?

Thanks

Best Answer

  • {} is called brace expansion. It has two forms: {x..y} and {x,y}, replace x and y with any alphabetic or numeric value.

    In the first form, {x..y} will be expanded to all values from x to y, inclusive. For example, {2..5} will be expanded to 2, 3, 4, 5.

    In the second explicit form, {x,y} will be expanded to only x, y. For example, {2,5} will be expanded to 2 and 5.

    If no expansion is possible, the pattern is treated literally e.g. in {a..3}.

    Important thing to note is that the brace expansion will be done (if possible) irrespective of any file match, while in file creation/matching.

  • [] is known as a pathname expansion (or globbing) pattern. Unlike brace expansion you can only use it while matching filenames, it will be treated literally if you use it while creating files.

    It has two forms: [xyz...] and [x-y].

    In the first form, it will match any single character inside the square brackets separately I.e. each character is treated individually. So for [25].txt it will match a file named 2.txt or 5.txt in the current directory, while for [245].txt it will match any file named 2.txt or 4.txt or 5.txt.

    In the second form, a range expansion is done first before matching individually, if possible otherwise treated literally. So for [2-5]. txt, it will match any file named 2.txt or 3.txt or 4.txt or 5.txt. If the range can't be expanded, it will treated literally e.g. in case of [a-3].txt.


An important behavioral difference between these two is when there are more patterns to match while less actually is matched. As the brace expansion is done no matter what, it will show error that certain file(s) does not exist while pathname expansion will match whatever possible without any error for the rest:

$ ls {1..4}.txt
ls: cannot access 4.txt: No such file or directory
1.txt  2.txt  3.txt

$ ls [1-4].txt
1.txt  2.txt  3.txt

Portability Note:

The [] pathname expansion operator is defined by POSIX hence portable among Bourne shell and derivatives.

The brace expansion, {}, is not defined by POSIX hence is not portable among all shells. For example, dash (sh in Ubuntu) does not have this. It was borrowed from csh and will work on certain shells only e.g. bash, zsh, ksh93, fish. Make sure the shell you are using supports this before implementing.


Example:

$ touch {1..3}.txt

$ ls {1..3}.txt
1.txt  2.txt  3.txt

$ ls {1,2,3}.txt
1.txt  2.txt  3.txt

$ ls {1..4}.txt
ls: cannot access 4.txt: No such file or directory
1.txt  2.txt  3.txt

$ ls {a..3}.txt
ls: cannot access {a..3}.txt: No such file or directory

$ ls [123].txt
1.txt  2.txt  3.txt

$ ls [1-3].txt
1.txt  2.txt  3.txt

$ ls [1-4].txt
1.txt  2.txt  3.txt

$ ls [a-3].txt
ls: cannot access [a-3].txt: No such file or directory