Shell – Unexpected behaviour with echo [[:digit:]]

shellwildcards

I'd like to ask:

Why is echo {1,2,3} expanded to 1 2 3 which is an expected behavior,
while echo [[:digit:]] returns [[:digit:]] while I expected it to print all digits from 0 to 9?

Best Answer

Because they are two different things. The {1,2,3} is an example of brace expansion. The {1,2,3} construct is expanded by the shell, before echo even sees it. You can see what happens if you use set -x:

$ set -x
$ echo {1,2,3}
+ echo 1 2 3
1 2 3

As you can see, the command echo {1,2,3} is expanded to:

echo 1 2 3

However, [[:digit:]] is a POSIX character class. When you give it to echo, the shell also processes it first, but this time it is being processed as a shell glob. it works the same way as if you run echo * which will print all files in the current directory. But [[:digit:]] is a shell glob that will match any digit. Now, in bash, if a shell glob doesn't match anything, it will be expanded to itself:

$ echo /this*matches*no*files
+ echo '/this*matches*no*files'
/this*matches*no*files

If the glob does match something, that will be printed:

$ echo /e*c
+ echo /etc
/etc

In both cases, echo just prints whatever the shell tells it to print, but in the second case, since the glob matches something (/etc) it is told to print that something.

So, since you don't have any files or directories whose name consists of exactly one digit (which is what [[:digit:]] would match), the glob is expanded to itself and you get:

$ echo [[:digit:]]
[[:digit:]]

Now, try creating a file called 5 and running the same command:

$ echo [[:digit:]]
5

And if there are more than one matching files:

$ touch 1 5       
$ echo [[:digit:]]
1 5

This is (sort of) documented in man bash in the explanation of the nullglob options which turns this behavior off:

nullglob
    If  set,  bash allows patterns which match no files (see
    Pathname Expansion above) to expand to  a  null  string,
    rather than themselves.

If you set this option:

$ rm 1 5
$ shopt -s nullglob
$ echo [[:digit:]]  ## prints nothing

$ 
Related Question