How to pass a long option to a bash script? - bash

./script.sh -abc hello
How can I write my script to use '-abc' as the option and 'hello' as the value to that option?
I should be able to pass this value to all the functions in this script. Lets say I have 2 functions: X and Y.

Use this in your script:
[[ $1 == -abc ]] && value="$2" || echo invalid option
If you don't want to print any messages on wrong option or no option, then omit the || echo ... part, value will be empty.
If you want to make the second argument a must, then:
[[ $1 == -abc ]] && [[ $2 != "" ]] && value="$2" || echo invalid option
Using if else loop will give you complete control over this:
if [[ $1 == -abc ]]; then
#if first option is valid then do something here
if [[ $2 != "" ]]; then
value="$2"
else
#if second option is not given then do something here
echo invalid option
fi
else
echo invalid option
#if first option is invalid then do something here
fi
If you want to make the first argument a must too, then change the first if statement line to
if [[ $1 == -abc && $1 != "" ]]; then
If you want to pass as many arguments as you wish and process them,
then use something like this:
#!/bin/bash
opts=( "$#" )
#if no argument is passed this for loop will be skipped
for ((i=0;i<$#;i++));do
case "${opts[$i]}" in
-abc)
# "${opts[$((i+1))]}" is the immediately follwing option
[[ "${opts[$((i+1))]}" != "" ]] &&
value="${opts[$((i+1))]}"
echo "$value"
((i++))
#skips the nex adjacent argument as it is already taken
;;
-h)
#dummy help option
echo "Options are [-abc value], -h"
;;
*)
#other unknown options
echo invalid option
break
;;
esac
done
This is an example of handling multiple arguments with only two options available -abc value and -h

bash doesn't have a built in command for processing long arguments. In order to parse long options in a shell script, you'll need to iterate over the arguments list yourself.
Here's one approach:
#!/bin/sh
is_option_arg () {
case $1 in
-*)
return 1
;;
*)
return 0
;;
esac
}
usage () {
echo "$(basename "$0") -abc ARG -def ARG -verbose"
}
OPT_ABC=
OPT_DEF=
OPT_VERBOSE=false
while [ "$#" -gt 0 ]; do
case $1 in
-abc)
shift
{ [ "$#" -ne 0 ] && is_option_arg "$1"; } || { usage >&2; exit 1; }
OPT_ABC=$1
;;
-def)
shift
{ [ "$#" -ne 0 ] && is_option_arg "$1"; } || { usage >&2; exit 1; }
OPT_DEF=$1
;;
-verbose)
OPT_VERBOSE=true
;;
*)
break
;;
esac
shift
done
echo "OPT_ABC=$OPT_ABC"
echo "OPT_DEF=$OPT_DEF"
echo "OPT_VERBOSE=$OPT_VERBOSE"
if [ "$#" -gt 0 ]; then
echo "Remaining args:"
for arg in "$#"; do
echo "$arg"
done
fi

You pretty much have to implement it yourself manually. Here's one way:
abc=
while [[ "$1" == -* ]]; do
opt=$1
shift
case "$opt" in
-abc)
if (( ! $# )); then
echo >&2 "$0: option $opt requires an argument."
exit 1
fi
abc="$1"
shift
;;
*)
echo >&2 "$0: unrecognized option $opt."
exit 2
;;
esac
done
echo "abc is '$abc', remaining args: $*"
Some sample runs of the above:
(0)$ ./script.sh
abc is '', remaining args:
(0)$ ./script.sh hello
abc is '', remaining args: hello
(0)$ ./script.sh -abc hello
abc is 'hello', remaining args:
(0)$ ./script.sh -abc hello there
abc is 'hello', remaining args: there
(0)$ ./script.sh -abc
./script.sh: option -abc requires an argument.
(1)$ ./script.sh -bcd
./script.sh: unrecognized option -bcd.
(2)$

Related

bash - passing optional arguments to script - parametername + string_value

i use a script that accepts parameter. parameters are optional and may occur in any order.
#!/bin/bash
# script name: test.sh
for var in "$#"
do
if [ ! -z "$var" ] && ([ $var = "--example" ] || [ $var = "-e" ]); then
echo "example"
elif [ ! -z "$var" ] && ([ $var = "--project" ] || [ $var = "-p" ]); then
echo "project with string xxxxxxx"
fi
done
in this simple example, you could call it like follows (some examples):
# this will echo example
./test.sh --example
# this will echo project with string xxxxxxx
./test.sh --project
# this will echo both example and project with string xxxxxxx
./test.sh --example --project
NOW, what i want to achieve is that i can do something like this (warning, this is pseuco code):
#!/bin/bash
# script name: test.sh
for var in "$#"
do
if [ ! -z "$var" ] && ([ $var = "--example" ] || [ $var = "-e" ]); then
echo "example"
elif [ ! -z "$var" ] && ([ $var = "--project" ] || [ $var = "-p" ]); then
echo "project with string $VAR_VALUE"
fi
done
# this will echo example
./test.sh --example
# this will echo project with string myproject1
./test.sh --project="myproject1"
# this will echo both example and project with string myproject2
./test.sh --example --project="myproject2"
can someone help me rewrite it so this will work somehow?
Use getopt. It handles short and long options, allows for both --long value and --long=value, decomposes -abc into -a -b -c, understands -- to end option parsing, and more.
#!/bin/bash
args=$(getopt -o ep: -l example,project: -n "$0" -- "$#") || exit
eval set -- "$args"
while [[ $1 != '--' ]]; do
case "$1" in
-e|--example) echo "example"; shift 1;;
-p|--project) echo "project = $2"; shift 2;;
# shouldn't happen unless we're missing a case
*) echo "unhandled option: $1" >&2; exit 1;;
esac
done
shift # skip '--'
echo "remaining non-option arguments: $#"
There are two possible path toward parsing argument list
Build custom option parser
use getopt, using 'long options'
The first approach is relatively simple (at this time). Using case instead of if to handle variants:
last_arg=
for arg in "$#"
do
if [ "$last_arg" = "-p" ] ; then
VAR_VALUE=$arg ;
last_arg=
echo "project with string $VAR_VALUE"
continue
fi
case "$arg" in
-e | --example)
echo "example" ;;
-p)
last_arg=$arg ;;
--project=*)
VAR_VALUE=${arg#*=}
echo "project with string $VAR_VALUE" ;;
*) ERROR-MESSAGE ;;
esac
done
exit
The BETTER approach is to leverage existing code. In particular getopt, which can handle long options:
#! /bin/bash
if T=$(getopt -o ep: --long 'example,project:' -n ${0#*/} -- "$#") ; then
eval set -- "$T"
else
exit $?
fi
while [ "$#" -gt 0 ] ; do
case "$1" in
-e | --example)
echo "example"
;;
-p | --project)
shift
VAR_VALUE=$1
echo "project with string $VAR_VALUE"
;;
--)
break
;;
*) echo "ERROR:$1" ;;
esac
shift
done

