Fortes


Merging piped stdin with command-line arguments within Bash

How to avoid using xargs at all costs

An old boat in Corcovado National Park

An old boat in Corcovado National Park Osa, Costa Rica

I am computer nerd who is easily seduced by automation, which means I end up writing a fair number of shell scripts. Even though I’ve written my fair share of Bash, my small primate brain never quite grasps the language and I always copy-paste my way through the task at hand, absorb nothing, and then pat myself on the back for being such a 1337 h4x0r.

Typically my needs are quite pedestrian, and I can immediately find an answer to plagiarize. My latest script, however, wasn’t solved within seconds so I’ve written up the solution for my very handsome future self.

The “Problem”

My script takes one or more command-line parameters, which is straightforward to invoke when you know the values:

# Called via command-line arguments
my_script item_one item_two

In other cases, the values I’m passing in are the output of some other command. As a godly Unix user, my instinct is to pipe the output (printf just for illustration):

# Called with piped input, one argument per line
printf "item_one\nitem_two" | my_script

The argument-handling Bash code I copy-paste every time only handles command-line arguments, and completely ignores any piped input. Here’s code that will read in the piped input and convert to command-line arguments, one per line:

# Copy command-line arguments over to new array
ARGS=( $@ )

# Read in from piped input, if present, and append to newly-created array
if [ ! -t 0 ]; then
  readarray STDIN_ARGS < /dev/stdin
  ARGS=( $@ ${STDIN_ARGS[@]} )
fi

# Single loop to process all arguments
for ARG in "${ARGS[@]}"; do
  echo "$ARG"
done

I’ve already forgotten I wrote this code, so don’t ask me what it does. I’m just gonna copy-paste it again in a few months, leave me alone!

Note that you can get around supporting piped input if you’re just willing to use xargs (which I also make sure to regularly forget how to use):

# This is way easier than what I just showed you
printf "item_one\nitem_two" | xargs my_script

Farewell, future self. Be glad that your tiny brain retains the vital lyrics to Ice Ice Baby instead of something as useless as xargs and Bash arrays.