Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

A lot of command-line utilities can take their input either from a pipe or as a filename argument. For long shell scripts, I find starting the chain off with a cat makes it more readable, especially if the first command would need multi-line arguments.

Compare

sed s/bla/blaha/ data \
| grep blah \
| grep -n babla

and

cat data \
| sed s/bla/blaha/ \
| grep blah \
| grep -n babla

Is the latter method less efficient? If so, is the difference enough to care about if the script is run, say, once a second? The difference in readability is not huge.

share|improve this question
8  
I spend way more time watching people attack each other about useless cat usage on this site than my system does actually starting the cat processes –  Michael Mrozek Jul 8 '11 at 14:23
    
@Michael: 100% agree. Heck it took me more time to link to the old usenet award once than my computer will ever waste instantiating cat. However I think the bigger question here is code readability which often is a priority over performance. When faster can actually be written prettier, why not? Pointing out the issue with cat usually leads to the user having a better understanding of pipelines and processes in general. It's worth the effort so they write comprehensible code next time around. –  Caleb Jul 8 '11 at 15:03
2  
I actually have another reason I don't like the first form - if you want to add another command at the beginning of the pipeline, you have to move the argument too, so the editing is more annoying. (Of course, this doesn't mean you have to use cat; Caleb's point about using functions and redirection solves that as well.) –  Jefromi Jul 17 '11 at 7:15
add comment

2 Answers

up vote 21 down vote accepted

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.

share|improve this answer
8  
I didn't know that <file could come before the command. This solves all of my problems! –  Tim N Jul 8 '11 at 13:43
3  
@Tim: Bash and Zsh both support that, although I think it's ugly. When I'm worried about my code being pretty and maintainable I usually use functions to clean it up. See my last edit. –  Caleb Jul 8 '11 at 13:55
    
There are cases where cat with ≤1 argument is useful. $(cat /some/file) is an obvious one. Another is cat at the end of a pipeline, to force a command to output to a pipe rather than a regular file or terminal. –  Gilles Jul 8 '11 at 21:41
2  
@Tim <file can come anywhere on the command line: <file grep needle or grep <file needle or grep needle <file. The exception is complex commands such as loops and groupings; there the redirection must come after the closing done/}/)/etc. @Caleb This holds in all Bourne/POSIX shells. And I disagree that it's ugly. –  Gilles Jul 8 '11 at 21:43
3  
@Gilles, in bash you can replace $(cat /some/file) with $(< /some/file), which does the same thing but avoids spawning a process. –  cjm Jul 10 '11 at 20:27
show 1 more comment

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.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.