1

I've got a bash script I'd like to loop over the lines in stdin, or loop over each argument passed in.

Is there a clean way to write this so I don't have to have 2 loops?

#!/bin/bash

# if we have command line args... 
if [ -t 0 ]
then
  # loop over arguments
  for arg in "$@" 
  do
    # process each argument
  done
else
  # loop over lines from stdin
  while IFS= read -r line; do
    # process each line
  done
fi

EDIT: I'm looking for a generic solution that just uses a single loop, as I find I want to do this quite often, but have always wrote out 2 loops and then called a function instead. So maybe something that turns stdin into an array, so I could use a single loop instead?

  • 1
    What are you actually trying to accomplish ? Sounds like a typical XY question... – don_crissti Mar 23 '18 at 13:51
  • What's the problem you're trying to solve? What kind of processing needs to happen in the loop? – Kusalananda Mar 23 '18 at 14:15
  • thanks for the feedback! I write lots of little bash scripts that I like to be able to accepts args from the command line, or from stdin. The particular script i'm writing now is one that will concatenate arguments using a delimiter, so I can write both cat /tmp/it | concat and concat a b c and it'd join these arguments together in both cases, trimming each arg and putting a comma between items. e.g. a,b,c. – Brad Parks Mar 23 '18 at 14:18
3

Create data for your while read loop:

#!/bin/sh

if [ "$#" -gt 0 ]; then
    # We have command line arguments.
    # Output them with newlines in-between.
    printf '%s\n' "$@"
else
    # No command line arguments.
    # Just pass stdin on.
    cat
fi |
while IFS= read -r; do
    printf 'Got "%s"\n' "$REPLY"
done

Note that your concat example can be done with the while read loop replaced by tr '\n' ',' or similar.

Also, the -t test says nothing about whether you have command line arguments or not.


Alternatively, to process both command line arguments and standard input (in that order):

#!/bin/sh

{
    if [ "$#" -gt 0 ]; then
        # We have command line arguments.
        # Output them with newlines in-between.
        printf '%s\n' "$@"
    fi

    if [ ! -t 0 ]; then
        # Pass stdin on.
        cat
    fi
} |
while IFS= read -r; do
    printf 'Got "%s"\n' "$REPLY"
done

Or, using short-cut notation that some people seems to like:

#!/bin/sh

{
    [ "$#" -gt 0 ] && printf '%s\n' "$@"
    [ ! -t 0 ]     && cat
} |
while IFS= read -r; do
    printf 'Got "%s"\n' "$REPLY"
done
  • Note that that changes the behaviour if there are arguments that contain newline characters. – Stéphane Chazelas Mar 23 '18 at 15:04
  • @StéphaneChazelas Yes, command line arguments with literal newlines in them will be broken up into multiple items when they arrive at the loop. – Kusalananda Mar 23 '18 at 15:07
1

With bash specifically, you could do:

if [ -t 0 ]; then
  args=("$@")
else
  readarray -t args
fi
for i in "${args[@]}"; do
   ...
done

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.