Bash Script – How to Read Filenames from a Pipe or Command Line Arguments

argumentsbashfilenamesstdin

I want my script to read a bunch of filenames (which may have spaces) given either as a glob or from STDIN and do stuff with them. I've been able to read either way separately, but not combine them.

This reads globs from the command line:

for filename in "$@"; do
    process_file "$filename"
done

And this reads from STDIN:

IFS=$'\n' read -d '' -r -a filenames
for filename in "${filenames[@]}"; do
    process_file "$filename"
done

What I really want is to read from either one into an array so I don't have to duplicate my entire for filename in... loop twice.
Sorry if this is obvious, I'm new to BASH.

EDIT: I think the best thing would be to read from args if they're given, and otherwise wait for STDIN. How would I do that?

EDIT: OK, the problem isn't what I thought it was. The problem is that process_file also asks for user input. Is there are way to read from STDIN until EOF, store that, and then start asking for input again?

Best Answer

You may use xargs to transform STDIN (even with a lot of entries, widely bigger than command line buffer) to arguments.

Or else, maybe something like:

if [ $# -gt 0 ] ;then
    for filename in  "$@" ; do
        process_file "$filename"
    done
  else
    IFS=$'\n' read -d '' -r -a filenames
    for filename in "${filenames[@]}"; do
        process_file "$filename"
    done
  fi

or both:

  IFS=$'\n' read -d '' -r -a filenames
  for filename in "${filenames[@]}" "$@" ; do
        process_file "$filename"
    done
  fi

or by adding such an option, like -l stand for line arguments only, to ensure no read (wait) from STDIN:

case "$1" in
    -l )
        shift
        for filename in "$@" ; do
            process_file "$filename"
        done
        ;;
     * )
        for filename in "${filenames[@]}" "$@" ; do
            process_file "$filename"
        done
        ;;
    esac

(Not tested!)

Related Question