I'm trying to write a script in a way that makes it simple to add future command line args. I'm using getopts and that works well. However, I'm running into issues with the case statement.
args=`getopt lLo:t: $*`
if [ $? -ne 0 ]
then
echo "Usage: Default error message"
fi
while [ "$#" -eq 3 ] || [ "$#" -eq 5 ]
do
if [ "$1" != "-o" ] && [ "$1" != "-t" ]
then
echo "\nInvalid argument sequence."
exit 1
fi
case "$1" in
(-o)
shift
VAR1="$1"
shift
;;
(-t)
shift
VAR2="$1"
shift
;;
(*)
LAST_VAR="$1"
;;
(--) shift; break;;
esac
done
If I then echo $LAST_VAR it is null. Any ideas? Is there a better way to do this?
Some findings:
You need spaces around each [ and ] (line 2). See help [.
You don't need ( in case matchings. See help case.
You can use VAR1="$2" and shift 2 instead of two separate shifts. See help shift.
You want to put the -- case before *, and move the break to the * case.
Follow the logic backwards: LAST_VAR will be set if $1 is neither -o nor -t. Before that you exit if $1 is neither -o nor -t. So LAST_VAR can never be set.
I would suggest looking at some getopt examples for some ideas. Shameless plug for some tested code:
# Process parameters
params="$(getopt -o d:e:fshv \
-l diff:,exclude:,force,skip-existing,help,verbose \
--name "$cmdname" -- "$#")"
if [ $? -ne 0 ]
then
usage
fi
eval set -- "$params"
unset params
while true
do
case $1 in
-d|--diff)
diff_exec=(${2-})
shift 2
;;
-e|--exclude)
# Will override $default_excludes
excludes+=("${2-}")
shift 2
;;
-f|--force)
action='R'
shift
;;
-s|--skip-existing)
action='S'
shift
;;
-h|--help)
usage
exit
;;
-v|--verbose)
verbose='--verbose'
shift
;;
--)
shift
if [ -z "${1:-}" ]
then
error "Missing targets." "$help_info" $EX_USAGE
fi
if [ -z "${2:-}" ]
then
error "Missing directory." "$help_info" $EX_USAGE
fi
targets=(${#:1:$(($#-1))})
source_dir="${#:$#}"
break
;;
*)
usage
;;
esac
done
Related
I wrote a bash script that reads script arguments and pass them to parsearg function:
#!/usr/bin/env bash
function main() {
parseargs2 "$#"
}
function parseargs2() {
MAINCOMMAND=$1
shift
while [ $# -gt 0 ]; do
case $1 in
-s|--service) SERVICE_NAME="$2" ;;
-r|--registry) REGISTRY="$2" ;;
-h|--help) HELP=true ;;
*) echo "help" && exit 1;;
esac
shift
done
echo "SERVICE_NAME: $SERVICE_NAME"
echo "REGISTRY: $REGISTRY"
echo "HELP: $HELP"
echo "cmd: $MAINCOMMAND"
}
main "$#"
now when I run my script it always executes help command and then exits, I don't know why it will be ok when I remove *) case
./example.sh build --service api --registry dockerhub
EDIT:
thanks to #chepner comment I found the problem I solved this by adding shift 2 at end of while loop
Doing your own option parsing is a little fraught, but the idea should be to do an extra shift whenever you have an argument:
while [ $# -gt 0 ]; do
case $1 in
-s|--service) SERVICE_NAME="$2" ; shift;;
-r|--registry) REGISTRY="$2" ; shift;;
-h|--help) HELP=true ;;
*) echo "help" && exit 1;;
esac
shift
done
You may or may not want to support a style where the single-letter version of an option has its argument jammed up against it, which works in many stock programs:
while [ $# -gt 0 ]; do
case $1 in
-s|--service) SERVICE_NAME="$2" ; shift;;
-s*) SERVICE_NAME=${1#-s};;
-r|--registry) REGISTRY="$2" ; shift;;
-r*) REGISTRY=${1#-r};;
-h|--help) HELP=true ;;
*) echo "help" && exit 1;;
esac
shift
done
Also, there's no reason to name your variables in all-caps. All caps is usually a sign that a variable is being exported into the environment so some other program you're going to run can see it; otherwise, just use lowercase. Additionally, you don't need quotation marks on the right-hand side of an assignment, and if you're really using bash (rather than some other POSIX type shell), you likely want to use ((...)) for numeric comparisons.
help= # don't assume variables aren't set on entry
maincommand=$1
shift
while (( $# )); do
case "$1" in
-s|--service) service_name=$2 ; shift;;
-s*) service_name=${1#-s};;
-r|--registry) registry=$2 ; shift;;
-r*) registry=${1#-r};;
-h|--help) help=true ;;
*) echo "help" && exit 1;;
esac
shift
done
for var in service_name registry help maincommand; do
printf '%s: "%s"\n' "$var" "${!var}"
done
FWIW, true is just a string to the shell with no special significance; the shell doesn't have Boolean values beyond "command succeeded/failed" (which is just zero/nonzero on the exit code) or Boolean operators other than the ones that operate on commands (e.g. command1 && run this if command1 succeeded). So when flags are stored in shell variables the Booleanness is usually represented either by empty vs. nonempty string (and true is a fine value to use for the nonempty case) or 0 vs. 1 (or 0 vs nonzero) number.
I have a bashscript.sh that I $ chmod +x bashscript.sh and move it to $ mv bashscript.sh ~/.local/bin/ in order for it to be executable like a system command.
I'd like to be able to invoke it with
bashscript [<-w|-working|--working>[=|:]] <y|yes|n|no>
And return usage/help/error (call it whatever we want) if the call isn't respected.
To do so, I wrote this parsing part:
usage(){
echo "you're wrong."
exit 1
}
[[ $# -lt 1 ]] && usage
options=$(getopt -o y,n,h,w: -l yes,no,help,working: -- "$#")
set -- $options
while true; do
case "$1" in
-h|--help|*) usage
shift;;
y|yes)
#do something
shift;;
n|no)
#..
shift;;
-w|-working|--working)
shift;;
--) #this is just syntax
shift
break;;
esac
done
But when I test it doesn't work as intended*, would you know why/have a sample that handles my option possibilites?
*edit : I always trigger the usage display
edit 2 : removed the spaces around the "=" of options as #costaparas3 pointed out, thank you, still stuck to usage() though
Here are the issues I found:
Exit if there are no arguments
Set the options with options=$(... (no spaces)
-h|--help|*) matches everything so you have an infinite loop. You don't need to match on * as getopt will reuturn non-zero if it finds an invalid argument, and the match on -- is what usually terminates the loop.
getopt returns non-zero for invalid arguments so exit 1 then
Use -n|--no to specify short and long options
--working requires an argument but you only shift 1.
-working is not valid (with getopt). Use either -w or --working.
Here is corrected version:
#!/bin/bash
usage() {
echo "you're wrong."
exit $1
}
[ $# -lt 1 ] && usage
options=$(getopt -o y,n,h,w: -l yes,no,help,working: -- "$#")
[ $? -ne 0 ] && usage 1
# default values
yes=
working=
set -- $options
while :
do
case "$1" in
-h|--help)
usage
;;
-y|--yes)
yes=1
shift
;;
-n|--no)
yes=0 # or no=1
shift
;;
-w|--working)
working=$2
shift 2
;;
--)
break
;;
esac
done
I have a script that launches certain nodes based on what arguments you enter
case "$1" in
start)
if [ "$2" == "puppet" ]; then
set_puppet_variables
check_ES_reqs
start
elif [ "$2" == "puppet1" ]; then
set_puppet1_variables
check_ES_reqs
start
elif [ "$2" == "master" ]; then
set_master_variables
check_ES_reqs
start
fi
if [ "$2" == "" ]; then
set_puppet_variables
check_ES_reqs
start
set_master_variables
check_ES_reqs
start
fi
I want to be able to launch multiple specific nodes for example when I type in command service ES start puppet puppet1 it will then launch only those two nodes.
Is there a way to format the $2 in my logic to accept $3, $4 depending on how many nodes I add? as there will be more. Like making the $2 to a $2* to accept the second argument and any others so I can launch multiple specific nodes.
Please help
Thank you
Shift $1 out of the argument list, then loop over the remaining arguments.
case "$1" in
start)
shift
while [ $# -ne 0 ]
do
case "$1" in
puppet)
... ;;
puppet1)
... ;;
master)
... ;;
"")
... ;;
esac
shift
done ;;
shift off your command, work on the rest. This is more or less what I'm expecting you require:
cmd="$1"
shift; # get rid of it.
if [ $# -eq 0 ]
then
# if there are no parameters, use puppet and master
set -- puppet master
fi
case "$cmd" in
start)
for node in "$#"
do
set_variables "$node" # slight rename here, make it easier to reuse
check_ES_reqs
start
done
;;
# ...
esac
I would need my bash scrip to work either with:
No arguments
./script.sh
Path argument (classic argument, will saved as var to var=$1)
./script.sh /root/home/dir/
With switch -a (with it's own argument)
./script.sh -a picture.jpeg
Or both combined
./script.sh -a picture.jpeg /root/home/dir/
I have something like this:
while getopts ":a:" opt; do
case $opt in
a)
I_ARGUMENT=$OPTARG
echo "A ARGUMENT IS: $OPTARG"
;;
:)
echo "-a requires argument"
;;
esac
done
And then something like this for the path argument:
if [ -z "$1" ]
then
:
else
PATH="$1"
fi
Which obviously doesn't work well (at all) together. Could you help me to combine this two things? Thank you.
How about something like this
pathToHomeSlashDir=~/dir/ #default value will be home/dir/
if [ $# -gt 0 ]
then
while [ $# -gt 0 ]
do
case "$1" in
-a)
if [ $# -gt 1 ]
then
image="$2"
shift
else
echo "Please insert -a argument"
exit
fi
;;
*)
pathToHomeSlashDir="$1"
;;
esac
shift
done
fi
You'd just need to handle the variables then
I would like to make a script where you can give a couple of parameters with it:
while getopts ":a:b:c:" opt; do
case $opt in
a)
echo "-a was triggered
;;
b)
echo "-b was triggered
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
The code I have now works but the problem I have is that I want to give another function/echo if it combines.
Example:
When I do: .MyScript -ab than it should give another function that what is defined in "a" or in "b"
so a bit like:
ab) -> Script -a -b or Script -ab
echo "-ab was triggered"
What is the best solution to do this?
Any ideas, your free to post!
while getopts ":a:b:c:" opt; do
case $opt in
a)
echo "-a was triggered"
a_val=$OPTARG
;;
b)
echo "-b was triggered"
b_val=$OPTARG
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
if [[ -n $a_val ]]; then do something with "$a_val"; fi
if [[ -n $b_val ]]; then do something with "$b_val"; fi
The only situation this may cause confusion is if the user passes -a "" -- one workaround is:
a=false
b=false
while getopts ":a:b:c:" opt; do
case $opt in
a) echo "-a was triggered"; a_val=$OPTARG; a=true ;;
b) echo "-b was triggered"; b_val=$OPTARG; b=true ;;
esac
done
if $a; then do something with "$a_val"; fi
if $b; then do something with "$b_val"; fi
Here, there are no brackets: if $a; then ... because I'm invoking the command "true" or "false" and acting on that exit status.
UPDATED
You can use the getopt(1) linux utility. This is not a bash internal function, but this can handle long arguments.
Try this:
declare -A OPTARG
GetOpt() {
local prog="${BASH_SOURCE[0]}"
[ $# -eq 0 ] && echo "$prog: Bad call of GetOpts">&2 && exit 1
local longopt="$1"
shift
local tmp
tmp=$(getopt -n"$prog" -a -l "$longopt" -- '' "$#") || exit
eval "tmp=($tmp)"
local i
for((i=0;i<${#tmp[*]};++i)){
key=${tmp[i]#--}
[ -z "$key" ] && break
if [[ "${tmp[i+1]}" =~ ^-- ]]; then OPTARG[$key]=1
else OPTARG[$key]="${tmp[++i]}"
fi
}
for((j=0;++i<${#tmp[*]};++j)){ OPTFILE[j]=${tmp[i]};}
}
GetOpt "a b ab file:" "$#"
[ "${OPTARG[a]}" -a "${OPTARG[b]}" ] && OPTARG[ab]=1 && unset OPTARG[a] OPTARG[b]
[ "${OPTARG[a]}" ] && echo Do a
[ "${OPTARG[b]}" ] && echo Do b
[ "${OPTARG[ab]}" ] && echo Do ab
The defined GetOpt function will place the parsed command line argument with the checked long options to the associative array called OPTARG. If there is an error it will fail informing about the problem.
With getopt's -a option you can use -ab or --ab format. Keep in mind that if You have del defined, then the -d option (if -d is not specified) will expand to --del.
If You specify an arg with additional option it can contain spaces. E.g. if -l file: defined in getopt then it can be used as ./test --file="q w". ${OPTARG[file]} will be q w.
The OPTFILE array contains the optional arguments (args given after --).