Skip to main content
1 of 7
Stéphane Chazelas
  • 584.9k
  • 96
  • 1.1k
  • 1.7k

First, expecting options after non-option arguments is bad practice.

The standard command line interface is to have options before non-option arguments, and allow an optional -- to mark the end of options so you can accept non-option arguments that happen to start with -:

myscript -c -ofile.out -- -file1-.in -file2-.in

Same as:

myscript -co file.out -- -file1-.in -file2-.in

Where, as an example -o is an option that takes an argument, -c one that doesn't, -- marks the end of options, and -file1-.in, -file2-.in are two non-option arguments, taken as such despite starting with - because they are after the -- end-of-option argument.

You can use the standard getopts shell builtin to process options with that standard convention.

To handle input to be given either via command line or stdin, you'd do:

while getopts...; do
  # process options
  ...
done
# option processing done.
shift "$((OPTIND - 1))"
# now the positional parameters contain non-option arguments

if (( $# )); then
  args=("$@")
else
  # no non-option arguments, read them one per line from stdin
  readarray -t args
fi

The ((...)) is a kshism (also supported by bash), readarray a bashism. In sh, you'd do:

if [ "$#" -eq 0 ]; then
  # no non-option arguments, read them from stdin
  IFS='
' # split on newline
  set -o noglob
  set -- $(cat) # read and split+glob with glob disabled
fi

Where the result is stored in the positional parameters ($1, $2... "$@") instead of the $args array in the bash example above. Beware that in that case, empty lines are discarded.

Stéphane Chazelas
  • 584.9k
  • 96
  • 1.1k
  • 1.7k