i have a case script as follow :
for i in "$#"; do
arg=( $# )
case $i in
--string)
for ((i=0; i<${#arg[#]}; i++)) ; do
if [ "${arg[$i]}" == "--string" ] ; then
((i++))
STRING=${arg[$i]}
fi
done
;;
*)
print_help
exit
;;
esac
done
when i run ./test --some_command --string pattern ; it prints the help option.
When i run ./test --some_command --string pattern without the *) option in the string, it works.
Can you please tell me how to fix this.
Another example :
#!/bin/bash
test(){
echo i am testing this now
}
print_help()
{
echo help
}
for i in "$#"; do
arg=( $# )
case $i in
--string)
for ((i=0; i<${#arg[#]}; i++)) ; do
if [ "${arg[$i]}" == "--string" ] ; then
((i++))
STRING=${arg[$i]}
fi
done
echo $STRING
;;
--test)
test
;;
*)
print_help
exit
;;
esac
done
when i run ./test --string pattern --test. it prints
pattern
help
When the for loop gets to "pattern", it is not covered by a case branch so it hits the default branch and prints the help. You have to iterate over the arguments in a smarter way. Replace for for loop with
while (( $# > 0 )); do
arg=$1
shift
case $arg in
--string) STRING=$1; shift; echo "$STRING" ;;
--some-command) : ;;
--) break ;;
*) print_help; exit ;;
esac
done
Related
I am new with bash and after reading and trying a lot about how to parse arguments I cannot what I really want to do I want to parse optional and not optional arguments. More specifically I want to parse 3 arguments, first (a fastaq file) second (a second optional fastaq file) a third argument that will be a directory.
my_script.sh -f1 file1.fasta --f2 file2.fasta -d/home/folder1/folder2
or
my_script.sh -f1 file1.fasta -d /home/folder1/folder2
I have tried to do this in many ways but I dont know how to let the program identifies when there are two fasta files and a directory and, when there is only one fasta file and a directory.
With this arguments I want to save them in variables because they will be used later by third parties.
I have tried this:
for i in "$#"; do
case $i in
-f1=|-fasta1=)
FASTA1="${i#=}"
shift # past argument=value
;;
-d) DIRECTORY=$2
shift 2
;;
-d=|-directory=) DIRECTORY="${i#=}"
shift # past argument=value
;;
--f2=|-fasta2=) FASTA2="${i#*=}"
shift # past argument=value
;;
*)
;;
esac
done
But I just got this
scripts_my_first_NGS]$ ./run.sh -f1 fasta.fasta -d /home/folder1
FASTA1 =
DIRECTORY =
FASTA2 =
Never parse command line options on your own!
Instead either use the Bash function getopts, if you do not need GNU style long options or use use the GNU program getopt otherwise.
The following examples uses an array for FASTA. FASTA1 is ${FASTA[0]} and FASTA2 is ${FASTA[1]}. In case of getopts this makes it possible to use just one option character (-f) multiple times.
Using getopts with only one-character options:
#! /bin/bash
FASTA=()
DIRECTORY=
while getopts 'f:d:' option; do
case "$option" in
f)
FASTA+=("$OPTARG")
;;
d)
DIRECTORY="$OPTARG"
;;
*)
printf 'ERROR: Invalid argument\n' >&2
exit 1
;;
esac
done
shift $((OPTIND-1))
if [[ -z ${FASTA[0]} ]]; then
printf 'ERROR: FASTA1 missing\n' >&2
exit 1
fi
if [[ -z $DIRECTORY ]]; then
printf 'ERROR: DIRECTORY missing\n' >&2
exit 1
fi
printf 'FASTA1 = %s\n' "${FASTA[0]}"
printf 'FASTA2 = %s\n' "${FASTA[1]}"
printf 'DIRECTORY = %s\n' "$DIRECTORY"
Usage:
run -f file1.fasta -f file2.fasta -d /home/folder1/folder2
Using getopt with one-character and GNU style long options mixed:
#! /bin/bash
FASTA=()
DIRECTORY=
options=$(getopt -o d: -l f1: -l f2: -- "$#") || {
printf 'ERROR: Invalid argument\n' >&2
exit 1
}
eval set -- "$options"
while true; do
case "$1" in
--f1)
FASTA[0]="$2"
shift 2;;
--f2)
FASTA[1]="$2"
shift 2;;
-d)
DIRECTORY="$2"
shift 2;;
--)
shift
break;;
*)
break;;
esac
done
if [[ -z ${FASTA[0]} ]]; then
printf 'ERROR: FASTA1 missing\n' >&2
exit 1
fi
if [[ -z $DIRECTORY ]]; then
printf 'ERROR: DIRECTORY missing\n' >&2
exit 1
fi
printf 'FASTA1 = %s\n' "${FASTA[0]}"
printf 'FASTA2 = %s\n' "${FASTA[1]}"
printf 'DIRECTORY = %s\n' "$DIRECTORY"
Usage:
run --f1 file1.fasta --f2 file2.fasta -d /home/folder1/folder2
Basically you need to add a separate parser for versions of the options where they aren't used with the equal sign.
Also your shift commands are useless since you're processing a for loop. So convert it to to a while [[ $# -gt 0 ]]; do loop instead.
I also added a few modifications which I suggest be added.
while [[ $# -gt 0 ]]; do
case $1 in
-f1|-fasta1)
FASTA1=$2
shift
;;
-f1=*|-fasta1=*)
FASTA1=${1#*=}
;;
-d|-directory)
DIRECTORY=$2
shift
;;
-d=*|-directory=*)
DIRECTORY=${1#*=}
;;
-f2|fasta2)
FASTA2=$2
shift
;;
-f2=*|-fasta2=*)
FASTA2=${1#*=}
;;
-*)
echo "Invalid option: $1" >&2
exit 1
;;
--)
# Do FILES+=("${#:2}") maybe
break
;;
*)
# TODO
# Do FILES+=("$1") maybe
;;
esac
shift
done
The "parser" for the with-equal and non-with-equal versions of the options can also be unified by
using a helper function:
function get_opt_arg {
if [[ $1 == *=* ]]; then
__=${1#*=}
return 1
elif [[ ${2+.} ]]; then
__=$2
return 0 # Tells that shift is needed
else
echo "No argument provided to option '$1'." >&2
exit 1
fi
}
while [[ $# -gt 0 ]]; do
case $1 in
-d|-directory|-d=*|-directory=*)
get_opt_arg "$#" && shift
DIRECTORY=$__
;;
-f1|-fasta1|-f1=*|-fasta1=*)
get_opt_arg "$#" && shift
FASTA1=$__
;;
-f2|fasta2|-f2=*|-fasta2=*)
get_opt_arg "$#" && shift
FASTA2=$__
;;
-*)
echo "Invalid option: $1" >&2
exit 1
;;
--)
# Do FILES+=("${#:2}") maybe
break
;;
*)
# TODO
# Do FILES+=("$1") maybe
;;
esac
shift
done
Update
I found a complete solution to command-line parsing without relying on getopt[s] and it does it even more consistentlty: https://konsolebox.io/blog/2022/05/14/general-command-line-parsing-solution-without-using-getopt-s.html
I have the following script.
I would like to modify it so that if I were to call temp.sh with both the options, I would have to space them. Ie: A call to the script like temp.sh -fc30 should be invalid, rather it should be temp.sh -f -c 30
ARGS=$(getopt -o c:f -l "charlie:fox" -n "temp.sh" -- "$#");
#bad args
if [ $? -ne 0 ];
then
exit 1
fi
eval set --"$ARGS";
while true; do
case "$1" in
-c|--charlie)
shift;
if [ -n "$1" ]; then
echo "-c =: $1";
shift;
fi
;;
-f|--fox)
shift;
echo "fox used";
;;
--)
shift;
break;
;;
esac
done
Just don't use getopt.
#!/bin/bash
# parse options
while [[ $# -gt 0 ]]; do
case $1 in
-c|--charlie)
echo "$1 = $2"
shift
;;
-f|--fox)
echo "fox used"
;;
--)
shift
break
esac
shift
done
# do script
I have a bash script where I need to have some parameters. Usage should be only between:
./script.sh --scan [scan type] [keyword]
or
./script.sh --help
In example it should be something like this:
$ ./script.sh
[Usage]
$ ./script.sh --scan
Specify scan type
$ ./script.sh --help --scan
[Usage]
$ ./script.sh --scan short
Specify keyword to search
$ ./script.sh --scan short keyword
[Starts short scanning for "keyword" - go to function where my script is, blah, blah]
$ ./script.sh keyword --scan short
[As above]
$ ./script.sh keyword
[Usage]
How I can achieve this?
This is just off the top of my head:
if [ "$1" = "--scan" ] ; then
if [ "$#" -ge 2 ] ; then
_SCANTYPE=$2
if [ "$#" -ge 3 ] ; then
_KEYWORD=$3
else
echo -n "Specify keyword to search: "
read _KEYWORD
fi
else
echo -n "Specify scan type: "
read _SCANTYPE
echo -n "Specify keyword to search: "
read _KEYWORD
fi
# Actual scan code
else
# Display usage info
fi
This is pretty basic, of course, but hopefully it will get you started.
Almost any Configure script to build a piece of software from source will have what you want. From perl's:
: option parsing
while test $# -gt 0; do
case "$1" in
-d) shift; fastread=yes;;
-e) shift; alldone=cont;;
-f)
shift
cd ..
if test -r "$1"; then
config_sh="$1"
else
echo "$me: cannot read config file $1." >&2
error=true
fi
cd UU
shift;;
--help|\
-h) shift; error=true;;
-r) shift; reuseval=true;;
-s) shift; silent=true; realsilent=true;;
-E) shift; alldone=exit;;
-K) shift; knowitall=true;;
-O) shift; override=true;;
-S) shift; silent=true; extractsh=true;;
-D)
shift
case "$1" in
*=)
echo "$me: use '-U symbol=', not '-D symbol='." >&2
echo "$me: ignoring -D $1" >&2
;;
*=*) echo "$1" | \
sed -e "s/'/'\"'\"'/g" -e "s/=\(.*\)/='\1'/" >> optdef.sh;;
*) echo "$1='define'" >> optdef.sh;;
esac
shift
;;
-U)
shift
case "$1" in
*=) echo "$1" >> optdef.sh;;
*=*)
echo "$me: use '-D symbol=val', not '-U symbol=val'." >&2
echo "$me: ignoring -U $1" >&2
;;
*) echo "$1='undef'" >> optdef.sh;;
esac
shift
;;
-A)
shift
xxx=''
yyy="$1"
zzz=''
uuu=undef
case "$yyy" in
*=*) zzz=`echo "$yyy"|sed 's!=.*!!'`
case "$zzz" in
*:*) zzz='' ;;
*) xxx=append
zzz=" "`echo "$yyy"|sed 's!^[^=]*=!!'`
yyy=`echo "$yyy"|sed 's!=.*!!'` ;;
esac
;;
esac
case "$xxx" in
'') case "$yyy" in
*:*) xxx=`echo "$yyy"|sed 's!:.*!!'`
yyy=`echo "$yyy"|sed 's!^[^:]*:!!'`
zzz=`echo "$yyy"|sed 's!^[^=]*=!!'`
yyy=`echo "$yyy"|sed 's!=.*!!'` ;;
*) xxx=`echo "$yyy"|sed 's!:.*!!'`
yyy=`echo "$yyy"|sed 's!^[^:]*:!!'` ;;
esac
;;
esac
case "$xxx" in
append)
echo "$yyy=\"\${$yyy}$zzz\"" >> posthint.sh ;;
clear)
echo "$yyy=''" >> posthint.sh ;;
define)
case "$zzz" in
'') zzz=define ;;
esac
echo "$yyy='$zzz'" >> posthint.sh ;;
eval)
echo "eval \"$yyy=$zzz\"" >> posthint.sh ;;
prepend)
echo "$yyy=\"$zzz\${$yyy}\"" >> posthint.sh ;;
undef)
case "$zzz" in
'') zzz="$uuu" ;;
esac
echo "$yyy=$zzz" >> posthint.sh ;;
*) echo "$me: unknown -A command '$xxx', ignoring -A $1" >&2 ;;
esac
shift
;;
-V) echo "$me generated by metaconfig 3.5 PL0." >&2
exit 0;;
--) break;;
-*) echo "$me: unknown option $1" >&2; shift; error=true;;
*) break;;
esac
done
For your two examples
./script.sh --scan [scan type] [keyword]
or
./script.sh --help
Try this
#!/bin/bash
usageMsg="usage:ScriptName --scan [scan type] [keyword] OR --help"
case $# in
[123] ) : nothing_may_be_OK ;;
0 ) # no args, display usageMsg
echo "$usageMsg" >&2 ; exit 1 ;;
esac
case "$1" in
--[Hh][Ee][Ll][Pp] ) echo "$usageMsg" >&2 ; exit 1 ;;
--[Ss][Cc][Aa][Nn] )
shift
case $# in
0) # using defaults
scanType="PrimaryScan" # change to your default scanType
keyword="PrimaryKeyWord" # again, change to your default value
;;
1)
echo "$usageMsg" >&2
echo "must provide both [scan type] and [keyword] arguments. Cant continue" >&2
exit 1
;;
2)
scanType="$1"
keyword="$2"
;;
esac # case $#
;;
esac # case $1
#restof your code goes here
echo "running scanner with scanType=$scanType and keyWord=$keyword"
echo ". . . . ."
exit 0
I'm guessing that your usage pattern means --scan OR advanced scan (with details), i.e. --scan [scan type] [keyword]
If you really intend for user to optionally add [scan type] AND/OR [keyword] then you'll need some further use of the techniques in the code above.
IHTH
I finally managed that! The basic version is:
#!/bin/bash
function usage
{
echo "Usage: usage"
exit
}
while [ $# -gt 0 ]; do
case $1 in
-h|--help)
usage
;;
-s|--scan)
scantype="$2"
shift
;;
-*)
usage
;;
*)
keyword="$1"
;;
esac
shift
done
if [ -z "$scantype" ]; then
echo "no scantype"
exit
fi
if [[ "$scantype" != "full" && "$scantype" != "long" && "$scantype" != "extended" && "$scantype" != "short" ]]; then
echo "invalid scantype"
exit
fi
if [ -z "$keyword" ]; then
echo "nokeyword"
exit
fi
echo "scantype: $scantype"
echo "keyword: $keyword"
Anyway thanks for your help :)
I'm trying to write a script that can use both ${1} and getopts options simultaneously. I would like it to work using the usage line:
./test_script test -a
to print:
test
-a was triggered!
I've tried
echo ${1};
while getopts "c:a" opt; do
case $opt in
a)
echo "-a was triggered!" >&2
;;
\?)
echo "Invalid option: -$OPTARG" >&2
;;
esac
done
Which is not able to give me access to both ${1} and detect that the -a option was used simultaneously. Is there a way to use both of these? I'd like to avoid turning the test string into another getopts option.
You could use the shift when getopts exit.
For example:
while [ $# -gt 0 ] ; do
while getopts "c:a" opt ; do
case $opt in
# YOUR OPTIONS
esac
done
OTHER_VALUE=$1
shift
done
PS: usually I don't use getopts, but I prefer to parse the args by myself as following:
while [ $# -gt 0 ] ; do
case "$1" in
'-a' | '--along' )
echo '-a was triggered' ;;
'-b' | '--blong' )
echo '-b was trigger with arg ' $2 ;
shift ;; # One extra shift for the argumnent $2
* )
echo 'Unknown value (maybe test)' ;;
esac
shift
done
FILE_LIST=$1
MOVE=0
while getopts "m" OPT; do
case $OPT in
m) MOVE=1 ;;
M) MOVE=1 ;;
*) echo "Invalid parameter." >&2; exit 1 ;;
esac
done
echo $MOVE
echo $FILE_LIST
I will pass optional argument ( -m/-M) and file list .
test.sh -m a.txt
its display 1 -m , but i am looking for 1 a.txt
Supost if test.sh a.xt
it should be diplsay 0 and a.txt
You need to shift the arguments.
MOVE=0
while getopts "mM" OPT; do
case $OPT in
M|m) MOVE=1
shift;;
*) echo "Invalid parameter." >&2; exit 1 ;;
esac
done
echo $MOVE
FILE_LIST=$1
echo $FILE_LIST
You can also combine m and M into one case.
If I understand right, you want the syntax for running the script to be something like:
./scriptname [-mM] firstfile [secondfile ...]
If this is correct, none of the other answers quite work; here's how I'd do it:
#!/bin/bash
# Parse command options
MOVE=0
while getopts "mM" OPT; do
case "$OPT" in
m|M) MOVE=1 ;;
*) echo "Invalid option." >&2; exit 1 ;;
esac
done
shift $(( OPTIND-1 )) # Remove options from the argument list
# Parse command arguments
if [[ $# -eq 0 ]]; then
echo "No files specified." >&2
exit 1
fi
FILE_LIST=( "$#" ) # Use an array in case of spaces in filenames
# Some examples of things to do with the results:
# Work with the specified files individually:
for FILE in "${FILE_LIST[#]}"; do
chmod g+w "$FILE"
done
# Work with the specified files as a group:
if (( MOVE == 1 )); then
mv "${FILE_LIST[#]}" "$DEST_DIR"
else
cp "${FILE_LIST[#]}" "$DEST_DIR"
fi
I do not exactly know what you want but Here are some code examples:
First example assumes that the filelist is given always after the -m option
while getopts "m:" OPT
do
case $OPT in
m)
echo "option m"
FILE_LIST = $OPTARG
;;
*)
echo "error"
;;
esac
done
echo $FILE_LIST
Or a different approach with a filelist not related to the -m option
while getopts "m:" OPT
do
case $OPT in
m)
echo "option m"
MOVE = 1
;;
*)
echo "error"
;;
esac
done
shift $(($OPTIND - 1))
FILE_LIST = $1
echo $FILE_LIST
Hope this suits your needs
You have to use $OPTARG value for this. Notice m:. The colon specifies that there are arguments passed to -m
#!/bin/bash
MOVE=0
while getopts "m:M:" OPT; do
case $OPT in
m|M) MOVE=1
FILE_LIST="$FILE_LIST $OPTARG"
;;
*) echo "Invalid parameter." >&2; exit 1 ;;
esac
done
shift $(( OPTIND-1 ))
[[ $MOVE != 1 ]] && FILE_LIST=$1
echo $MOVE
echo $FILE_LIST