Bash: Extract remaining unflagged arguments (when using getopts) - bash

I'm using the getopts command to process the flags in my bash script, using the structure found in this answer: https://stackoverflow.com/a/21128172/2230446
#!/bin/bash
t_flag='30'
print_usage() {
printf "Usage: ..."
}
while getopts 't:' flag; do
case "${flag}" in
t) t_flag="${OPTARG}" ;;
*) print_usage
exit 1 ;;
esac
done
echo "${t_flag}"
I run the script with the following command:
./test.sh -t dog cat fish
My goal is to extract cat fish to a variable so I can use it in the rest of the script, similar to how the t_flag was extracted to a variable.

Discard -t dog after parsing it, e.g:
shift $((OPTIND-1))
Then only cat and fish will be left as positional parameters. To extract them to a variable:
var=$*

Related

How to take 2 argument from getopts

I am creating a bash script that will two argument from the user from command line . But i am not sure how I can take 2 argument from the user and both arguments are required if not passed will show error and return from the script . Below is the code i am using to take argument from the user , but currently my getopts is taking only one argument.
optspec="h-:"
while getopts "$optspec" optchar; do
case "${optchar}" in
-)
case "$OPTARG" in
file)
display_usage ;;
file=*)
INPUTFILE=${OPTARG#*=};;
esac;;
h|*) display_usage;;
esac
done
How could i add an an option to take one more args from command line. Like below
script.sh --file="abc" --date="dd/mm/yyyy"
getopts does not support long arguments. It only supports single letter arguments.
You can use getopt. It is not as widely available as getopts, which is from posix and available everywhere. getopt will be for sure available on any linux and not only. On linux it's part of linux-utils, a group of most basic utilities like mount or swapon.
Typical getopt usage looks like:
if ! args=$(getopt -n "your_script_name" -oh -l file:,date: -- "$#"); then
echo "Error parsing arguments" >&2
exit 1
fi
# getopt parses `"$#"` arguments and generates a nice looking string
# getopt .... -- arg1 --file=file arg2 --date=date arg3
# would output:
# --file file --date date -- arg1 arg2 arg3
# the idea is to re-read bash arguments using `eval set`
eval set -- "$args"
while (($#)); do
case "$1" in
-h) echo "help"; exit; ;;
--file) file="$2"; shift; ;;
--date) date="$2"; shift; ;;
--) shift; break; ;;
*) echo "Internal error - programmer made an error with this while or case" >&2; exit 1; ;;
esac
shift
done
echo file="$file" date="$date"
echo Rest of arguments: "$#"

Shell script not reading getopts arguments on MacOS

I have written a script that performs tasks according to switches used on command line. Here is my script:
Expects the git branch name and two optional switches
-r for resetting database
-t for merging training data
test.sh
merge_training=false
reset_database=false
while getopts ":tr" opt; do
case ${opt} in
t ) # process option t
echo "one"
$merge_training=true
;;
r ) # process option r
echo "two"
$reset_database=true
;;
esac
done
echo $reset_database
echo $merge_training
When I run this script with command:
sh test.sh branchname -r -t
It does not print one or two and the last statements prints:
false
false
What is wrong here?
What is wrong is your assignments. Your should have got 2 error messages saying:
false=true: command not found
Hint: Never put a $ on the left-side of an assignment:
The convention is to put options first, then extra parameters, so your command-line should be:
bash test.sh -r -t branchname
Use bash not sh. sh is a POSIX shell and is roughly a subset of bash (its complicated). Don't confuse the two.
merge_training=false
reset_database=false
while getopts ":tr" opt; do
case ${opt} in
t ) # process option t
echo "one"
merge_training=true # <<<<<<<<<<<<<<<
;;
r ) # process option r
echo "two"
reset_database=true # <<<<<<<<<<<<<<<
;;
esac
done
shift $(( OPTIND-1 ))
extra="$1"
echo $reset_database
echo $merge_training
echo $extra
when you remove the branchname as a param, then it works (- but you have to also remove the $ from $merge_training=true, else you try to false=true..)
So what you could do is to save the $1 param in a variable and simply shift.
Here the code:
#!/bin/bash
merge_training=false
reset_database=false
branchname="$1"
shift
while getopts ":tr" opt; do
case $opt in
t ) # process option t
echo "one"
merge_training=true
;;
r ) # process option r
echo "two"
reset_database=true
;;
esac
done
echo $branchname
echo $reset_database
echo $merge_training
Some of the points you could enhance on your script,
Variable assignments don't take $ on the left-hand-side
The command-line arguments are seeing an extra argument branchname before processing the actual OPTSTRING, you need to exclude the first parameter before getting into the getopts() call.
Always set the shell interpreter to the one you want to run your script with. In bash do #!/usr/bin/env bash
Always quote your shell variables unless you have a good reason not to.
The updated script should look like below if you are passing arguments as bash test.sh branchname -r -t
#!/usr/bin/env bash
merge_training=false
reset_database=false
branchname="$1"
shift 1
while getopts ":tr" opt; do
case "${opt}" in
t ) # process option t
echo "one"
merge_training=true
;;
r ) # process option r
echo "two"
reset_database=true
;;
esac
done
echo "$reset_database"
echo "$merge_training"

pass verbatim arguments to getopts without $# or $* inside posix shell script