Bash script command handler to replace long if chain

Let's say I have a bunch of if statements with the form:
if (some flag variable/argument is set) then
execute another command or bash script
This is a bit troublesome to maintain, so I was wondering if there was some other way of doing this. While this guide is for node.js, I was wondering if it is possible to achieve something similar in bash
I wonder if you're looking for something like this:
#!/bin/bash
# initialize global options
debug=false
verbose=0
main() {
local OPTIND
while getopts :hdv: opt; do
case $opt in
h) show_help; exit ;;
d) debug=true ;;
v) verbose=$OPTARG;;
:) echo "error: missing argument for -$OPTARG"; exit 1;;
?) echo "error: unknown option -$OPTARG"; exit 1 ;;
esac
done
shift $((OPTIND-1))
if [[ -z $1 ]]; then
echo "error: missing subcommand"
show_help
exit 1
fi
case $1 in
bar|baz)
# invoke the command with the arguments
"$#" ;;
*) echo "error: unknown subcommand $1"; exit 1;;
esac
}
show_help() {
echo "usage: $(basename "$0") [global opts] subcommand [local opts and args]"
echo "... more details..."
}
bar() {
echo "you called $FUNCNAME with args $*"
}
baz() {
echo "this is baz"
local OPTIND
while getopts :ab opt; do
case $opt in
a|b) echo "you selected option $opt";;
?) echo "unknown option -$OPTARG";;
esac
done
if $debug; then echo "some debug message"; fi
(( verbose > 0 )) && echo "some verbose message"
}
main "$#"
You could write a wrapper function that checks the variable and then executes the command passed in:
#!/bin/bash
run_if_set() {
local var=$1
shift
(($# == 0)) && return # nothing to run
[[ $var ]] && "$#" # execute only if var is set to a non-empty string
}
Then replace your if statements with:
run_if_set "$var" command ...
which is slightly more readable than
if [[ $var ]]; then
command ...
fi
or
[[ $var ]] && command ...

Testing truth/falseness in bash script

For some reason, I can't figure out how to test truth in bash:
#!/bin/bash
FORCE_DELETE=""
BE_VERBOSE=""
OPTIND=1
while getopts ":fv" FLAG "$#" ; do
if [[ "$FLAG" == "f" ]] ; then
FORCE_DELETE="true"
fi
if [[ "$VALUE" == "v" ]] ; then
BE_VERBOSE="true"
fi
if [[ "$FLAG" == "?" ]] ; then
echo "Usage: $0 [-fv] file ..."
exit 1
fi
done
shift `expr $OPTIND - 1`
if [[ "$FORCE_DELETE" == "true" && "BE_VERBOSE" == "true" ]] ; then
echo "FORCE_DELETE AND BE_VERBOSE $#"
elif [[ "$FORCE_DELETE" == "true" ]] ; then
echo "FORCE_DELETE $#"
elif [[ "$BE_VERBOSE" == "true" ]] ; then
echo "BE_VERBOSE $#"
else
echo "$#"
fi
exit 0
Transcript:
$ test a b
a b
$ test -f a b
FORCE_DELETE a b
$ test -v a b
a b
$ test -fv a b
FORCE_DELETE a b
Why does my bash script respond to the -f flag but not the -v flag?
Most likely a typo :
[[ "$VALUE" == "v" ]],
this should be
[[ "$FLAG" == "v" ]]
You specifically ask about testing true/false. These are built in to the language rather than using strings, and you don't need the [[ test. Here is how I would write this:
#!/bin/bash
force_delete=false # Don't use UPPERCASE
be_verbose=false # they could collide with reserved variables
# OPTIND does not need to be initialised
while getopts :fv flag
do
# appears one of your if statements is incorrect
# a case is often used with getopts
case $flag in
f) force_delete=true
;;
v) be_verbose=true
;;
\?) echo "Usage: $0 [-fv] file ..."
exit 1
;;
esac
done
shift $((OPTIND-1)) # don't create a child process for simple arithmetic
if $force_delete && $be_verbose
then
echo "force_delete AND be_verbose $#"
elif $force_delete
then
echo "force_delete $#"
elif $be_verbose
then
echo "be_verbose $#"
else
echo "$#"
fi
# Bash exits 0 by default

