Pass a list of variables to a Bash script - bash

I need to be able to read a list of variables that follow certain parameters (similar to, say, mysqldump --databases db1 db2 db3)
Basically the script should be invoked like this:
./charge.sh --notify --target aig wfc msft --amount 1bln
In the script itself I need to assign "aig wfc msft" either to a single variable or create an array out of them.
What would be a good way of doing that?

I often find the shift statement to be really useful in situations like this. In a while loop, you can test for expected options in a case statement, popping argument 0 off during every iteration with shift, until you either get to the end, or the first positional parameter.
When you get to the --target argument in the loop, you can use shift, to pop it off the argument list, then in a loop, append each argument to a list (in this case $TARGET_LIST) and shift, until you get to the end of the argument list, or the next option (when '$1' starts with '-').
NOTIFY=0
AMOUNT=''
TARGET_LIST=''
while :; do
case "$1" in
-h|--help)
echo "$HELP"
exit 0
;;
--notify)
NOTIFY=1
shift
;;
--amount)
shift; AMOUNT="$1"; shift
;;
--target)
shift
while ! echo "$1" | egrep '^-' > /dev/null 2>&1 && [ ! -z "$1" ]; do
TARGET_LIST="$TARGET_LIST $1"
shift
done
;;
-*)
# Unexpected option
echo $USAGE
exit 2
;;
*)
break
;;
esac
done

If you can invoke the script like this (note the quotes):
./charge.sh --notify --target "aig wfc msft" --amount 1bln
You can assign "aig wcf msft" to a single variable.
If you cannot change the way the script is invoked and if you can guarantee that the --target option arguments are always followed by another option or other delimiter, you could grab the arguments between them and store them in a variable.
var=$(echo $* | sed -e 's/.*--target\(.*\)--.*/\1/')

Related

Shell: two loops over coomandline parameters

In a shell-script I have a loop over the positional parameters using the shift-command. After the loop I d like to reset and start another loop over the parameters. Is it possible to go back to start?
while [ $# -gt 0 ]; do
case "$1" in
"--bla")
doing sth
shift 2
;;
*)
shift 1
;;
esac
done
You can save arguments in a temporary array. Then restore positional arguments from it.
args=("$#") # save
while .....
done
set -- "${args[#]}" # restore
Don't use shift if you need to process the arguments twice. Use a for loop, twice:
for arg in "$#"
do
…
done
If you need to process argument options, consider using the GNU version of getopt (rather than the Bash built-in getopts because that only handles short options). See Using getopts in bash shell script to get long and short command line options for many details on how to do that.

how to pass other arguments besides flags

I am trying to execute my file by passing in an absolute path as the first argument ($1). I also want to add flags from that absolute path onward, but i do not know how to tell optargs to start counting from $2 forward since if i pass in the absolute path as the $1 it seems to break the getopts loop.
I'm gussing i have to implement a shift for the first argument in the following code:
while getopts :lq flag; do
case $flag in
l) echo "executing -l flag"
;;
q) echo "executing -q flag"
;;
esac
done
I'm not sure how to approach this. Any tips are welcome, thank you.
getopts does, indeed, stop processing the arguments when it sees the first non-option argument. For what you want, you can explicitly shift the first argument if it is not an option. Something like
if [[ $1 != -* ]]; then
path=$1
shift
fi
while getopts :lq flag; do
...
done
Keep the options before file argument (i.e. absolute path).
Many standard bash commands follow the same practice.
Example :
wc -wl ~/sample.txt
ls -lR ~/sample_dir
So if you follow the above practice, your code goes like this.
This code works even if options are not provided.
In general, that is the desired behavior with options.
# Consider last argument as file path
INPUT_FILEPATH=${*: -1}
echo $INPUT_FILEPATH
# Process options
while getopts :lq flag
do
case $flag in
l) echo "executing -l flag"
;;
q) echo "executing -q flag"
;;
esac
done
Sample execution :
bash sample.sh /home/username/try.txt
/home/username/try.txt
bash sample.sh -lq /home/username/try.txt
/home/username/try.txt
executing -l flag
executing -q flag

Saving argument that comes before the flag

