0

I have a bash script which processes an input file with optional arguments. The script looks like this

#!/bin/bash
while getopts a:b:i: option
do
case "${option}"
in
a) arg1=${OPTARG};;
b) arg2=${OPTARG};;
i) file=${OPTARG};;
esac
done

[ -z "$file" ] && { echo "No input file specified" ; exit; }

carry out some stuff

The script runs fine, but I need to specify the input file like so

sh script.sh -a arg1 -b arg2 -i filename

I would prefer to be able to call the script without the -i option, like so

sh script.sh -a arg1 -b arg2 filename

while still having the error message when no input file is specified. Is there a way to do this?

2
  • And what should happen if the script is called as script.sh -i file1 -i file2 file3 file4? Oct 29, 2020 at 15:02
  • @Stéphane-Chazelas Hm, I don't care really (it's for my personal use only). It should just complain (and more importantly, do nothing) if I write something like sh script.sh -a arg1 or sh script.sh -a arg1 -b arg2.
    – 220284
    Oct 29, 2020 at 15:07

2 Answers 2

1
#!/bin/sh -

# Beware variables can be inherited from the environment. So
# it's important to start with a clean slate if you're going to
# dereference variables while not being guaranteed that they'll
# be assigned to:
unset -v file arg1 arg2

# no need to initialise OPTIND here as it's the first and only
# use of getopts in this script and sh should already guarantee it's
# initialised.
while getopts a:b:i: option
do
  case "${option}" in
    (a) arg1=${OPTARG};;
    (b) arg2=${OPTARG};;
    (i) file=${OPTARG};;
    (*) exit 1;;
  esac
done

shift "$((OPTIND - 1))"
# now "$@" contains the rest of the arguments

if [ -z "${file+set}" ]; then
  if [ "$#" -eq 0 ]; then
    echo >&2 "No input file specified"
    exit 1
  else
    file=$1 # first non-option argument
    shift
  fi
fi

if [ "$#" -gt 0 ]; then
  echo There are more arguments:
  printf ' - "%s"\n' "$@"
fi

I changed the bash to sh as there's nothing bash-specific in that code.

3
0

I would like to expand on Stéphane Chazelas' excellent answer in order to use bashism more on the question style, namely [ -z "$file" ] && { echo "No input file specified" ; exit; } -- instead of using ifs.

#!/bin/bash
unset -v file arg1 arg2

while getopts "a:b:i:" option; do
    case "$option" in
        a ) arg1="$OPTARG";;
        b ) arg2="$OPTARG";;
        i ) file="$OPTARG";;
        * ) exit 1;;
    esac
done

shift "$((OPTIND - 1))"

[ -z "$file" ] && [ "$#" -eq 0 ] && { echo "No input file" >&2; exit 1; }
[ -z "$file" ] && [ "$#" -gt 0 ] && { file="$1"; shift; }

echo "DEBUG: arg 1 is $arg1"
echo "DEBUG: arg 2 is $arg2"
echo "DEBUG: file  is $file"

[ "$#" -gt 0 ] && { echo "More arguments"; printf " - %s\n" "$@"; }
2
  • That's helpful too. Thanks!
    – 220284
    Nov 12, 2020 at 9:32
  • Not sure what you mean there. && / || are not bashisms. They were already present in the Bourne shell in the 70s. They're also present in csh which make them more portable than if whose syntax is different between Bourne-like shells and csh. Using them in place of if constructs is often considered bad practice though. Note that [ -z "$file" ] would also return true if the script is called with -i '' (likely not a problem in practice) Nov 29, 2021 at 15:28

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .