3

I have a script where I've implemented switches using getopts. However, I'm having trouble referencing the next argument.

My script is for backporting a backup of our website on a local development environment. I've added a -p switch to run some post-deploy steps. Here's my syntax:

backport -p /path/to/website_backup.sql.gz

So, before the switch, I was testing that a file was specified, and that it's a proper file. Since a filename path was the only argument, I could assume that it was necessary, and also that it would be the first argument ($1).

if [[ $# -eq 0 ]] ; then
  echo 'Specifcy the sql file to backport.';
  exit 0;
fi

if [[ ! -f "$1" ]]; then
  echo "$1 is not a valid file.";
  exit 0;
fi

I found this answer which demonstrated an example of how to use getopts to parse switch arguments:

while getopts "p" opt; do
  case $opt in
    p) p_post_deploy=true ;; # Handle -a
  esac
done

Of course, using $1 as the argument didn't work after implementing getopts. Without the switch it was fine. But, when I added the switch, it was, of course, the first argument, which my script was testing for being a file.

$ backport -p /path/to/website_backup.sql.gz
-p is not a valid file.

So, with the introduction of switches, I can't rely on any argument appearing at particular position in the command. Hard-coding $2 won't work, because the filename argument won't be the second argument if there is no switch. I want a solution that will allow me to accept arguments after switches, and allow me to introduce new switches in the future while only updating the code to handle the new switches themselves (and not have to re-shuffle later arguments that may be moved after the introduction of a new switch).

I looked at the answers to the unix.stackexchange question that instructed me how to use getopts to parse switches. One of the answers mentioned $* as a variable representing the remaining arguments.

if [[ "$*" -eq 0 ]] ; then
  echo 'Specifcy the sql file to backport.';
  exit 0;
fi

However, when I try to use it, I'm not expressing syntax correctly, and I get a parse error.

~/scripts/backport: line 14: [[: /d/Downloads/database.sql.gz: syntax error: operand expected (error token is "/d/Downloads/database.sql.gz")

How do I test the filename argument after getops?


Here is the script in its current version:

$ cat backport
#!/bin/bash

set -e

while getopts "p" opt; do
  case $opt in
    p) p_post_deploy=true ;;
  esac
done

shift $(($OPTIND - 1))
# testing what this variable looks like
printf "Remaining arguments are: %s\n" "$*"

if [[ "$*" -eq 0 ]] ; then
  echo 'Specifcy the sql file to backport.';
  exit 0;
fi

if [[ ! -f "$*" ]]; then
  echo "$* is not a valid file.";
  exit 0;
fi

drush @local.dev sql-drop -y ;
zcat $1 | drush @local.dev sqlc ;
drush @local.dev cr;

if [ ! -z "$p_post_deploy" ] ; then
  echo "Running post-deploy..."
  SCRIPT_PATH=$(dirname "$BASH_SOURCE")
  source "$SCRIPT_PATH/post-deploy"
  post_deploy
fi
9
  • can you add a minimalist version of you script using getopts Commented Oct 31, 2019 at 1:33
  • @EchoMike444 I've edited to include the current form of the script
    – user394
    Commented Oct 31, 2019 at 13:07
  • Could you expand on "didn't work after implementing getopts"? What did your script look like after you'd added getopts, and what error did you encounter, when you were still trying to use $1 (ie. before you added those "$*", which are causing other problems)?
    – JigglyNaga
    Commented Oct 31, 2019 at 13:26
  • 2
    @user394 After shift "$(( OPTIND - 1 ))", the value of $1 will be the first argument after any option, not -p (this is why you do that shift, after all)
    – Kusalananda
    Commented Oct 31, 2019 at 13:54
  • 2
    @user394 No, you misunderstand the statement and what OPTIND is. The OPTIND variable holds the position of the first command line argument that is not an option. Shifting by this number, minus 1, always leaves the non-options. No need to update that statement ever.
    – Kusalananda
    Commented Oct 31, 2019 at 14:17

2 Answers 2

2

I can only blame myself for poor communication, but Kusalananda has given the answer I was looking for in a comment.

After using getopts to parse switches

while getopts "p" opt; do
  case $opt in
    p) p_post_deploy=true ;;
  esac
done

this line

shift "$((OPTIND - 1))"

will remove all switches from the list of arguments, so that you can use positional arguments again, just as you would without switches.

The shell does not reset OPTIND automatically. You have to reset it manually.

0

Your application of the test construct

if [[ "$*" -eq 0 ]]

is incorrect. -eq is used for integer comparisons, and given that $* refers to the argument list of all remaining arguments, not their number, your test construct is bound to produce a syntax error unless the list of remaining arguments at that point happens to contain only one element, which must also happen to represent an integer number (which is not what you want to achieve).

Now, for the actual task, I think it is difficult to mix "non-option parameters" (which are not preceded by an "announcing" -<some letter>) with option parameters as long as you allow an arbitrary order (i.e. as long as you do not specify that the filename must come last). If you want to use getopts, you may simply want to make the filename an option parameter too, by requiring it to be specified as -f <filename>. Then you can use

while getopts "pf:" opt; do
  case $opt in
    p)
       p_post_deploy=true
    ;;
    f)
       backportfile=$OPTARG
    ;;
  esac
done

and have getopts automatically complain if no filename is specified.

./test.sh: option requires an argument -- f

You can then still test later if the file exists, as

if [[ ! -f "$backportfile" ]]
then
  echo "$backportfile is not a valid file"
  exit 1
fi
6
  • Notice how the text in the question uses [[ $# -eq 0 ]] first (which is correct), but how the more complete code uses [[ "$*" -eq 0 ]] for the same test.
    – Kusalananda
    Commented Oct 31, 2019 at 13:40
  • @Kusalananda Yes, I noticed it. You mean it could have been a copy-and-paste error?
    – AdminBee
    Commented Oct 31, 2019 at 13:42
  • Or a re-typing error. In any case, $# -eq 0 is closer to what I think their intent was. You could also comment on the use of unquote variable expansions.
    – Kusalananda
    Commented Oct 31, 2019 at 13:53
  • I believe I've miscommunicated: -p does not require an argument; it's strictly an optional switch to the command. The command itself always requires an argument.
    – user394
    Commented Oct 31, 2019 at 14:18
  • Yes, I didn't get that part of your post correctly. Maybe my revised suggestion helps ...
    – AdminBee
    Commented Oct 31, 2019 at 14:34

You must log in to answer this question.

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