10

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?

Peter Mortensen
  • 1,029
  • 1
  • 8
  • 10
AbdAllah Talaat
  • 177
  • 1
  • 3
  • 10

3 Answers3

34

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

$ 
terdon
  • 234,489
  • 66
  • 447
  • 667
  • 4
    See also `shopt -s failglob` to get a more useful behaviour similar to that of modern shells like `zsh` or `fish`. – Stéphane Chazelas Mar 07 '18 at 22:24
  • I agree with Stéphane, use `failglob`. `nullglob` can cause unexpected issues, e.g. when pasting a URL that happens to have a `?`. – Kevin Mar 07 '18 at 23:39
  • 1
    Sure, I only mentioned `nullglob` to demonstrate that the pattern is being interpreted as a glob by the shell. – terdon Mar 08 '18 at 00:55
14

{1,2,3} is brace expansion, it expands to the words listed without regard to their meaning.

[...] is a character group, used in filename expansion (or wildcard, or glob) similarly to the asterisk * and question mark ?. It matches any single character listed within, or characters that are members of named groups such as [:digit:] if those are listed. The default behaviour of most shells is to leave the wildcard as-is if there are no files that match it.

(Note that you can't really turn a wildcard/pattern into the set of strings it would match. The asterisk can match any string of any length, so expanding any pattern containing it would produce an infinite list of strings. )

So:

$ bash -c 'echo [[:digit:]]'           # bash leaves it as-is
[[:digit:]]
$ zsh -c 'echo [[:digit:]]'            # zsh by default complains if no match
zsh:1: no matches found: [[:digit:]]
$ touch 1 3 d i g t
$ bash -c 'echo [[:digit:]]'           # now there are two matches
1 3                                    # note that d, i, g and t do NOT match

But still:

$ bash -c 'echo {1,2,3}'
1 2 3

Both of those are expanded by the shell, it doesn't matter if the command you're running is ls, or echo or rm. Also note that if either of those is quoted, they will not be expanded:

$ bash -c 'echo "[[:digit:]]"'         # even though matching files still exist
[[:digit:]]
$ bash -c 'echo "{1,2,3}"'
{1,2,3}
ilkkachu
  • 133,243
  • 15
  • 236
  • 397
  • thanks for your answer , am new to linux so let me please ask you how echo is related to files 1 3 , it's function is to print its arguments to stdout not searching for files as to my knowledge – AbdAllah Talaat Mar 07 '18 at 19:05
  • 1
    @AbdAllahTalaat this has nothing to do with echo, actually. The shell (e.g. bash) will "expand" the `[[:digit:]]` _before_ passing it to `echo`, so `echo` never sees `[[:digit:]]`, it sees only `1 3`. You can see this in action by running `set -x` which will print the actual commands being run (run `set +x` to turn it off again). – terdon Mar 07 '18 at 19:07
  • @AbdAllahTalaat, `echo` doesn't look for files, _the shell_ does, before running the `echo`. – ilkkachu Mar 07 '18 at 19:07
  • Especially since I think in DOS/Windows the utilities expand the wildcards, not the shell. (I maybe wrong) – ilkkachu Mar 07 '18 at 19:13
  • sorry guys I shifted the correct answer to tedron's answer because his comment contained the meaning that bash is what does the work not echo ... his answer too contained that meaning .. all of you helped me ... i wished if i could put correct answer for all your answers and comments – AbdAllah Talaat Mar 07 '18 at 19:17
4

{1,2,3} (and e. g. {1..3} are brace expansions. They are interpreted by the shell before command execution.

[[:digit:]] is a pattern matching token, but you're not using it in a location with any files which match that pattern. If you use a pattern match which has no matches, it expands to itself:

$ echo [[:digit:]]; touch 3; echo [[:digit:]]
[[:digit:]]
3
DopeGhoti
  • 73,792
  • 8
  • 97
  • 133