Should I care about unnecessary cats?
The "definitive" answer is of course brought to you by The Useless Use of cat
Award.
The purpose of cat is to concatenate (or "catenate") files. If it's only one file, concatenating it with nothing at all is a waste of time, and costs you a process.
Instantiating cat just so your code reads differently makes for just one more process and one more set of input/output streams that are not needed. Typically the real hold-up in your scripts is going to be inefficient loops and actuall processing. On most modern systems, one extra cat
is not going to kill your performance, but there is almost always another way to write your code.
Most programs, as you note, are able to accept an argument for the input file. However, there is always the shell builtin <
that can be used wherever a STDIN stream is expected which will save you one process by doing the work in the shell process that is already running.
You can even get creative with WHERE you write it. Normally it would be placed at the end of a command before you specify any output redirects or pipes like this:
sed s/blah/blaha/ < data | pipe
But it doesn't have to be that way. It can even come first. For instance your example code could be written like this:
< data \
sed s/bla/blaha/ |
grep blah |
grep -n babla
If script readability is your concern and your code is messy enough that adding a line for cat
is expected to make it easier to follow, there are other ways to clean up your code. One that I use a lot that helps make scripts easiy to figure out later is breaking up pipes into logical sets and saving them in functions. The script code then becomes very natural, and any one part of the pipline is easier to debug.
function fix_blahs () {
sed s/bla/blaha/ |
grep blah |
grep -n babla
}
fix_blahs < data
You could then continue with fix_blahs < data | fix_frogs | reorder | format_for_sql
. A pipleline that reads like that is really easy to follow, and the individual components can be debuged easily in their respective functions.
Here's a summary of some of the drawbacks of:
cat $file | cmd
over
< $file cmd
First, a note: there are (intentionally for the purpose of the discussion) missing double quotes around
$file
above. In the case ofcat
, that's always a problem except forzsh
; in the case of the redirection, that's only a problem forbash
orksh88
and, for some other shells (includingbash
in POSIX mode) only when interactive (not in scripts).The most often cited drawback is the extra process being spawned. Note that if
cmd
is builtin, that's even 2 processes in some shells likebash
.Still on the performance front, except in shells where
cat
is builtin, that also an extra command being executed (and of course loaded, and initialised (and the libraries it's linked to as well)).Still on the performance front, for large files, that means the system will have to alternately schedule the
cat
andcmd
processes and constantly fill up and empty the pipe buffer. Even ifcmd
does1GB
largeread()
system calls at a time, control will have to go back and forth betweencat
andcmd
because a pipe can't hold more than a few kilobytes of data at a time.Some
cmd
s (likewc -c
) can do some optimisations when their stdin is a regular file which they can't do withcat | cmd
as their stdin is just a pipe then. Withcat
and a pipe, it also means they cannotseek()
within the file. For commands liketac
ortail
, that makes a huge difference in performance as that means that withcat
they need to store the whole input in memory.The
cat $file
, and even its more correct versioncat -- "$file"
won't work properly for some specific file names like-
(or--help
or anything starting with-
if you forget the--
). If one insists on usingcat
, he should probably usecat < "$file" | cmd
instead for reliability.If
$file
cannot be open for reading (access denied, doesn't exist...),< "$file" cmd
will report a consistent error message (by the shell) and not runcmd
, whilecat $file | cmd
will still runcmd
but with its stdin looking like it's an empty file. That also means that in things like< file cmd > file2
,file2
is not clobbered iffile
can't be opened.Or in other words you can choose the order in which the input and output files are opened as opposed to
cmd file > file2
where the output file is always opened (by the shell) before the input file (bycmd
), which is hardly ever preferable.Note however that it won't help in
cmd1 < file | cmd2 > file2
wherecmd1
andcmd2
and their redirections are performed concurrently and independently and which you'd need to write as{ cmd1 | cmd2; } < file > file2
or(cmd1 | cmd2 > file2) < file
for instance to avoidfile2
being clobbered andcmd1
andcmd2
being run iffile
can't be opened.
Putting <file
on the end of a pipeline is less readable than having cat file
at the start. Natural English reads from left to right.
Putting <file
a the start of the pipeline is also less readable than cat, I would say. A word is more readable than a symbol, especially a symbol which seems to point the wrong way.
Using cat
preserves the command | command | command
format.