shell get parse arguments [duplicate] - linux

This question already has answers here:
Using getopts to process long and short command line options
(32 answers)
Closed 8 years ago.
I want to use my shell script like this:
myscript.sh -key keyValue
How can I get the keyValue ?
I tried getopts, but it requires the key to be a single letter!

use a manual loop such as:
while :; do
case $1 in
-key)
shift
echo $1
break
;;
*)
break
esac
done

No need to use getopts:
while [ "$#" -gt 0 ]; do
case "$1" in
-key)
case "$2" in
[A-Za-z])
;;
*)
echo "Argument to $1 must be a single letter."
exit 1
;;
esac
keyValue=$2
shift
;;
*)
echo "Invalid argument: $1"
exit 1
;;
esac
shift
done
If your shell is bash it could be simplified like this:
while [[ $# -gt 0 ]]; do
case "$1" in
-key)
if [[ $2 != [A-Za-z] ]]; then
echo "Argument to $1 must be a single letter."
exit 1
fi
keyValue=$2
shift
;;
*)
echo "Invalid argument: $1"
exit 1
;;
esac
shift
done

I really think it's well worth learning getopts: you'll save lots of time in the long run.
If you use getopts then it requires short versions of switches to be a single letter, and prefixed by a single "-"; long versions can be any length, and are prefixed by "--". So you can get exactly what you want using getopts, as long as you're happy with
myscript.sh --key keyValue
This is useful behaviour for getopts to insist on, because it means you can join lots of switches together. If "-" indicates a short single letter switch, then "-key" means the same as "-k -e -y", which is a useful shorthand.

Related

why bash script default case statement always executed?

I wrote a bash script that reads script arguments and pass them to parsearg function:
#!/usr/bin/env bash
function main() {
parseargs2 "$#"
}
function parseargs2() {
MAINCOMMAND=$1
shift
while [ $# -gt 0 ]; do
case $1 in
-s|--service) SERVICE_NAME="$2" ;;
-r|--registry) REGISTRY="$2" ;;
-h|--help) HELP=true ;;
*) echo "help" && exit 1;;
esac
shift
done
echo "SERVICE_NAME: $SERVICE_NAME"
echo "REGISTRY: $REGISTRY"
echo "HELP: $HELP"
echo "cmd: $MAINCOMMAND"
}
main "$#"
now when I run my script it always executes help command and then exits, I don't know why it will be ok when I remove *) case
./example.sh build --service api --registry dockerhub
EDIT:
thanks to #chepner comment I found the problem I solved this by adding shift 2 at end of while loop
Doing your own option parsing is a little fraught, but the idea should be to do an extra shift whenever you have an argument:
while [ $# -gt 0 ]; do
case $1 in
-s|--service) SERVICE_NAME="$2" ; shift;;
-r|--registry) REGISTRY="$2" ; shift;;
-h|--help) HELP=true ;;
*) echo "help" && exit 1;;
esac
shift
done
You may or may not want to support a style where the single-letter version of an option has its argument jammed up against it, which works in many stock programs:
while [ $# -gt 0 ]; do
case $1 in
-s|--service) SERVICE_NAME="$2" ; shift;;
-s*) SERVICE_NAME=${1#-s};;
-r|--registry) REGISTRY="$2" ; shift;;
-r*) REGISTRY=${1#-r};;
-h|--help) HELP=true ;;
*) echo "help" && exit 1;;
esac
shift
done
Also, there's no reason to name your variables in all-caps. All caps is usually a sign that a variable is being exported into the environment so some other program you're going to run can see it; otherwise, just use lowercase. Additionally, you don't need quotation marks on the right-hand side of an assignment, and if you're really using bash (rather than some other POSIX type shell), you likely want to use ((...)) for numeric comparisons.
help= # don't assume variables aren't set on entry
maincommand=$1
shift
while (( $# )); do
case "$1" in
-s|--service) service_name=$2 ; shift;;
-s*) service_name=${1#-s};;
-r|--registry) registry=$2 ; shift;;
-r*) registry=${1#-r};;
-h|--help) help=true ;;
*) echo "help" && exit 1;;
esac
shift
done
for var in service_name registry help maincommand; do
printf '%s: "%s"\n' "$var" "${!var}"
done
FWIW, true is just a string to the shell with no special significance; the shell doesn't have Boolean values beyond "command succeeded/failed" (which is just zero/nonzero on the exit code) or Boolean operators other than the ones that operate on commands (e.g. command1 && run this if command1 succeeded). So when flags are stored in shell variables the Booleanness is usually represented either by empty vs. nonempty string (and true is a fine value to use for the nonempty case) or 0 vs. 1 (or 0 vs nonzero) number.

