Search code examples
linuxbashcatls

bash order of commands?


after wondering how if can pipe ls into cat to output a bunch of files I realized a better way to do that is to cat $ $(ls *.py), but also realized that $(ls *.py) cat $ does not work:

$(ls *.py) cat $                                                   1 ⨯
bash: command not found: 1.py

to me this is weird, why does this happen?


Solution

  • First: don't use $(ls *.py), just use *.py. Adding the $(ls) doesn't do anything useful, but it does add a couple of layers of processing and potential confusion. See this question for details, but I'll give a quick summary near the end here.

    Second: when you use something like $(ls *.py) or *.py as part of a command like, the shell will "expand" it before executing the command. With *.py, that means replacing it with the names of the files matching *.py in the current directory. So if you use cat *.py, that'll expand to something like:

    cat 1.py 2.py file.py otherfile.py
    

    ...which is what you want. But *.py cat expands to something like:

    1.py 2.py file.py otherfile.py cat
    

    ...which tries to execute 1.py as the command name (which doesn't exist as a command, so you get a "command not found" error), with the other filenames and "cat" as arguments.

    With $(ls *.py) cat, it first goes through expanding *.py and running ls 1.py 2.py file.py otherfile.py, captures the output of that ("1.py 2.py file.py otherfile.py"), splits it into "words" (which probably correctly splits it into filenames... unless any files have spaces or you changed IFS) then tries to expand any of those that look like wildcards (just like it did the initial *.py), then finally builds a command out of those + cat. Just like before, that comes out to:

    1.py 2.py file.py otherfile.py cat
    

    which fails as you saw.