How to getopts after $1 in bash [closed] - bash

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I want to run the script as ./script speed -a some_value -b some_value also ./script accuracy -a some_value -b some_value
What I tried is
while [ -n "$1" ]; do
case "$1" in
speed)
for i in "${#:2}"
do while getopts "a:b:" opt; do
case "${opt}" in
a) list=$OPTARG
echo $list
;;
b) list2=$OPTARG
echo $list2
;;
esac
done
done
echo "speed option passed"
break ;;
accuracy) echo "similar to above function"
break ;;
*) echo "Option $1 not recognized" ;; # In case you typed a different option other than a,b,c
esac
shift
done
getting output as when ran ./script speed -a some_value
this is something
speed option passed
I don't know if this is possible or not or is there any way to do something like this?

I don't think you want the outer loop (while [ -n "$1" ]; do), unless you want to be able to process multiple subcommands in a single run. That is, do you want this:
./script speed -a some_value -b some_value accuracy -a some_value -b some_value
To be roughly equivalent to this:
./script speed -a some_value -b some_value
./script accuracy -a some_value -b some_value
If not, remove that loop because you'll only be processing one subcommand per run. If you do want to process more than one subcommand per run, then you need to take some extra steps to remove or skip over the arguments relating to one subcommand before running the next one.
You do want to remove the for i in "${#:2}" loop -- that just doesn't mix with the way getopts works. What you do need to do is skip over the subcommand name before processing the options. You could either use shift to remove the subcommand name, something like this:
case "$1" in
speed)
shift # Remove the first argument ("speed")
while getopts "a:b:" opt; do
...
If you're going to allow multiple subcommands, add shift $((OPTIND-1)) sfter the getopts loop, to get it ready for the next subcommand.
Or you could modify OPTIND to tell getopts that it's already processed the first argument and it can go to work on the second:
case "$1" in
speed)
OPTIND=2 # Tell getopts to start processing at arg #2
while getopts "a:b:" opt; do
...
If you're going to handle multiple subcommands with this method... well, it's a bit more complicated and I think I'll duck the question.
Yet another option is to put the code for each subcommand in a function, and call it with all but the first argument:
speed_subcommand() {
local OPTIND
while getopts "a:b:" opt; do
...
}
case "$1" in
speed)
speed_subcommand "${#:2}" ;;
accuracy)
accuracy_subcommand "${#:2}" ;;
...
This method doesn't really mix with handling multiple subcommands per run.

Related

How do I make an option accept an arbitrary number of arguments

I am trying to write a shell script with options that take different numbers of values in each run, e.g.
Run 1:
./myscript --input <file1> <file2> --output <folder1>
Run 2:
./myscript --input <file1> <file2> <file3> --output <folder1> <folder2>
I tried using getopts as follows:
while getopts ":i:o:" opt; do
case $opt in
i)
echo "treatment files are: $OPTARG" >&2
infiles="${OPTARG}"
;;
o)
echo "control files are: $OPTARG" >&2
outdir="${OPTARG}"
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
However, the only way I found to input variable amounts of values for the "-i" option is by quotation ("file1 file2"), which prevents me/the user to use the autocomplete function of the command line to help generating the file path. Also this option does not seem allow me to use "--input" syntax to pass the options.
Anybody can help?
Thanks a lot.
You have two questions here. One of them is "how do I implement long options in my shell script?", for which there is good reading here.
The other question is, "how do I make an option accept an arbitrary number of arguments?" The answer there is, mostly, "you don't". Often the cleanest solution is to have your script accept the option multiple times, as in:
./myscript -i <file1> -i <file2> -i <file3>
This is relatively easy to handle by appending to a Bash array, as in:
infiles=()
while getopts ":i:o:" opt; do
case $opt in
i)
infiles+=("${OPTARG}")
;;
esac
done
And later on you can iterate over those values:
for inputfile in "${infiles[#]}"; do
...
done
Read about bash arrays for more information.
If you don't like that solution, you could implement your own option parsing in a manner similar to some of the answers to the question to which I linked in the first paragraph. That would allow you to get the command line semantics you want, although I would argue that behavior is at odds with the way a typical program behaves.

bash - getopts in switch case

I'm trying to use getopts inside a switch case loop.
if i use only getopts or only the switch case it's work, however when i combine those two the getopts dos not trigger.
i have search a lot but i cat fins any mention for how to combine them, and problem i missing something stupid so for give me ...
here is the code essence.
#!/bin/bash
case $1 in
ver)
echo "vesion"
exit 0
;;
op)
while getopts ":a" opt; do
case $opt in
a)
echo "-a was triggered!" >&2
;;
\?)
echo "Invalid option: -$OPTARG" >&2
;;
esac
done
;;
esac
when i do that
# bash -x test.sh op -a
i get
+ case $1 in
+ getopts :a opt
(and without debug i get nothing)
what is that that i missing to combine these two
Thanks :)
You should add a shift instruction at the beginning of your op) choice, before the call to getopts, to eat the op argument itself. Else, the first argument that getopts will analyze is op and it will silently stop (end 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

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

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