0

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))
else
    tail -n +"$HEAD" "$1" | head -n $((TAIL-HEAD+1)) | cat -n
fi

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
else
    belly | cat -n
fi

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

2
  • 1
    Please take a look: shellcheck.net
    – Cyrus
    Commented Mar 26, 2017 at 11:51
  • @Cyrus done. This website rocks :)
    – aloisdg
    Commented Mar 26, 2017 at 12:07

3 Answers 3

1

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

1
  • 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
    Commented Mar 26, 2017 at 12:54
1

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:

cat_arg=""
if [ -n "${NUMBER+x}" ]; then
    cat_arg="-n"
fi    
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:

final=(cat) 
if [ -n "${NUMBER+x}" ]; then
    final=(cat -n)
fi    
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
else 
    belly "$1" | that
fi

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

1

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}"
3
  • Clever. Is the useless call to cat is troublesome?
    – aloisdg
    Commented Mar 26, 2017 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
    Commented Mar 26, 2017 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
    Commented Mar 26, 2017 at 12:57

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .