How to read arguments from bash [duplicate] - bash

This question already has answers here:
Creating bash scripts that take arguments
(3 answers)
Closed 8 years ago.
I am curious as to how to pass in arguments via terminal to the bash script and read them and process the script functions based on the arguments.
So if I did something like:
./scriptname.sh install
#or
./scriptname.sh assets install
How would I say, ok the first argument installs something, while the second sais to do something else based on the first argument.

$0 is the name of the command
$1 first parameter
$2 second parameter
$3 third parameter etc. etc
$# total number of parameters
for args in $*
blah blah

One great way to pass arguments to a script is to use the bash builtin functionality getopts
you can use it like this:
# a script that accepts -h -a <argument> -b
while getopts "ha:b" OPTION
do
case $OPTION in
h)
# if -h, print help function and exit
helpFunction
exit 0
;;
a)
# -a requires an argument (because of ":" in the definition) so:
myScriptVariable=$OPTARG
;;
b)
# do something special
doSomeThingSpecial
;;
?)
echo "ERROR: unknonw options!! ABORT!!"
helpFunction
exit -1
;;
esac
done

You can access a particular argument with $1, $2, ... See eg What does "$1/*" mean in "for file in $1/*"
You can also use "$#" to loop on your arguments. Ex : https://github.com/gturri/dotfiles/blob/master/bootstrap.sh#L64

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.

Getopt generates a double-dash (--) even if there's none on the command line, and doesn't validate an extraneous argument

I'm learning the getopt command and using the following diagnostic script to study its workings:
$ cat test-getopt.sh
#!/bin/bash
args=`getopt ab:c $*`
set -- $args
for i
do
echo "-->$i"
done
echo $#
I cannot understand its behavour in the following cases. Could you clarify?
1st case:
$ ./test-getopt.sh -ab arg -c
-->-a
-->-b
-->arg
-->-c
-->--
5
Why does getopt add -- as $5? What does it mean here? To point out the end of options?
2nd case:
$ ./test-getopt.sh -ab arg c
-->-a
-- -b
-->arg
-->--
-->c
5
Now, getopt adds c as $5's value, after that --. It is not a option, what does c mean here?
Which kind of element is it -- option, or option's argument, or positional argument?
It's not defined in getopt's parameter specifying valid options, why doesn't the program raise an error?
I've already skimmed through the getopt man page as well as some tutorials but couldn't quite work out a clear explanation.
According to getopt manpage:
Normally, no non-option parameters output is generated until all
options and their arguments have been generated. Then '--' is
generated as a single parameter, and after it the non-option
parameters in the order they were found, each as a separate parameter.
I.e. -- by itself is generated to signify the end of options. (And after it, positional parameters are generated if there are any.)
I guess this is done for uniformity -- to use the same code logic regardless of whether the user specified -- on the command line or not.
In the 2nd case, c is a positional argument. Positional arguments are not checked by getopt in any way and are rather passed as-is. The manpage doesn't say anything about validating non-option arguments:
getopt is used to break up (parse) options in command lines for easy
parsing by shell procedures, and to check for legal options.
Finally, note that to correctly process arguments with whitespace, you need to: use $# instead of $*; quoting; eval with set; and use the enhanced mode of getopt -- as per Example of how to parse options with bash/getopt. Also should use bash -e mode to quit the program on an invalid option:
#!/bin/bash -e
args=`getopt -o ab:c -- "$#"`
eval set -- "$args"
for i
do
echo "-->$i"
done
echo $#
$ ./test-getopt.sh -b "arg ument"
-->-b
-->arg ument
-->--
3
$ ./test-getopt.sh -d ; echo $?
getopt: unknown option -- d
1
Also, a while loop with shift as per the same example could be more convenient that for as it: makes it easy to get the next argument -- to get the option's argument and check if there is an argument if it's optional; check the number of the remaining (positional) arguments when you're done with options.
I normally use constructs like this to run getopts:
# Set defaults
opt_a=0; opt_b=""; opt_c=false
# Step through options
while getopts ab:c opt; do
case "$opt" in
a) opt_a=1 ;;
b) opt_b="${OPTARG:?The -b option requires an argument.}" ;;
c) opt_c=true ;;
*) usage; exit 64 ;;
esac
done
shift $((OPTIND - 1))
Use of shift like this at the end causes your positional arguments to be shifted back such that the first argument that getopts can't process becomes $1. For example, if the above snippet was part of a script named foo, one might run:
$ foo -ab meh smoo blarg
which would set $opt_a to 1, $opt_b to "meh", $1 to "smoo" and $2 to "blarg" for the portion of the script following the snippet.

