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?
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.