I am experimenting with getopts in bash. I am adding the following snippet as an example.
script-setup.sh
set -e
...
package_version="v1.1.0"
grafana_username=""
grafana_password=""
subcommand=$1; shift
while [ ! -z "$subcommand" ]; do
case "$subcommand" in
package_version)
package_version=$1; shift; echo "Package_version:::: $package_version"
shift $((OPTIND -1))
;;
grafana_username)
grafana_username=$1; shift; echo "Grafana_username::: $grafana_username"
shift $((OPTIND -1))
;;
grafana_password)
grafana_password=$1; shift; echo "Grafana_password::: $grafana_password"
shift $((OPTIND -1));
;;
esac
subcommand=$1; shift;
done
touch sample.xml
echo "Creating a file"
echo "Package version chosen: $package_version"
...
Expected Result:
$ ./script-setup.sh package_version "v1.3.0" grafana_username "admin" grafana_password "abcd"
Package_version:::: v1.3.0
Grafana_username::: admin
Grafana_password::: abcd
Creating a file
Package version chosen: v1.3.0
Actual Result:
$ ./script-setup.sh package_version "v1.3.0" grafana_username "admin" grafana_password "abcd"
Package_version:::: v1.3.0
Grafana_username::: admin
Grafana_password::: abcd
As we see, in the above case: the script fails to create a file viz. sample.xml and fails to execute the last two echo statements. This is a confusing behavior according to me, or maybe I am wrong.
The loop in the bash script was replaced by for loop, but still nothing changed.
Bash version:
$ bash -version 1 ↵
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
Related
I'm trying to make a getopt command such that when I pass the "-ab" parameter to a script,
that script will treat -ab as a single parameter.
#!/bin/sh
args=`getopt "ab":fc:d $*`
set -- $args
for i in $args
do
case "$i" in
-ab) shift;echo "You typed ab $1.";shift;;
-c) shift;echo "You typed a c $1";shift;;
esac
done
However, this does not seem to work. Can anyone offer any assistance?
getopt doesn't support what you are looking for. You can either use single-letter (-a) or long options (--long). Something like -ab is treated the same way as -a b: as option a with argument b. Note that long options are prefixed by two dashes.
i was struggling with this for long - then i got into reading about getopt and getopts
single char options and long options .
I had similar requirement where i needed to have number of multichar input arguments.
so , i came up with this - it worked in my case - hope this helps you
function show_help {
echo "usage: $BASH_SOURCE --input1 <input1> --input2 <input2> --input3 <input3>"
echo " --input1 - is input 1 ."
echo " --input2 - is input 2 ."
echo " --input3 - is input 3 ."
}
# Read command line options
ARGUMENT_LIST=(
"input1"
"input2"
"input3"
)
# read arguments
opts=$(getopt \
--longoptions "$(printf "%s:," "${ARGUMENT_LIST[#]}")" \
--name "$(basename "$0")" \
--options "" \
-- "$#"
)
echo $opts
eval set --$opts
while true; do
case "$1" in
h)
show_help
exit 0
;;
--input1)
shift
empId=$1
;;
--input2)
shift
fromDate=$1
;;
--input3)
shift
toDate=$1
;;
--)
shift
break
;;
esac
shift
done
Note - I have added help function as per my requirement, you can remove it if not needed
That's not the unix way, though some do it e.g. java -cp classpath.
Hack: instead of -ab arg, have -b arg and a dummy option -a.
That way, -ab arg does what you want. (-b arg will too; hopefully that's not a bug, but a shortcut feature...).
The only change is your line:
-ab) shift;echo "You typed ab $1.";shift;;
becomes
-b) shift;echo "You typed ab $1.";shift;;
GNU getopt have --alternative option
-a, --alternative
Allow long options to start with a single '-'.
Example:
#!/usr/bin/env bash
SOPT='a:b'
LOPT='ab:'
OPTS=$(getopt -q -a \
--options ${SOPT} \
--longoptions ${LOPT} \
--name "$(basename "$0")" \
-- "$#"
)
if [[ $? > 0 ]]; then
exit 2
fi
A=
B=false
AB=
eval set -- $OPTS
while [[ $# > 0 ]]; do
case ${1} in
-a) A=$2 && shift ;;
-b) B=true ;;
--ab) AB=$2 && shift ;;
--) ;;
*) ;;
esac
shift
done
printf "Params:\n A=%s\n B=%s\n AB=%s\n" "${A}" "${B}" "${AB}"
$ ./test.sh -a aaa -b -ab=test
Params:
A=aaa
B=true
AB=test
getopt supports long format. You can search SO for such examples.
See here, for example
Based on tutorials I found here and here getopt should provide me with information about errors using some combination of characters :?*.
But when I used this code:
#!/bin/bash
eval set -- "$(getopt -o hspna: --long help,server,project,name-prefix,action: -- "$#")"
while [ : ]; do
case "$1" in
-s | --server)
echo "Setting server"
shift
;;
-p | --project)
echo "Setting project"
shift
;;
-n | --name-prefix)
echo "Setting name prefix"
shift
;;
-a | --action)
echo "Setting action"
shift
;;
--)
shift
break
;;
-h | --help)
echo "Providing help 1"
exit
;;
:)
echo "Providing help 2"
exit
;;
?)
echo "Providing help 3"
exit
;;
*)
echo "Providing help 4"
exit
;;
esac
done
echo $#
echo "Configured"
exit
Then following command that was supposed to show an error gave me the following output:
$ ./debug.sh -a -s -b -- foo bar baz
getopt: invalid option -- 'b'
Setting action
Setting server
foo bar baz
Configured
I was expecting that:
Providing help 2 will appear due to -a missing a value
Providing help 3 will appear due to -b not being a valid parameter
Providing help 4 will appear due to overall errors
Configured should never appear since the previous 3 points have an exit
But none of the above was true.
Also when testing further even more things did not work as expected.
# Expecting error due to missing value for `-a` but instead everything worked fine
$ ./debug.sh -a -s
Setting action
Setting server
Configured
# This time I expected everything to work fine, since I provided `X` as value of `-a`, but error was shown.
$ ./debug.sh -aX
Setting action
Providing help 3
What am I doing wrong?
What am I doing wrong?
Util-linux getopt prints and handles errors.
if ! args="$(getopt \
-n your_command \
-o hspna: \
--long help,server,project,name-prefix,action: \
-- "$#"\
)"; then
exit 1
fi
eval "set -- $args"
...
$ ./util -a
your_command: option requires an argument -- 'a'
I was expecting that:
I do not understand why. There is no such documentation in getopt. No, getopt will not output ? nor :. You can handle your (as the author of the program) errors, like you forgot to handle the option in case that you have given to getopt - you handle that with *).
The ? is a glob that matches any character. Because you forgot a shift after esac before done, X remains in $1, which is one character and is matched by ?). You meant '?'). This should go into *) case, and you should print yourself an error message.
Example, subjective in my style that I use (many people do not like set -eu):
set -euo pipefail
args=$(getopt -o ab -- "$#")
eval "set -- $args"
aflag=0
while (($#)); do
case "$1" in
-a) afloag=1; ;;
--) shift; break;
*) echo "Och no, I forgot about -b, or some other error!" >&2; exit 1; ;;
easc
shift
done
In the following script:
#!/usr/bin/env bash
func_usage ()
{
cat <<EOF \
USAGE: ${0} \
EOF
}
## Defining_Version
version=1.0
## Defining_Input
options=$(getopt -o "t:" -l "h,help,v,version,taxonomy:" -a -- "$#")
eval set -- "$options"
while true;do
case $1 in
-h|--h|-help|--help)
func_usage
exit 0
;;
-v|--v|-version|--version)
echo $version
;;
-t|--t|-taxonomy|--taxonomy)
echo "Option t = $2 ";
Taxonomy_ID=$2
echo $Taxonomy_ID
shift
;;
--)
shift
break;;
esac
shift
done
## Defining Taxonomy Default Value (in case is not provided)
TaxonomyID=${Taxonomy_ID:=9606};
echo $TaxonomyID
exit 0
The commands:
./script.sh -v
./script.sh --v
./script.sh -version
./script.sh --version
Work as expected. But what I do not understand is why the commands:
./script.sh -ver
./script.sh --ver
work at all. An equivalent unexpected behavior is also observed for the commands:
./script.sh -tax 22
./script.sh --tax 22
I would be grateful to get an explanation and/or a way to correct this unexpected behavior.
Note that getopt is an external utility unrelated to Bash.
what I do not understand is why the commands: .. work at all.
Because getopt was designed to support it, there is no other explanation. From man getopt:
[...] Long options may be abbreviated, as long as the abbreviation is not ambiguous.
Unambiguous abbreviations of long options are converted to long options.
Based on the comments I have received, specially from #CharlesDuffy, I have modified my code to what I believe is a more robust and compatible version. Importantly, the code below addresses the pitfalls of the original code
#!/usr/bin/env bash
func_usage ()
{
cat <<EOF
USAGE: ${0}
EOF
## Defining_Version
version=1.0
## Defining_Input
while true;do
case $1 in
-h|--h|-help|--help|-\?|--\?)
func_usage
exit 0
;;
-v|--v|-version|--version)
echo $version
;;
-t|--t|-taxonomy|--taxonomy)
echo "Option t = $2 ";
Taxonomy_ID=$2
echo $Taxonomy_ID
shift
;;
--)
shift
break;;
-?*)
printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2
;;
*)
break
esac
shift
done
TaxonomyID=${Taxonomy_ID:=9606};
echo $TaxonomyID
exit 0
The code above behaves as expected in that the commands:
./script -tax 22
Gives the warning:
WARN: Unknown option (ignored): -tax
9606
As expected
I want my bash script to only accept one letter in the switch, with nothing after it.
Here's my code (foo.sh):
#!/bin/bash
while getopts ":ap" jeep;do
case $jeep in
a)
echo 'Hi Brad'
;;
b)
echo 'Hi Eddy'
;;
else)
echo 'Hi whoever'
\?)
echo 'Bad input' >&2
exit 1
;;
esac
done
I want it to act like this:
$ foo.sh -a
Hi Brad
$ foo.sh -b
Hi Eddy
$ foo.sh
Hi whoever
$ foo.sh -r
Bad input
$ foo.sh -ab
Bad input
$ foo.sh -ba
Bad input
Instead, it runs both a and b (shown below) if I use -ab, which I don't want it to.
$ foo.sh -ab
Hi Brad
Hi Eddy
Also, else) doesn't work like I'd like it to. I want it to say Hi whoever if no switches are typed.
How can I fix this?
getopt does not have a way of specifying mutually exclusive flags, so you have to create your own. The simplest way to do this is probably something like this:
while getopts ":ab" jeep
do
case $jeep in
a)
if [ -n "${run-}" ]
then
exit 1
fi
echo 'Hi Brad'
run=1
;;
b)
if [ -n "${run-}" ]
then
exit 1
fi
echo 'Hi Eddy'
run=1
;;
\?)
if [ -n "${run-}" ]
then
exit 1
fi
echo 'Hi whoever'
run=1
;;
esac
done
I am having trouble with a bash script that is launched as a subshell from another shell. The script is installed in a number of different environments each of which have a variety of local-specific values set in a single config file per environment.
(UPDATE: actual code included)
Immediately on launch, the script needs to change to a directory whose value $scriptpath it must read from a configuration file that sets many other local variable values. (I know how to read this particular value from the correct line of config file using sed). The reason I have it set up this way is that the script sets configuration variables before it reads the positional parameters (so that the parameters will trump the configuration variables).
Because this script is used by others and must be easy to install, I do not want to set up aliases, functions, or shell procedures that would require a user to make changes to his local environment. I want all needed config values, including $scriptpath, to be in a single configuration file.
OR $scriptpath could be set from the parent shell. BUT because I process the positional parameters later in the script, I have had trouble processing them both right at the top and later.
the script is triggered from a shell script that is launched by a Magento extension. The squiggly brackets are values drawn from the Magento database by the extension.
scriptpath={{var base_path}}
echo $scriptpath
export scriptpath
/opt/bitnami/apache2/htdocs/git-pk-production/pagekicker-community/scripts/bin/builder.sh --scriptpath "{{var base_path}}"--seedfile "{{var base_tmp_path}}/seedlist" --booktype "{{var product.booktype}}" --buildtarget "{{var target}}" --jobprofile "{{var product.jobprofile}}.jobprofile" --booktitle "{{var product.name}}" --truncate_seed "yes" --ebook_format "mobi" --sample_tweets "no" --wikilang "{{var product.wikilang}}" --coverfont "{{var product.coverfont}}" --covercolor "{{var product.covercolor}}" --customername "{{var customer.name}}" --yourname "{{var product.yourname}}"
builder.sh:
#!/bin/bash
# accepts book topic and book type definition, then builds book
echo "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
#scriptpath=$(cat scriptpath.var) #works but is not portable
# echo $scriptpath
cd $scriptpath
. ../conf/config.txt
. includes/set-variables.sh
#echo "set variables, now echoing them"
# . includes/echo-variables.sh
echo "revision number is" $SFB_VERSION
echo "sfb_log is" $sfb_log
echo "completed reading config file and beginning logging at" `date +'%m/%d/%y%n %H:%M:%S'`
jobprofile="default"
jobprofilename="default"
singleseed="none"
sample_tweets="no"
todaysdate=`date`
wikilang="en"
summary="false"
truncate_seed="yes"
coverfont="Minion"
covercolor="RosyBrown"
export PERL_SIGNALS="unsafe"
echo "PERL_SIGNALS" is $PERL_SIGNALS
while :
do
case $1 in
--help | -\?)
echo "for help review source code for now"
exit 0 # This is not an error, the user requested help, so do not exit status 1.
;;
--passuuid)
passuuid=$2
shift 2
;;
--passuuid=*)
passuuid=${1#*=}
shift
;;
--seedfile)
seedfile=$2
shift 2
;;
--seedfile=*)
seedfile=${1#*=}
shift
;;
--booktype)
booktype=$2
shift 2
;;
--booktype=*)
booktype=${1#*=}
shift
;;
--booktitle)
booktitle=$2
shift 2
;;
--booktitle=*)
booktitle=${1#*=}
shift
;;
--buildtarget)
buildtarget=$2
shift 2
;;
--buildtarget=*)
buildtarget=${1#*=}
shift
;;
--singleseed)
singleseed=$2
shift 2
;;
--singleseed=*)
singleseed=${1#*=}
shift
;;
--truncate_seed)
truncate_seed=$2
shift 2
;;
--truncate_seed=*)
shift
truncate_seed=${1#*=}
;;
--sample_tweets)
sample_tweets=$2
shift 2
;;
--sample_tweets=*)
shift
sample_tweets=${1#*=}
;;
--ebook_format)
ebook_format=$2
shift 2
;;
--ebook_format=*)
shift
ebook_format=${1#*=}
;;
--jobprofile)
jobprofile=$2
shift 2
;;
--jobprofile=*)
jobprofile=${1#*=}
shift
;;
--jobprofilename)
jobprofilename=$2
shift 2
;;
--jobprofilename=*)
jobprofilename=${1#*=}
shift
;;
--wikilang)
wikilang=$2
shift 2
;;
--wikilang=*)
wikilang=${1#*=}
shift
;;
--summary)
summary=$2
shift 2
;;
--summary=*)
summary=${1#*=}
shift
;;
--safe_product_name)
safe_product_name=$2
shift 2
;;
--safe_product_name=*)
safe_product_name=${1#*=}
shift
;;
--coverfont)
coverfont=$2
shift 2
;;
--coverfont=*)
coverfont=${1#*=}
shift
;;
--covercolor)
covercolor=$2
shift 2
;;
--covercolor=*)
covercolor=${1#*=}
shift
;;
--fromccc)
fromccc=$2
shift 2
;;
--fromccc=*)
fromccc=${1#*=}
shift
;;
--editedby)
editedby=$2
shift 2
;;
--editedby=*)
editedby=${1#*=}
shift
;;
--yourname)
yourname=$2
shift 2
;;
--yourname=*)
yourname=${1#*=}
shift
;;
--customername)
customername=$2
shift 2
;;
--customername=*)
customername=${1#*=}
shift
;;
--storecode)
storecode=$2
shift 2
;;
--storecode=*)
storecode=${1#*=}
shift
;;
--environment)
environment=$2
shift 2
;;
--environment=*)
environment=${1#*=}
shift
;;
--shortform)
shortform=$2
shift 2
;;
--shortform=*)
shortform=${1#*=}
shift
;;
--flickr)
flickr=$2
shift 2
;;
--flickr=*)
flickr=${1#*=}
shift
;;
--dontcleanupseeds)
dontcleanupseeds=$2
shift 2
;;
--dontcleanupseeds=*)
dontcleanupseeds=${1#*=}
shift
;;
--batch_uuid)
batch_uuid=$2
shift 2
;;
--batch_uuid=*)
batch_uuid=${1#*=}
shift
;;
--) # End of all options
shift
break
;;
-*)
echo "WARN: Unknown option (ignored): $1" >&2
shift
;;
*) # no more options. Stop while loop
break
;;
esac
done
# Suppose some options are required. Check that we got them.
if [ ! "$passuuid" ] ; then
echo "creating uuid"
uuid=$("$PYTHON_BIN" -c 'import uuid; print uuid.uuid1()')
echo "uuid is" $uuid | tee --append $xform_log
mkdir -p -m 777 $TMPDIR$uuid
else
uuid=$passuuid
echo "received uuid " $uuid
mkdir -p -m 777 $TMPDIR$uuid
fi
if [ -z "$covercolor" ]; then
covercolor="RosyBrown"
echo "no cover color in command line so I set it to "$covercolor
else
echo "$covercolor"
fi
if [ -z "$coverfont" ]; then
coverfont="Minion"
echo "no cover font in command line so I set it to "$coverfont
else
echo "$coverfont"
fi
if [ -z "$wikilang" ]; then
wikilang="en"
echo "no wikilang in command line so I set it to "$wikilang
else
echo "$wikilang"
fi
echo "debug: booktitle is $booktitle"
echo "debug: scriptpath is $scriptpath"
The result is this:
scriptpath={{var base_path}}
echo $scriptpath
export scriptpath
Executing: /opt/bitnami/apache2/htdocs/git-pk-production/pagekicker-community/scripts/bin/builder.sh --seedfile "/opt/bitnami/apps/magento/htdocs/media/downloadable/tmp/build/8/seedlist" --booktype "Default" --buildtarget "/opt/bitnami/apps/magento/htdocs/media/downloadable/tmp/build/8/Russian_intervention_in_Syria.mobi" --jobprofile "default.jobprofile" --booktitle "Russian intervention in Syria" --truncate_seed "yes" --ebook_format "mobi" --sample_tweets "no" --wikilang "en" --coverfont "Adler" --covercolor "DodgerBlue1" --customername "Fred Zimmerman" --yourname ""
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
revision number is
sfb_log is
completed reading config file and beginning logging at 06/12/16 18:43:09
PERL_SIGNALS is unsafe
debug: booktitle is Russian intervention in Syria
creating uuid
uuid is
DodgerBlue1
Adler
en
debug: exiting for test
Exit status returned: 0
Target File is missing: /opt/bitnami/apps/magento/htdocs/media/downloadable/tmp/build/8/Russian_intervention_in_Syria.mobi
Notification Information
This fixed my problem:
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
echo $DIR
cd $DIR
. $DIR/../../conf/config.txt
cd $scriptpath
With the program aware of its own location, it is able to find the right configuration values.