How to use getopts in bash script? [duplicate]

This question already has answers here:
Optional option argument with getopts
(15 answers)
Closed 5 years ago.
I'm trying to use getopts like this:
#!/bin/bash
while getopts "i" option
do
case "${option}"
in
i) INT=${OPTARG};;
esac
done
echo "$INT"
But it prints $INT only if i use getopts "i:". If I understand correctly, the colons in the optstring mean that values are required for the corresponding flags. But I want to make this flag optional.
Can anyone explain why the script acts like this and how can I fix it?
You cannot make it (bash getopts) optional like that. The "getopts" does not support mandatory or optional options.
You would need to code for that.
And if a ":" is specified then there needs to be an argument to that option. There is no way to around it.
The following code snippets shows how to check for mandatory arguments.
# Mandatory options
arg1=false;
..
...
case "${option}"
in
i) INT=${OPTARG}; arg1=true;
;;
esac
if ! $arg1;
then
echo -e "Mandatory arguments missing";
# assuming usage is defined
echo -e ${usage};
exit 1;
fi

Bug in parsing args with getopts in 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

Making a CLI command using an SH script [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 9 years ago.
Improve this question
I want to make a Pathogen helper script using a .sh file. I know if you make it executable it can be run as a command, but I have no idea how to do -o --options or arguments or anything like that.
Basically that's what I want answered, really all I need to know is how to do something like:
pathogen install git://...
Or something along those lines. Any help is appreciated. :)
The bash builtin getopts does not handle long arg parsing mechanism as far as I know.
getopt(1) is the tool you are looking for.
Not a program entirely, but you'll get the idea
PARSED_OPTIONS=$(getopt -n "$0" -o h123: --long "help,one,two,three:" -- "$#")
while true;
do
case "$1" in
-h|--help)
echo "usage $0 -h -1 -2 -3 or $0 --help --one --two --three"
shift;;
-1|--one)
echo "One"
shift;;
--)
shift
break;;
esac
done
Take a look at code example and explanation given here.
Passing arguments is the easiest of the two (see "What are special dollar sign shell variables?" on SO):
#!/bin/sh
echo "$#"; # total number of arguments
echo "$0"; # name of the shell script
echo "$1"; # first argument
Assuming the file is named "stuff" (sans an extension) and the result of running ./stuff hello world:
3
stuff
hello
To pass in single letter switches (w/ optional associated params), e.g. ./stuff -v -s hello you'll want to use getopts. See "How do you use getopts" on SO and this great tutorial. Here is an example:
#!/bin/sh
verbose=1
string=
while getopts ":vs:" OPT; do
case "$OPT" in
v) verbose=0;;
s) string="$OPTARG";;
esac;
done;
if verbose; then
echo "verbose is on";
fi;
echo "$string";
The line having getopts coupled with while needs further explanation:
while - start the while loop, going through everything getopts returns back after it processes
getopts :vs: OPT; - the program getopts with 2 arguments :vs: and OPT
getopts - returns something while can iterate over
:vs: - the first argument, this describes what switches getopts will look for while it parses the shell line
: - the first colon takes getopts out of debug mode, omit this to make getopts verbose
v - find the switch -v, this will not have an argument after it, just a simple switch
s: - find the option -s with an argument after it
OPT - will store the character used (the name of the switch), e.g. "v" or "s"
OPTARG - the variable to load the value into during each of while's iterations. For v, $OPTARG will not have a value, but for s it will.
The colon : tells getopts to look for an argument after the switch. The only exception is if the sequence of characters starts with : then it toggles getopts in/out of debug/verbose mode. For example:
getopts :q:r:stu:v will take getopts out of debug mode, will tell it that switches q, r, and u will expects args, while s, t, and u won't. This would be applicable for something like: stuff -q hello -r world -s -t -u 123 -v
getopts tuv will only tell getopts to search for switches t, u and v with no arguments, e.g. stuff -t -u -v, and to be verbose

Resources