How to handle shell getopts with parameter containing blank spaces - shell

I'm looking for a way to handle arguments containing blank spaces that has to be parsed
by shell getopts command.
while getopts ":a:i:o:e:v:u:" arg
do
echo "ARG is: $arg" >> /tmp/submit.log
case "$arg" in
a) arg1="$OPTARG" ;;
i) arg2="$OPTARG" ;;
o) arg3="$OPTARG" ;;
...
u) argn="$OPTARG" ;;
-) break ;;
\?) ;;
*) echo "unhandled option $arg" >> /tmp/submit.log ;;
?) echo $usage_string
exit 1 ;;
esac
done
Now if -u has argument like "STRING WITH WHITE SPACE"
than just the first part of the string is triggered and the while loop doesn't go to the end.
many thanks.

a trap for young players (ie me!)
beware a line like this:
main $#
what you really need is:
main "$#"
otherwise getopts will mince up your options into little pieces
http://www.unix.com/shell-programming-scripting/70630-getopts-list-argument.html

As Mat notes, your script fragment is already correct. If you're invoking your script from a shell, you need to quote arguments properly, e.g.
myscript -u "string with white space"
myscript -u 'string with white space'
myscript -u string\ with\ white\ space
myscript -u string' w'ith\ "whi"te" "''space
Requiring these quotes is not a defect in your script, it's the way the calling shell works. All programs, scripts or otherwise, receive arguments as a list of strings. The quotes in the calling shell are used to sort these arguments into separate “words” (list elements). All the calls above (made from a unix shell) pass a list of three strings to the script: $0 is the script name (myscript), $1 is -u and $2 is the string string with white space.

Related

How to quote bash flag arguments to pass through one getopts call and be interpreted by a second?

