I can't remember how to append a command to a shell script. I searched for append, add, concat, and more without success.

Basically I have

belly = tail -n +"$HEAD" "$1" | head -n $((TAIL-HEAD+1))

if [ -z "${NUMBER+x}" ]; then # check if NUMBER exists
    tail -n +"$HEAD" "$1" | head -n $((TAIL-HEAD+1))
    tail -n +"$HEAD" "$1" | head -n $((TAIL-HEAD+1)) | cat -n

and it works fine, but I don't like the duplicate logic. I know that I could use a function or eval, but is there a simpler way to do this? In my head I have something like this:

belly = tail -n +$HEAD $1 | head -n $((TAIL-HEAD+1))

if [ -z "${NUMBER+x}" ]; then # check if NUMBER exists
    belly | cat -n

But it doesn't work. What am I missing?


Actually this may have been answered here: Conditional pipeline

That answer takes the shell if/then/else mechanic and uses it to embed logic inside pipeline. You can do that also with the && operator, but it is not so clean to read, so I prefer if.

Basically in your case the tail line would be like so (please note also the change of -z to -n):

tail -n +"$HEAD" "$1" |
head -n $((TAIL-HEAD+1)) |
if [ -n "${NUMBER+x}" ]; then cat -n; else cat; fi

(this may have some errors, I'm not so keen on Linux shell programming. But you get the point.)

  • You don't need to put the conditional in a subshell, just something | if ... fi works. But you do need an else branch, and a cat again, since otherwise the subshell/if-statement just eats up all input, if the condition isn't true. Try echo foo | if false ; then cat -n ; fi – ilkkachu Mar 26 '17 at 12:54

One option would be to set a variable to manage the changing part of the pipeline. If the only changing part is a command line parameter, the implementation is simple:

if [ -n "${NUMBER+x}" ]; then
somepipeline ... | cat $cat_arg

Though this way, it's hard to completely remove the last part of the pipeline, so we'll uselessly use a cat, but that's not likely to matter much. Also, this depends on not quoting the variable, which makes it impossible to use whitespace or glob characters within the changing arguments.

So an array would be better:

if [ -n "${NUMBER+x}" ]; then
    final=(cat -n)
somepipeline ... | "${final[@]}" 

Or you could create two functions and call one through a variable.

Incidentally, a shell function is also the answer to your other question.

# define the function
belly() {
    tail -n +"$HEAD" "$1" | head -n $((TAIL-HEAD+1))
if ... ; then
    belly "$1" | this
    belly "$1" | that

You need to pass $1 explicitly since functions have their own context of positional parameters.


Essentially what you want accomplished is to pass or not to pass the -n option to cat depending on the state of $NUMBER

Just do this:

tail -n +"$HEAD" "$1" | head -n $((TAIL-HEAD+1)) | cat ${NUMBER:+'-n'}

We can do away with the UUOC scenario as well:

eval "tail -n +\"$HEAD\" \"\$1\" | head -n \$((TAIL-HEAD+1)) ${NUMBER:+|cat -n}"
  • Clever. Is the useless call to cat is troublesome? – aloisdg says Reinstate Monica Mar 26 '17 at 12:37
  • @aloisdg This is not a case of UUOC. Just look at the complexity involved in doing away with the lone cat, IMHO not worth it. – user218374 Mar 26 '17 at 12:43
  • @aloisdg, it's an extra process and an extra step of copying data, but if you that starts to matter performance-wise, you shouldn't be using a three-command pipeline to begin with, but something like awk or perl instead. – ilkkachu Mar 26 '17 at 12:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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