Bash is weird.

April 18th, 2007 by lucas

Consider the following command:
echo <(cat /etc/{motd,passwd})

(you can replace "echo" with any command that takes one file as argument and cannot take it as stdin)

It's obvious that the goal is to expand this to:
echo <(cat /etc/motd /etc/passwd)

Then to:
echo /dev/fd/63

However, it doesn't work like this:
$ echo <(cat /etc/{motd,passwd})
++ cat /etc/motd
++ cat /etc/passwd
+ echo /dev/fd/63 /dev/fd/62
/dev/fd/63 /dev/fd/62

But bash doesn't work like this. Brace expansion is done first, but inside parameters of the command (that is, <(cat /etc/{motd,passwd})) not words. So when <(cat /etc/{motd,passwd}) is expanded, the prefix is <(cat /etc/, the suffix is ")", so it's expanded to <(cat /etc/motd) <(cat /etc/passwd).

After reporting a bug about that, Chet Ramey gave me the correct way to reach the initial goal:
cat <(eval cat /etc/\{passwd,motd})

6 Responses to “Bash is weird.”

  1. Soren Hansen wrote on 04/18/07 at 11:27 am :

    /dev/fd/{62,63} ?!? What?

  2. Soren Hansen wrote on 04/18/07 at 11:29 am :

    Oh! Somewhere on the way to Planet Ubuntu, the

  3. Huygens wrote on 04/18/07 at 12:03 pm :

    I’m not sure I follow you… First there was a pb with the planet (Ubuntu) that stripped the ‘cat
    or
    cat
    Give me the same result (output).

    This was maybe the weird thing you wanted to point out:
    $ echo

  4. garaged wrote on 04/18/07 at 2:50 pm :

    I just don’t understand the need for the backslash, do you know why is there ?, actually I tested with and without and works just the same :), now I don’t know why it does actually work with the backslash :)

  5. Michael Hofmann wrote on 04/30/07 at 10:16 am :

    Thanks a lot for the tip. I never expected it to be useful when I read it on the planet, not until diff

  6. Michael Hofmann wrote on 04/30/07 at 10:16 am :

    Thanks a lot for the tip. I never expected it to be useful when I read it on the planet, not until diff <$(ls *.{h,cpp}) … complained about extra operands :-D.