Script nerf calls script herd, which calls script er. nerf uses a flag on herd that explicitly takes arguments needing to be passed to er.
This was not a problem before nerf existed - when herd was just called from the command line, we could single-quote the arguments to the -p flag, and they would never be interpreted by herd's getopts, but instead they would be interpreted by er's getopts.
But now we have generated values in the flags that eventually need to go to er, so I need to expand the variable $file_contents in nerf, but not let them be interpreted by getopts until they get to er.
Any of these three scripts can be modified.
$ cat nerf
#!/bin/bash
file_contents="`cat one_liner_file`"
er_args="-jkl -m $file_contents"
./herd -p "$er_args" # <-- the problem
$ cat herd
#!/bin/bash
passthru_args=""
while getopts "p:a:b:cde" opt
do
case $opt in
p) passthru_args="$OPTARGS" ;;
...
esac
done
./er "$passthru_args"
$ cat er
#!/bin/bash
while getopts "jklm:" opt
do
case $opt in
...
esac
done
If I use single quotes on the marked line above, I get the literal string "$er_args" passed through. Using double quotes, the flags are directly interpreted by herd. Using single inside double quotes, the flags aren't interpreted by ANY getopts.
I'm thinking there's no elegant solution here, but please let me know if I'm wrong. The only solutions I can think of are crappy:
Expose all of er's flags explicitly through herd.
Remove the er call from herd and place it directly into nerf.
???
What many tools do is when passed -p "-jkl -m something", they split up the string using pseudo-shell syntax. This is a bad idea because it makes space and quote handling unpredictable.
Instead, the better way is to have a way to pass individual words to the command. This is what find -exec does -- all arguments after -exec and up until + or ; are passed literally as separate arguments.
Here's a simple example of a herd with the same semantics:
#!/bin/bash
passthru_args=()
while getopts "pa:b:cde" opt
do
case $opt in
p)
while [[ ${!OPTIND} != ';' ]]
do
passthru_args+=("${!OPTIND}")
let OPTIND++
done
let OPTIND++
;;
*) echo "herd: $opt is $OPTARG"
;;
esac
done
./er "${passthru_args[#]}"
You can now run ./herd -p -jkl -m "some stuff" \; -a foo
This will run ./er -jkl -m "some stuff" safely without any space issues (but you'll have a hard time nesting multiple calls that use ; as an argument terminator).

How can I generate a list of arguments unacceptable to getopts in Bash?

Let's say I'm running getopts in a Bash script with the option string ":a:b" and I provide the following command line parameters:
./script.sh -a foo bar -b bar zappo
The instance of "foo" is an expected argument for the option a and the option b has no expected argument. However, both instances of "bar" and the instance of "zappo" are unacceptable to getopts. After running getopts, how can I echo a variable containing a list of all of the unacceptable arguments? How could I produce the list "bar bar zappo"? Thanks!
Here's some code for your convenience:
#!/bin.bash
option_string=":a:b"
unexpected_parameters=""
OPTIND=1
while getopts "${option_string}" options; do
case ${options} in
a)
echo "-a triggered with parameter ${OPTARG}" >&2 # output to STDERR
;;
b)
echo "-b triggered" >&2 # output to STDERR
;;
\?)
echo "invalid option: -${OPTARG}" >&2 # output to STDERR
;;
esac
#<insert code magic... unexpected_parameters="${unexpected_parameters} "$(MAGIC)"">
done
echo "unexpected parameters: ${unexpected_parameters}"
getopts stops processing at the first non-option argument. That's Posix-style argument processing. In Posix-style argument processing, given the command
utility -a foo bar -b bar zappo
utility will not interpret -b as a command-line flag. If -a takes an argument, then bar will be the first positional argument, and then there will be three more positional arguments, -b, bar, and zappo.
GNU extends this syntax by permuting command-line arguments so that flag options can be anywhere in the command-line. However, if you set the environment variable POSIXLY_CORRECT, then GNU utilities will (mostly) behave like normal Posix utilities. The GNU version of the Posix C library function getopt(3) (and getopt_long(3)) use GNU syntax by default, and also react appropriate to the POSIXLY_CORRECT environment variable.
However, the bash getopts builtin is strictly Posix-style. So in your case, with getopts ":a:b", your loop will only be executed once, for flag a with argument foo. When getopts terminates, OPTIND is set the the index of the first unconsumed command-line argument, in this case 3 (bar).
If you want to use getopts with GNU-style option handling, you have to do the permuting yourself. For example, you could do something like this:
# Positional arguments are not necessarily unexpected; in fact, they are usually
# expected. So I accumulate them here in a bash array
positional=()
while (($#)); do
# If you really wanted to do GNU style processing, you'd need to special case
# '--' and "long options" starting with '--'. Here, I just do the simplest
# thing, which is to pass anything that vaguely looks like an optional argument
# (starts with a -) to getopts.
if [[ $1 = -* ]]; then
# Grab some options:
while getopts "$option_string" option; do
case $option in
a) ... ;;
# etc.
esac
done
# get rid of whichever options we've handled and reset OPTIND
shift $((OPTIND - 1))
OPTIND = 1
else
# Accumulate and ditch the positional argument:
positional+=("$1")
shift
fi
done
Alternatively, you can use the GNU implementation of getopt(1), which will do all that for you, at the cost of a slightly more annoying interface. (At least, I find it annoying, but YMMV.)

Passing command line options to invoked script in bash

Suppose I have a script a.sh to be invoked with options
a.sh -a1 a1option -a2 a2option
Suppose also I have a script b.sh, which invokes a.sh and uses its own options. So user executes the scripts as follows:
b.sh -b1 b1option -b2 b2option -a1 a1option -a2 a2option
Now I wonder how to parse the command line options in b.sh.
I do not need to parse the entire command line. I do not want b.sh to be aware of options a1 and a2. I would like to get only options b1 and b2 and pass the rest to a.sh.
How would you do it ?
As requested, this method avoids parsing the entire command line. Only the arguments up to -- are collected for b.sh. Then the arguments for b are stripped and only the remaining arguments are passed to a.sh.
b.sh is invoked with b.sh -b b1option -B b2option -- -a1 a1option -a2 a2option. In this line, the double dash -- indicates the end of options for b.sh. The following parses the options before the -- for use by b.sh, then removes the b arguments from the $# so you can pass it to a.sh without worrying about what errors a.sh might give you.
while getopts ":b:B:" opt; do
case $opt in
b) B1=${OPTARG}
;;
B) B2=${OPTARG}
;;
esac
done
## strips off the b options (which must be placed before the --)
shift $(({OPTIND}-1))
a.sh "$#"
A note: This method utilizes the bash builtin getopts. Getopts (as opposed to getopt, no s) takes only single-character options; hence, I have used b and B instead of b1 and b2.
My favorite getopts reference.
You can do something like this:
#!/bin/bash
while [[ $# -gt 0 ]]; do
case "$1" in
-b1)
B1=true
B1OPT=$2
shift
;;
-b2)
B2=true
B2OPT=$2
shift
;;
--)
shift
break
;;
*)
echo "Invalid option: $1"
exit 1 ## Could be optional.
;;
esac
shift
done
bash a2.sh "$#"
Note that you should place your variable $# inside doublequotes to prevent word splitting when expanded.
If a.sh can ignore options it doesn't know you can just call it with all the options b.sh was called:
a.sh "${#}"

How to write a command line tool using bash

I want to write a command line tool like git which will follow the POSIX standards. It will take the options like --help or -h , --version ..etc. But i am not getting how to do it. Can anybody tell me how to do this using bash scripting. Please help me. This is something very new to me.
Example : if the name of my tool is Check-code then i want to use the tool like ;
Check-code --help
or
Check-code --version
So far as I know, "long options", like --help and --version are not POSIX standard, but GNU standard. For command-line utilities the POSIX standard says:
The arguments that consist of hyphens and single letters or digits, such as 'a', are known as "options" (or, historically, "flags").
To support POSIX short options options it is worth getting to know getopts (there are tutorials on the web), but it does not support GNU long options.
For long options you have to roll your own:
filename=default
while (( $# > 0 ))
do
opt="$1"
shift
case $opt in
--help)
helpfunc
exit 0
;;
--version)
echo "$0 version $version"
exit 0
;;
--file) # Example with an operand
filename="$1"
shift
;;
--*)
echo "Invalid option: '$opt'" >&2
exit 1
;;
*)
# end of long options
break;
;;
esac
done
You can use the 'getopts' builtin, like so:
#!/bin/bash
# Parse arguments
usage() {
echo "Usage: $0 [-h] [-v] [-f FILE]"
echo " -h Help. Display this message and quit.
echo " -v Version. Print version number and quit.
echo " -f Specify configuration file FILE."
exit
}
optspec="hvf:"
while getopts "$optspec" optchar
do
case "${optchar}" in
h)
usage
;;
v)
version
;;
f)
file=${OPTARG}
;;
*)
usage
;;
esac
done
This only works for single character options, not for long options like -help or --help. In practice, I've never found that this is a significant restriction; any script which is complex enough to require long options is probably something that I would write in a different language.
There is probably a better way to do this, but here is what I find useful:
Each argument is represented by a variable in BASH. The first argument is $1. The second is $2, and so on. Match an option string with the first argument, and if it matches run some code accordingly.
Example:
#!/bin/bash
if [ $1 == "--hello" ]
then
echo "Hello"
else
echo "Goodbye"
fi
If you code in C or C++, then use the **argv variable. **argv is a double pointer that holds a list of all arguments passed to the program (with argv[0] being the program name).

Using grep/sed to parse BASH cmdline arguments

I got a script that expects two args (filename and MD5hashval). I can extract just the hex output of MD5sum using md5sum test.sh | grep -om1 '^[0-9a-f]*.' For some reason, the same cmd fails when invoked from a script. Whats the best way to check cmdline arguments passed to a Bash script? Here's what the code looks like:
#!/bin/bash
while getopts ":f:s" opt; do
case $opt in
f)
FILENAME=`echo $OPTARG | sed 's/[-a-zA-Z0-9]*=//'`
echo ${FILENAME}
;;
s)
MD5SUM=`echo $OPTARG | grep -om1 '^[0-9a-f]*'`
echo $MD5SUM
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
Since the s option requires an argument, you need to place a colon after it. It should be:
while getopts "f:s:" opt; do
...
From the getopts man page:
if a character is followed by a colon, the option is expected
to have an argument, which should be separated from it by white space.
My first action would be to place a debug line before your actual command:
echo "[$OPTARG]"
MD5SUM=`echo $OPTARG | grep -om1 '^[0-9a-f]*'`
But it actually has to do with the fact that s is not followed by a colon in your getopts options string. You should use f:s: instead:
... optstring contains the option characters to be recognized; if a character is followed by a colon, the option is expected to have an argument, which should be separated from it by white space.
And, just as an aside, I think your error lines should be -$opt rather than -$OPTARG.

Resources