bash script options parser fails

I have a bashscript.sh that I $ chmod +x bashscript.sh and move it to $ mv bashscript.sh ~/.local/bin/ in order for it to be executable like a system command.
I'd like to be able to invoke it with
bashscript [<-w|-working|--working>[=|:]] <y|yes|n|no>
And return usage/help/error (call it whatever we want) if the call isn't respected.
To do so, I wrote this parsing part:
usage(){
echo "you're wrong."
exit 1
}
[[ $# -lt 1 ]] && usage
options=$(getopt -o y,n,h,w: -l yes,no,help,working: -- "$#")
set -- $options
while true; do
case "$1" in
-h|--help|*) usage
shift;;
y|yes)
#do something
shift;;
n|no)
#..
shift;;
-w|-working|--working)
shift;;
--) #this is just syntax
shift
break;;
esac
done
But when I test it doesn't work as intended*, would you know why/have a sample that handles my option possibilites?
*edit : I always trigger the usage display
edit 2 : removed the spaces around the "=" of options as #costaparas3 pointed out, thank you, still stuck to usage() though
Here are the issues I found:
Exit if there are no arguments
Set the options with options=$(... (no spaces)
-h|--help|*) matches everything so you have an infinite loop. You don't need to match on * as getopt will reuturn non-zero if it finds an invalid argument, and the match on -- is what usually terminates the loop.
getopt returns non-zero for invalid arguments so exit 1 then
Use -n|--no to specify short and long options
--working requires an argument but you only shift 1.
-working is not valid (with getopt). Use either -w or --working.
Here is corrected version:
#!/bin/bash
usage() {
echo "you're wrong."
exit $1
}
[ $# -lt 1 ] && usage
options=$(getopt -o y,n,h,w: -l yes,no,help,working: -- "$#")
[ $? -ne 0 ] && usage 1
# default values
yes=
working=
set -- $options
while :
do
case "$1" in
-h|--help)
usage
;;
-y|--yes)
yes=1
shift
;;
-n|--no)
yes=0 # or no=1
shift
;;
-w|--working)
working=$2
shift 2
;;
--)
break
;;
esac
done

Detect number of argument and the value passed into the bash

I want to make sure my bash script can correctly detect user's input argument. Specifically, user can only pass 1 or 2 or 3 into the script, otherwise the script will be terminated. I wrote the following code:
#!/bin/bash
for args in $#
do
echo $args
done
if [ "$#" != 1 ] && [ "$#" -ne 1 ]; then
echo "Illegal number of parameters"
exit 1
fi
This script can only capture when user does not give any input, but cannot check whether user indeed input the number 1 not other values.
By the way, I am not sure how to express "input argument can accept number 1 or 2 or 3".
$# is an integer, so you have to use integer comparison. You can for example say:
if [ "$#" -ne 1 ]; then
echo "illegal number of parameters"
exit 1
fi
To check that the parameter is either 1, 2 or 3, you can use this regular expression (see something related):
if [[ ! $1 =~ ^(1|2|3)$ ]]; then
echo "the number is not 1, 2 or 3"
exit 1
fi
To express "input argument can accept number 1 or 2 or 3" I would for example say "we can just accept the argument being either 1, 2 or 3".
First off you can detect if the string is null or empty simply by doing the following:
if [ -z "$1" ]
then
echo "Argument $1 contains nothing"
fi
That I would say is your first step, and will allow you to filter out args that have no content.
Following on from that, you'd most likely need to do some comparison work on $1, $2 & $3
I'll just check something and come back to this in a moment.
Update
Just had to go find one of my scripts and check something... :-)
One way I've handled the checking of parms in the past is something like the following
#!/bin/sh
while [ $# -gt 0 ] || [ "$#" -le 4 ]; do
case "$1" in
*[!1-9]*) echo "Text: $1";;
*) echo "Number: $1"
esac
case "$2" in
*[!1-9]*) echo "Text: $2";;
*) echo "Number: $2"
esac
case "$3" in
*[!1-9]*) echo "Text: $3";;
*) echo "Number: $3"
esac
shift
done
Basically a simple regex, if I have more than 0 parameters or less than 4 parameters then I allow it through to a case statement, which then checks the content of each parameter.
This one just has an echo in, but you could just as easy set some flags, and then decide how to continue based on those flags.
For simple range checking however, you might just want to use a one liner similar to the following:
if [[ $# -gt 0 && $# -lt 4 ]]; then echo "Correct number of parameters"; fi
Again setting a flag to use later rather than echoing the results.
I assume you mean the input can only be 1, 2 or 3, so using a case statement is the best way. $1 is the variable that stores your argument, if it is equal to 1 case will execute the code in the block corresponding to the value 1 and so on.
case "$1" in
1) ...
;;
2) ...
;;
3) ...
;;
*) echo "Invalid argument"
;;
esac