In bash shell scripting, I'm trying to take the argument that comes before the flag.
When the argument comes after the flag, I know that I could use getopts and have the case smth like echo "there's an -g flag! Argument: $OPTARG
However I have no clue how to take an argument that comes before the flag. Let's say I would like to process this command: ./filename 2345 -g.
And the argument is a PID that the flag is trying to take argument as.
Thanks in advance!
Assuming Best Practices
Let's say your -g stands for global, and that you support passing -g either before or after the number whose meaning it changes. A mostly-conventional parser (not compliant with baseline POSIX conventions only inasmuch as the latter require all options to come before any positional arguments) might look a bit like the following:
#!/usr/bin/env bash
args=( )
global=0
while (( $# )); do
case $1 in
-g) global=1 ;;
--) shift; args+=( "$#" ); break ;;
-*) echo "Unrecognized argument $1" >&2; exit 1 ;;
*) args+=( "$1" ) ;;
esac
shift
done
if (( global )); then
echo "Doing something with global PID ${args[0]}"
fi
That is to say: Store your positional arguments in a separate location (in this case, the args array), and refer back to them as-needed.
Real, Literal (Awful) Answer
If you really want to store your last argument in a variable and refer back to that variable when you see -g, of course, you can do that:
#!/usr/bin/env bash
last_arg=
while (( $# )); do
case $1 in
-g) global=$last_arg ;;
esac
last_args=$1
shift
done
if [[ $global ]]; then
echo "Global value is $global"
fi
...however: Don't. This violates both POSIX and GNU command-line utility conventions, and thus will be surprising to any of your users who are long-time UNIX users.

How to read -argument from within a .sh shell script

I'm calling a bash script with the following arguments:
myscript.sh -d /tmp -e dev -id 12345 -payload /tmp/test.payload
and inside the script, would like to get the value for the -payload. I don't really care about the other arguments, but they will be present in the call.
Here's some code that almost works on retrieving the argument:
while getopts "d:e:payload:id:" arg; do
case $arg in
payload)
echo "payload"
;;
esac
done
Of course payload) in the case control structure doesn't work, so how can I grab the value for -payload and assign it to a variable?
i not sure if this is the best way to handle it... but check these marked lines in a script
in your case i'd use
while test $# -gt 0; do
case "$1" in
-payload)
shift
PAYLOAD=$1
;;
*)
# Catch other parameters here
# this part is not relevant
# to the answer but I added it
# to avoid infinite loop mentioned
shift
;;
esac
done

Parsing a flag with a list of values

I'm creating a bash script which involves parsing arguments. The usage would be:
$ ./my_script.sh -a ARG_1 -b ARG_2 [-c LIST_OF_ARGS...]
Using getopts I'm able to parse -a and -b and get their respective values ARG_1 and ARG_2. If and only if user places -c as last argument, then I'm also able to get -c and create a list with all values in LIST_OF_ARGS....
But I would not like to force user to insert -c as the last flag. For instance, it would be great if the script can be invoked this way:
$ ./my_script.sh -b ARG_2 -c V1 V2 V3 -a ARG_1
Here is my current code:
while getopts a:b:c opt
do
case $opt in
a)
A_FLAG=$OPTARG
;;
b)
B_FLAG=$OPTARG
;;
c)
# Handle values as regular expressions
args=("$#")
C_LIST=()
for (( i=$OPTIND-1 ; i <= $#-1 ; i++ ))
do
C_LIST=("${C_LIST[#]}" ${args[$i]})
done
;;
?)
usage
;;
esac
done
You need to separate your detection of the -c flag with the processing associated with it. For example, something like:
while getopts a:b:c opt
do
case $opt in
a)
A_FLAG=$OPTARG
;;
b)
B_FLAG=$OPTARG
;;
c)
C_FLAG=1
;;
?)
usage
;;
esac
done
# discard all of our options.
shift `expr $OPTIND - 1`
if [ "$C_FLAG" = 1 ]; then
# Handle values as regular expressions
args=("$#")
C_LIST=()
for (( i=0 ; i <= $#-1 ; i++ ))
do
C_LIST=("${C_LIST[#]}" ${args[$i]})
done
fi
This script doesn't collect all the non-option arguments until after processing all the command line options.
Here's a question: why have a -c option at all?
If the full usage involves a list of values, why not just have no -c option and allow the -a and -b options only while the rest are regular args as in ./myscript.sh -a ARG_1 -b ARG_2 [argument ...], where any arguments are optional (like the -c option and its arguments are in your usage example?
Then your question becomes "how do I intersperse program options and arguments", to which I would respond: "You shouldn't do this, but to achieve this anyway, parse the command line yourself; getopts won't work the way you want it to otherwise."
Of course, parsing is the hard way. Another possibility involves adding the values after -c to a list, so long as you don't encounter another option or the end of the options:
C_LIST=()
while getopts a:b:c: opt; do
#Skipping code...
c)
C_LIST+="$OPTARG"
shift $(expr $OPTIND - 1)
while [ -n "$1" ] && [ $(printf "%s" "$1" | grep -- '^[^-]') ]; do
C_LIST+="$1"
shift
done
OPTIND=1
;;
The behaviour of getopts is mimicked: even if OPTARG begins with a '-' character, it is still kept, but after OPTARG, any string starting with the '-' character may simply be an invalid option such as -n. I used printf instead of echo because some versions of echo, such as the one that bash has built-in, have a -e option that may or may not allow the loop to continue, which isn't desired. The grep expression should prevent this, but who knows if that version of echo allows for -e'hello', which would cause grep to succeed because it sees "hello"? While possibly unnecessary, why take chances?
Personally, I'd avoid this behaviour if you can, but I also don't understand why you're asking for this behaviour in the first place. If I were to recommend anything, I'd suggest the more common /path/to/script -a ARG_1 -b ARG_2 [argument ...] style above any other possible choice of implementation.
On my system, I haven a /usr/share/doc/util-linux/examples/getopt-parse.bash file. It puts the result of getopt into a variable, and set the positional parameters to that variable. Then uses a switch similar to yours, but uses shift to remove arguments when found.
You could do something similar, but for your -c option use shift until you get an option or run out of arguments.
Or it might be enough for you to use your current solution, but remember to set the OPTIND variable after the loop.

Resources