I'm trying to pass an argument as "verbatim" to my script, the problem is that the argument in inside double quotes "" and it starts with a double dash/double hyphen --.
This is an example
script.sh -f "--conf=bla"
In my script both $* and $# transform this into
-f --conf=bla
and when this thing reaches getopts there is no way to decode this information the way it should be.
And by the way this is my getopts
foo()
{
while getopts ":f:" vars
do
case ${vars} in
f ) MYVAR=${OPTARG};;
* ) Err; exit 1;;
esac
done
shift $((OPTIND-1))
}
I would like to store --conf=bla inside MYVAR, I can't find a way to do this, apparently I can't control the way the double quotes are stripped away and, in general, I can't really pass text as verbatim to my script.
How I can control this ?
$ cat t.sh
#!/bin/sh
foo()
{
# unset OPTIND
while getopts ":f:" vars
do
case ${vars} in
f) MYVAR=${OPTARG} ;;
*) echo "error"; exit 1 ;;
esac
done
shift $((OPTIND-1))
echo "\$MYVAR=[${MYVAR}]"
}
foo "$#"
.
$ ./t.sh -f "--conf=blah"
$MYVAR=[--conf=blah]
Please elaborate?

shell script arguments non positional

Is there a way to feed non positional arguments to a shell script?
Meaning explicitly specify some kind of flag?
. myscript.sh value1 value2
. myscript.sh -val1=value1 -val2=value2
You can use getopts, but I don't like it because it's complicated to use and it doesn't support long option names (not the POSIX version anyway).
I recommend against using environment variables. There's just too much risk of name collision. For example, if your script reacts differently depending on the value of the ARCH environment variable, and it executes another script that (unbeknownst to you) also reacts to the ARCH environment variable, then you probably have a hard-to-find bug that only shows up occasionally.
This is the pattern I use:
#!/bin/sh
usage() {
cat <<EOF
Usage: $0 [options] [--] [file...]
Arguments:
-h, --help
Display this usage message and exit.
-f <val>, --foo <val>, --foo=<val>
Documentation goes here.
-b <val>, --bar <val>, --bar=<val>
Documentation goes here.
--
Treat the remaining arguments as file names. Useful if the first
file name might begin with '-'.
file...
Optional list of file names. If the first file name in the list
begins with '-', it will be treated as an option unless it comes
after the '--' option.
EOF
}
# handy logging and error handling functions
log() { printf '%s\n' "$*"; }
error() { log "ERROR: $*" >&2; }
fatal() { error "$*"; exit 1; }
usage_fatal() { error "$*"; usage >&2; exit 1; }
# parse options
foo="foo default value goes here"
bar="bar default value goes here"
while [ "$#" -gt 0 ]; do
arg=$1
case $1 in
# convert "--opt=the value" to --opt "the value".
# the quotes around the equals sign is to work around a
# bug in emacs' syntax parsing
--*'='*) shift; set -- "${arg%%=*}" "${arg#*=}" "$#"; continue;;
-f|--foo) shift; foo=$1;;
-b|--bar) shift; bar=$1;;
-h|--help) usage; exit 0;;
--) shift; break;;
-*) usage_fatal "unknown option: '$1'";;
*) break;; # reached the list of file names
esac
shift || usage_fatal "option '${arg}' requires a value"
done
# arguments are now the file names
The easiest thing to do is pass them as environment variables:
$ val1=value1 val2=value2 ./myscript.sh
This doesn't work with csh variants, but you can use env if you are using such a shell.
Yes there is. The name is getopts http://www.mkssoftware.com/docs/man1/getopts.1.asp
Example:
#!/bin/bash
while getopts d:x arg
do
case "$arg" in
d) darg="$OPTARG";;
x) xflag=1;;
?) echo >&2 "Usage: $0 [-x] [-d darg] files ..."; exit 1;;
esac
done
shift $(( $OPTIND-1 ))
for file
do
echo =$file=
done
Script has arguments as follows:
- $0 - script name
- $1, $2, $3.... - received arguments
$* = all arguments,
$# = number of arguments
Reference:
http://famulatus.com/ks/os/solaris/item/203-arguments-in-sh-scripts.html

bash script parameters [duplicate]

This question already has answers here:
How do I parse command line arguments in Bash?
(40 answers)
Closed 6 years ago.
I need to write a bash script, and would like it to parse unordered parameters of the format:
scriptname --param1 <string> --param2 <string> --param3 <date>
Is there a simple way to accomplish this, or am I pretty much stuck with $1, $2, $3?
You want getopts.
while [[ $1 = -* ]]; do
arg=$1; shift # shift the found arg away.
case $arg in
--foo)
do_foo "$1"
shift # foo takes an arg, needs an extra shift
;;
--bar)
do_bar # bar takes no arg, doesn't need an extra shift
;;
esac
done
A nice example of how to implement short & long switches side by side is mcurl:
http://www.goforlinux.de/scripts/mcurl/
Bash has a getops function, as mentioned here before, that might solve your problems.
If you need anything more sophisticated, bash also supports positional parameters (ordered $1 ... $9, and then ${10} .... ${n}), you'll have to come up with your own logic to handle this input. One easy way to go is to put a switch/case inside of a for loop, iterating over the parameters. You can use either one of the two special bash vars that handle the input: $* or $#.
#!/bin/bash
# Parse the command-line arguments
while [ "$#" -gt "0" ]; do
case "$1" in
-p1|--param1)
PARAM1="$2"
shift 2
;;
-p2|--param2)
PARAM2="$2"
shift 2
;;
-p3|--param3)
PARAM3="$2"
shift 2
;;
-*|--*)
# Unknown option found
echo "Unknown option $1."
exit 1
;;
*)
CMD="$1"
break
;;
esac
done
echo "param1: $PARAM1, param2: $PARAM2, param3: $PARAM3, cmd: $CMD"
When I execute this:
./<my-script> --param2 my-param-2 --param1 myparam1 --param3 param-3 my-command
it outputs what you expect:
param1: myparam1, param2: my-param-2, param3: param-3, cmd: my-command

Resources