12

I have a black box UNIX program used in a Bash shell that reads columns of data from stdin, processes them (applying a smoothing effect) then outputs to stdout. I use it by UNIX pipes, like

generate | smooth | plot  

For more smoothing, I can repeat the smooth, so it'd be invoked from the Bash command line as

generate | smooth | smooth | plot   

or even

generate | smooth | smooth | smooth | smooth | smooth | smooth | smooth | smooth | smooth | smooth | plot

This is getting unweildy. I would like to make a Bash wrapper to be able to pipe into smooth and feed its output right back into a new instance of smooth an arbitrary number of times, something like

generate | newsmooth 5 | plot

instead of

generate | smooth | smooth | smooth | smooth | smooth | plot

My first attempt was a Bash script that generated temp files in the current directory and deleted them, but that turned ugly when I wasn't in a directory with write access, and also left garbage files when interrupted.

There are no arguments to the smooth program.

Is there a more elegant way to "wrap" such a program to parameterize the number of calls?

  • 1
    I hope your example is a forced case for the sake of the question and not an actual need – arielnmz Jun 19 at 22:25
18

You could wrap it in a recursive function:

smooth() {
  if [[ $1 -gt 1 ]]; then # add another call to function
    command smooth | smooth $(($1 - 1)) 
  else
    command smooth # no further 
  fi
}

You would use this as

generate | smooth 5 | plot

which would be equivalent to

generate | smooth | smooth | smooth | smooth | smooth | plot
5

If you can afford to type as many commas as the amount of smooth commands you want, you might take advantage of the shell's comma-separated Brace Expansion.

TL; DR

The whole command-line for your sample case would be:

generate | eval 'smooth |'{,,,,} plot

Note:

  • add or remove commas if you want more or fewer repetitions of smooth |
  • there's no | before plot because that's included in the last smooth | string produced by the Brace Expansion
  • you can also provide arguments to smooth, as long as you can include them correctly within the quoted fixed part that precedes the open brace; in any case remember that you'd be providing them to all repetitions of the command

How it works

Comma-separated Brace Expansion allows you to dynamically produce strings, each made of a specified fixed part plus the specified variable parts. It produces as many strings as there are variable parts indicated, like a{b,c,d} produces ab ac ad.

The little trick here is that if you rather make a list of empty variable parts, i.e. with only commas inside the braces, the Brace Expansion will just produce copies of the fixed part only. For instance:

smooth{,,,,}

will produce:

smooth smooth smooth smooth smooth

Note that 4 commas produces 5 smooth strings. That's just how this Brace Expansion works: it produces strings as many commas plus one.

Of course in your case you need also a | separating each smooth, so just add it in the fixed part but take care to quote it properly to have the shell not interpret it at once. That is:

'smooth|'{,,,,}

will produce:

'smooth|' 'smooth|' 'smooth|' 'smooth|' 'smooth|'

Take care of always placing the fixed part immediately adjacent to the open brace, i.e. no spaces between the ' and the {.

(Note also that to form the fixed part you may also use double-quotes instead of single-quotes, if you need to expand shell variables in the fixed part. Just take care of the extra escaping that are required when some shell's special characters occur inside a double-quoted string).

At this point you need an eval applied to that string in order to make the shell finally interpret it as the pipelined command it is supposed to be.

Thus, to sum it all, the whole command-line for your sample case would be:

generate | eval 'smooth |'{,,,,} plot
  • 1
    There are significant security concerns if this is used in places where the call is parameterized. See my answer on Recursive bash function vs iterative “eval” string building: Which performs better? over on Stack Overflow. – Charles Duffy Jun 19 at 19:16
  • 1
    @CharlesDuffy I fully agree with your concerns about the implied risks on using eval when one provides untrusted, non-sanitized, strings for it to evaluate, that is when used with variables that may carry "unknown" content like the case you linked. On the other hand, eval can also be very handy for quick "plumbing" of commands, especially when used at the prompt, like the case at hand seems to be, where eval's input would only be a literal string typed manually by the user in person – LL3 Jun 19 at 21:45
  • As already seen elsewhere, you can always replace eval str with something pretentious & stupid like . /dev/stdin <<<str. Not only will this make an impression on fools, it will also keep @CharlesDuffy off your back ;-) – pizdelect Jun 20 at 1:21
  • 1
    @pizdelect, you might read LL3's prior comment carefully -- it's balanced, nuanced and wise. (Indeed, my own initial comment had nuances you seem to be ignoring; "if used in cases where the call is parameterized" is a critical distinction: LL3's instance isn't parameterized, making it safe). – Charles Duffy Jun 20 at 2:12

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.