Bug in parsing args with getopts in bash - bash

I was trying to modify the bd script to use getopts. I am a newbie at bash scripting
my script is
while getopts ":hvis:d:" opt
do
...
done
...
echo $somedirpath
cd "$somedirpath"
this runs fine when doing
$ ./bd -v -i -s search
or
$ ./bd -is search -d dir
But when running it like this
$ . ./bd -s search
getopts doesn't read the arguments at all. And all the variables I set in the while loop according to the arguments are all not set, so the script no longer works. Please help!

Setting OPTIND=1 before invoking getopts works fine.
The problem is that getopts relies on OPTIND to loop through the arguments provided, and after sourcing the script, it will be set to some value greater than 1 by getopts according to how many arguments you pass. This value gets carried over even after the script ends(because its being sourced). So the next time its sourced, getopts will pick up from that OPTIND, rather than starting from 1!
This might cause strange behaviour with other scripts, and I don't know how safe this is. But it works!
For a better workaround, I think what #tripleee suggests looks safe and robust.

When you source a script, the arguments parsed by getopts are those of the current shell, not the parameters on the source command line.
The common workaround is to have your script merely print the path, and invoke it like cd "$(bd)" instead (perhaps indirectly through a function or alias).

Setting OPTIND=1 may not work reliably on zsh. Try to use something different than getopts:
while [ "$#" -gt 0 ]
do
case "$1" in
-h|--help)
help
return 0
;;
-o|--option)
option
return 0
;;
-*)
echo "Invalid option '$1'. Use -h|--help to see the valid options" >&2
return 1
;;
*)
echo "Invalid option '$1'. Use -h|--help to see the valid options" >&2
return 1
;;
esac
shift
done

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

Need help understanding a strange bashrc expression

The first thing in my bashrc file is this expression:
# If not running interactively, don't do anything
case $- in
*i*) ;;
*) return;;
esac
Can somebody explain what this means?
all these symbols make it really hard to google, and there is no Haskell "hoogle" equivalent for bash so I can search for symbol expressions.
The intended behavior seems to be similar to this.
nonsourced=0;
# if sourced,
if [[ "$0" == "$BASH_SOURCE" ]]; then
nonsourced=1;
else
nonsourced=0;
fi
echo "nonsourced? $nonsourced";
case $- in
*i*)
# this case is entered if "$-" contains "i".
if [[ "$nonsourced" -eq "0" ]]; then
echo "1. " "$-";
fi
;; # leave case?
*) # this case is entered in all other cases.
if [[ "$nonsourced" -eq "0" ]]; then
echo "2. " "$-";
return
else
# cannot return from nonsourced, use exit.
echo "avoided return from nonsourced #2";
exit 0;
fi
;; # leave case?
esac
echo "3";
Can somebody explain what this means?
$- The list of options set in the shell at the point of evaluation.
When a shell (bash) starts it accepts some options:
LESS=+'/^ *OPTIONS' man bash
All of the single-character shell options documented in the description of the set builtin command can be used as options when the shell is invoked. In addition, bash interprets the following options when it is invoked:
One of such options is -i. So calling bash as bash -i … should [a] trigger that option inside[a] the shell.
[a] I say should as some other conditions are also required to have an effective interactive shell. Also, an interactive shell may be started by simply writing bash in a terminal (no -i option used)
[b] The way to print some options that have been set is with echo $-
*i*) ;; tests if the string from $- contains an i, if so, do nothing.
*) return;; On any other value of $- return (go out the script[c] ).
[c] Please read this answer for return vs. exit.
In overall, it does what the comment says:
# If not running interactively, don't do anything
Or with a clearer wording:
# If running interactively, exit[d].
[d] It may be more technically correct to use the word return instead of exit, but the idea is cleaner, I believe.
Note that there is a quite similar construct with $PS1 (used in /etc/bash.bashrc and repeated in ~/.bashrc in debian based systems, for example):
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
As for the problem of finding symbols:
> all these symbols make it really hard to google
even if it doesn't cover so many pages, SymbolHound may be of help here.
If we try it
we find this
Which clearly explains what you are asking.
See the documentation for Bash variables:
$-: A hyphen expands to the current option flags as specified upon invocation, by the set built-in command, or those set by the shell itself (such as the -i).
The asterisks in the case patterns are wildcards, so essentially the whole case says “if there’s an i [as interactive] somewhere in the arguments, go on, otherwise return”.
$- lists the current shell options.
The two cases are whether the -i interactive flag is present anywhere in that list of options.

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

is bash getopts function destructive to the command-line options?

Can you use the bash "getopts" function twice in the same script?
I have a set of options that would mean different things depending on the value of a specific option. Since I can't guarantee that getopts will evaluate that specific option first, I would like to run getopts one time, using only that specific option, then run it a second time using the other options.
Yes, just reset OPTIND afterwards.
#!/bin/bash
set -- -1
while getopts 1 opt; do
case "${opt}" in
1) echo "Worked!";;
*) exit 1;
esac
done
OPTIND=1
set -- -2
while getopts 2 opt; do
case "${opt}" in
2) echo "Worked!";;
*) exit 1;
esac
done
getopts does not modify the original arguments, as opposed to the older getopt standalone executable. You can use the bash built-in getopts over and over without modifying your original input.
See the bash man page for more info.
HTH.
cheers,
Rob

Resources