In a bash script, I'd like to load settings from a config file and override individual settings with command-line options. In cases where a setting is specified both in the config file and also on the command line, the command-line setting should take precedence.
How do you ensure the config file is loaded before the other getopts blocks? Here's what I've got:
#!/bin/bash
# ...
while getopts “c:l:o:b:dehruwx” OPTION
do
case $OPTION in
c)
echo "load"
CONFIG_FILE=$OPTARG
# load_config is a function that sources the config file
load_config $CONFIG_FILE
;;
l)
echo "set local"
LOCAL_WAR_FILE=$OPTARG
;;
# ...
esac
done
shift $(($OPTIND - 1))
No matter what order I put the handler for the -c option, it always loads the config file AFTER the other options are set. This makes it more of a pain to merge the config file settings with the command-line options.
Each call to getopts
always processes the "next" option (as determined by examining $OPTIND
), so your while
-loop will necessarily process the options in the order they appear.
Since you want -c
to be partly superseded by other options, even if it appears after them on the command-line, there are a few approaches you can take.
One is to loop over the options twice:
#!/bin/bash
# ...
optstring='c:l:o:b:dehruwx'
while getopts "$optstring" OPTION
do
case $OPTION in
c)
echo "load"
CONFIG_FILE=$OPTARG
# load_config is a function that sources the config file
load_config $CONFIG_FILE
esac
done
OPTIND=1
while getopts "$optstring" OPTION
do
case $OPTION in
l)
echo "set local"
LOCAL_WAR_FILE=$OPTARG
;;
# ...
esac
done
shift $(($OPTIND - 1))
Another is to save options in variables that -c
won't override, and then copy them over:
#!/bin/bash
# ...
while getopts c:l:o:b:dehruwx OPTION
do
case $OPTION in
c)
echo "load"
CONFIG_FILE=$OPTARG
# load_config is a function that sources the config file
load_config $CONFIG_FILE
;;
l)
echo "set local"
LOCAL_WAR_FILE_OVERRIDE=$OPTARG
;;
# ...
esac
done
shift $(($OPTIND - 1))
LOCAL_WAR_FILE="${LOCAL_WAR_FILE_OVERRIDE-${LOCAL_WAR_FILE}}"
(Or, conversely, the config file can set options like LOCAL_WAR_FILE_DEFAULT
, and then you'd write LOCAL_WAR_FILE="${LOCAL_WAR_FILE-${LOCAL_WAR_FILE_DEFAULT}}"
.)
Another option is to require that -c
, if present, come first. You can do that by handling it first yourself:
if [[ "$1" = -c ]] ; then
echo "load"
CONFIG_FILE="$2"
# load_config is a function that sources the config file
load_config "$CONFIG_FILE"
shift 2
fi
and then in your main while
-loop, just handle -c
by printing an error message.
Another is simply to document your existing behavior and call it a "feature". A lot of Unix utilities have later options supersede earlier ones, so this behavior isn't really a problem.
Assuming your config file contains the default options for your program, you should always use those options by default unless they are overridden by their equivalent command-line options. This is reasonable. In your case, simply source/load the config file first and then parse command-line options - and assigning new values to them in the parseopts
loop as needed.