Complex expression in case in linux shell [duplicate]

This question already has answers here:
Using case for a range of numbers in Bash
(5 answers)
Closed 9 years ago.
i want to do something like ..
if mark is between 80-100 it will show A+
if mark is between 70-79 it will show A
etc
I want to do it with “case in esac” not with if-elif-fi
But how do I do complex expression in case
echo “enter mark”
read mark
case $mark in
(( mark>=80 && mark<=100 ))) echo “A+”
..
..
..
esac
i tried this. it is easy with if-elif-fi but what i should do in the 'case'
thank you.
You don't specify a shell, so let me plug zsh:
#!/bin/zsh
echo “enter mark”
read mark
case $mark in
<80-100>) echo "A+" ;;
<70-79>) echo "A" ;;
...
esac
That said, the POSIX case statement (and the case statement in most shells) is geared towards matching text patterns, not arithmetic comparison. You could use a case statement, but it can get ugly.
case $mark in
[89]?|100) echo "A+" ;;
7?) echo "A" ;;
...
esac
It's not so bad in this case, since you don't have ranges like 65-74, but an if statement would be better.
if (( mark >=80 && mark <=100 )); then
echo "A+"
elif (( mark >=70 )); then
echo "A"
elif ...; then
...
else
...
fi
Use if condition its easier
if [[ ${mark} -ge 80 && ${mark} -le 100 ]]; then
echo "A+"
fi
But if you still need case then you need to specify a pattern
case ${mark} in
8[0-9]|9[0-9]|100)
echo "A+"
esac
In a regular POSIX-ish shell:
echo "enter mark"
read mark
case "$mark" in
([89][0-9]|100) echo "A+";;
(7[0-9]) echo "Grade";;
(6[0-9]) echo "Grade";;
...
esac
The leading open parenthesis on the expressions is optional.

concatenate inputs in bash script [duplicate]

This question already has answers here:
Concatenate all arguments and wrap them with double quotes
(6 answers)
Closed 5 years ago.
I would like to concatenate all the arguments passed to my bash script except the flag.
So for example, If the script takes inputs as follows:
./myBashScript.sh -flag1 exampleString1 exampleString2
I want the result to be "exampleString1_exampleString2"
I can do this for a predefined number of inputs (i.e. 2), but how can i do it for an arbitrary number of inputs?
function concatenate_args
{
string=""
for a in "$#" # Loop over arguments
do
if [[ "${a:0:1}" != "-" ]] # Ignore flags (first character is -)
then
if [[ "$string" != "" ]]
then
string+="_" # Delimeter
fi
string+="$a"
fi
done
echo "$string"
}
# Usage:
args="$(concatenate_args "$#")"
This is an ugly but simple solution:
echo $* | sed -e "s/ /_/g;s/[^_]*_//"
You can also use formatted strings to concatenate args.
# assuming flag is first arg and optional
flag=$1
[[ $1 = ${1#-} ]] && unset $flag || shift
concat=$(printf '%s_' ${#})
echo ${concat%_} # to remove the trailing _
nJoy!
Here's a piece of code that I'm actually proud of (it is very shell-style I think)
#!/bin/sh
firsttime=yes
for i in "$#"
do
test "$firsttime" && set -- && unset firsttime
test "${i%%-*}" && set -- "$#" "$i"
done
IFS=_ ; echo "$*"
I've interpreted your question so as to remove all arguments beginning with -
If you only want to remove the beginning sequence of arguments beginnnig with -:
#!/bin/sh
while ! test "${1%%-*}"
do
shift
done
IFS=_ ; echo "$*"
If you simply want to remove the first argument:
#!/bin/sh
shift
IFS=_ ; printf %s\\n "$*"
flag="$1"
shift
oldIFS="$IFS"
IFS="_"
the_rest="$*"
IFS="$oldIFS"
In this context, "$*" is exactly what you're looking for, it seems. It is seldom the correct choice, but here's a case where it really is the correct choice.
Alternatively, simply loop and concatenate:
flag="$1"
shift
the_rest=""
pad=""
for arg in "$#"
do
the_rest="${the_rest}${pad}${arg}"
pad="_"
done
The $pad variable ensures that you don't end up with a stray underscore at the start of $the_rest.
#!/bin/bash
paramCat () {
for s in "$#"
do
case $s in
-*)
;;
*)
echo -n _${s}
;;
esac
done
}
catted="$(paramCat "$#")"
echo ${catted/_/}

Resources