How to validate shell arguments?

I am trying to write a simple sh script that must be invoked with 2 arguments:
sh myscript.sh --user "some user" --fizz "buzz"
At the top of myscript.sh I have:
#!/bin/sh
# VALIDATION
# 1. Make sure there are 5 positional arguments (that $4 exists).
die () {
echo >&2 "$#"
exit 1
}
[ "$#" -eq 5 ] || die "5 arguments required, $# provided"
# 2. Make sure $1 is "-u" or "--user".
# 3. Make sure $3 is "-f" or "--fizz".
If validation fails, I'd like to print a simple usage message and then exit the script.
I think I have #1 correct (checking # of positional arguments), but have no clue how to implement #2 and #3. Ideas?
# 2. Make sure $1 is "-u" or "--user".
if ! [ "$1" = -u -o "$1" = --user ]; then
# Test failed. Send a message perhaps.
exit 1
fi
# 3. Make sure $3 is "-f" or "--fizz".
if ! [ "$3" = -f -o "$3" = --fizz ]; then
# Test failed. Send a message perhaps.
exit 1
fi
Other forms for testing a variable for two possible possible values:
[ ! "$var" = value1 -a ! "$var" = value2 ]
[ ! "$var" = value1 ] && [ ! "$var" = value2 ]
! [ "$var" = value1 && ! [ "$var" = value2 ]
For Bash and similarly syntaxed shells:
! [[ $var = value1 || $var = value2 ]]
[[ ! $var = value1 || ! $var = value2 ]]
Besides using negated conditions with if blocks, you can also have positive conditions with ||
true_condition || {
# Failed. Send a message perhaps.
exit 1
}
true_condition || exit 1
Of course && on the other hand would apply with negated conditions.
Using case statements:
case "$var" in
value1|value2)
# Valid.
;;
*)
# Failed.
exit 1
;;
esac
Manually:
if [ -z "$1" ];then
fi
if [ -z "$2" ];then
fi
if [ -z "$3" ];then
fi
...
Or check getopt
while getopts "uf" OPTION
do
case $OPTION in
u)
echo "-u"
;;
f)
echo "-f"
;;
esac
done

Getopts in bash not selecting option

I have one problem , when i select one option , for exemple ./test.sh -f it should print "mel" but it reads all code.
How does it enter the if condition and passes with other argument ?
if getopts :f:d:c:v: arg ; then
if [[ "${arg}" == d ]] ; then
d_ID=$OPTARG
eval d_SIZE=\$$OPTIND
else
echo "Option -d argument missing: needs 2 args"
echo "Please enter two args: <arg1> <arg2>"
read d_ID d_SIZE
echo "disc $d_ID $d_SIZE" >> $FILENAME
fi
if [[ "${arg}" == c ]] ; then
c_NOME="$OPTARG"
eval c_ID1=\$$OPTIND
eval c_ID2=\$$OPTINDplus1
eval c_FICHEIRO=\$$OPTINDplus2
else
echo "Option -c argument missing: needs 4 args"
echo "Please enter two args: <arg1> <arg2> <arg3> <agr4>"
read c_NOME c_ID1 c_ID2 c_FICHEIRO
echo "raidvss $c_NOME $c_ID1 $c_ID2 $c_FICHEIRO" >> $FILENAME
fi
if [[ "${arg}" == f ]] ; then
echo "mel"
fi
fi
You are using getopts parameters wrong.
if getopts :f:d:c:v: arg
means that -f will follow the value of parameter, like
-f 5
If you want just have -f (without value) you need to change it to
if getopts :fd:c:v: arg ; then
(I deleted the ':'). Also, I think you should better use while cycle and case statements.
See this example
while getopts fd:c:v: opt
do
case "$opt" in
f) echo "mel";;
d) discFunction "$OPTARG";;
c) otherFunction "$OPTARG";;
v) nop;;
\?) echo "$USAGE" >&2; exit 2;;
esac
done
shift `expr $OPTIND - 1`

Resources