I'm reading my command line parameters using getopt, and I'm reading a configuration file using .:
test.sh:
#!/bin/bash
set -- `getopt C:a:b:c: "$#"`
C="default.cfg"
. $C
while [ $# -gt 0 ]; do
case "$1" in
-a) cfg1="$2"; shift;;
-b) cfg2="$2"; shift;;
-c) cfg3="$2"; shift;;
-C) C="$2"; #you'll see what this is for later
shift;;
--) shift;
break;;
-*) echo "invalid option";
exit 1;;
*) break;;
esac
shift
done
echo "cfg1 = $cfg1"
echo "cfg2 = $cfg2"
echo "cfg3 = $cfg3"
exit 0
default.cfg::
cfg1=hello
cfg2=there
cfg3=friend
This all works as expected:
$ ./test.sh
cfg1 = hello
cfg2 = there
cfg3 = friend
$ ./test.sh -b optional
cfg1 = hello
cfg2 = optional
cfg3 = friend
This issue is I want configurations to be prioritized in the following manner:
options given on the command line
options defined in the config file defined by the -C option
options defined in the default config file
So if I have this:
test.cfg:
cfg1=custom_file_1
cfg2=custom_file_2
I want to get this:
$ ./test.sh -b command_line -C test.cfg
cfg1 = custom_file_1
cfg2 = command_line
cfg3 = friend
I just can't figure out how to load the default config file, then search the options for -C, then load the custom config file, overwriting the default, then search the command line parameters AGAIN and overwrite the configs again. I'm pretty new to shell scripting, so forgive me if I'm missing something obvious.
You can preprocess the arguments and pull out the value you're looking for:
#!/bin/bash
args=$(getopt C:a:b:c: "$#")
eval set -- $args
conf="default.cfg"
source "$conf"
# pre-process the arguments and see if we can find -C
found=0
for opt in "$#"; do
if [[ $found -eq 1 ]] && [[ -f "$opt" ]]; then
source "$opt"
break
fi
if [[ "$opt" == "-C" ]]; then
found=1
fi
done
while [ $# -gt 0 ]; do
case "$1" in
-a) cfg1="$2"; shift;;
-b) cfg2="$2"; shift;;
-c) cfg3="$2"; shift;;
-C) shift;; #don't do anything with this
--) shift;
break;;
-*) echo "invalid option";
exit 1;;
*) break;;
esac
shift
done
echo "cfg1 = $cfg1"
echo "cfg2 = $cfg2"
echo "cfg3 = $cfg3"
exit 0
To overwrite variables, try to replace :
-C) C="$2";
with :
-C) . "$2";
And invoke it with :
./test.sh -C test.cfg -a command_line1 -b command_line2
Update :
For options in any order, you can try this :
C="default.cfg"
. $C
while getopts C:a:b:c: OPTION
do
case $OPTION in
a) cfg1_override=$OPTARG;;
b) cfg2_override=$OPTARG;;
c) cfg3_override=$OPTARG ;;
C) . $OPTARG;;
-) break;;
-*) echo "invalid option";
exit 1;;
*) break;;
esac
done
shift $(($OPTIND - 1))
cfg1="${cfg1_override-${cfg1}}"
cfg2="${cfg2_override-${cfg2}}"
cfg3="${cfg3_override-${cfg3}}"
echo "cfg1 = $cfg1"
echo "cfg2 = $cfg2"
echo "cfg3 = $cfg3"
exit 0
Based on Is it possible to specify the order getopts conditions are executed?
First source default.cfg.
Than scan your options for a -C option. Handle this one when found.
Finally use getopts and skip -C when you find it during getopts.
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 shell script that takes parameters, below is the code..
Right now it will only accept parameters if passed if called like this: script.sh --mode=load (or -m=load)
Is there a way to modify this so that it can be called with or without the "=" sign, so that I can call: script.sh --mode load (or -m load)
Ideally needs to work in pure bash as I don't have access to install additional tools, etc.
for i in "$#"
do
case $i in
-m=*|--mode=*)
MODE="${i#*=}"
if [[ $MODE =~ ^(dump|load)$ ]]; then
echo "" > /dev/null
else
bark "Invalid --mode set, set this to dump or load.";
exit 1
fi
;;
-p=*|--db-path=*)
DBPATH="${i#*=}"
;;
-d=*|--dump-dir=*)
DUMPDIR="${i#*=}"
;;
-l=*|--list-file=*)
TABLES="${i#*=}"
# check if file exists on disk
if [ -e $TABLES ]
then
echo "" >> /dev/null
else
bark "Table file not found!";
exit 1
fi
;;
-t=*|--tenant-name=*)
TENANT="${i#*=}"
# check if tenant is correct
if [[ $TENANT =~ ^($TENANT_LIST)$ ]]; then
echo "" >> /dev/null
else
bark "Tenant name does not match, aborting.";
exit 1
fi
;;
-s|--shared)
SHARED=YES
;;
*) usage # unknown option
;;
esac
done
My bash version:
bash --version
GNU bash, version 4.3.22(1)-release (powerpc-ibm-aix5.1.0.0)
Loop on $#. When $1 is "-m", do a shift. So in the next loop $1 will now be the argument to the -m option.
script.sh --mode load
# FIRST LOOP
$# is "--mode load"
$1 is "--mode"
shift
# SECOND LOOP
$# is "load"
$1 is "load"
This is also useful if you can specify many arguments instead of just one like you have right now. Error checking should be done to validate your argument values, and if a user did script.sh --mode with no other argument.
Don't reinvent the wheel.
If you're OK with just 1 character options, use the bash builtin getopts
#!/bin/bash
while getopts :m:p:d:l:t:s opt; do
case $opt in
m) mode=$OPTARG ;;
p) dbpath=$OPTARG ;;
d) dumpdir=$OPTARG ;;
l) tables=$OPTARG
# test file existence
;;
t) tenant=$OPTARG
# test tenant
;;
s) shared=YES ;;
:) echo "Missing argument for option -$OPTARG" >&2
exit 2
;;
*) echo "Invalid option -$OPTARG" >&2
exit 2
;;
esac
done
shift $((OPTIND - 1))
cat << SHOW_VARS
I have:
mode=$mode
dbpath=$dbpath
dumpdir=$dumpdir
tables=$tables
tenant=$tenant
shared=$shared
rest of args=$*
SHOW_VARS
Otherwise, you may be able to use the external getopt program to help parse your args. I don't have an AIX box to test on, so YMMV
tempargs=$(
getopt \
-o m:d:l:t:s \
--long mode:,db-path:,dump-dir:,list-file:,tenant-name:,shared \
-- "$#"
)
if [[ $? -ne 0 ]]; then echo "Error..." >&2; exit 2; fi
eval set -- "$tempargs"
while true; do
case $1 in
-m|--mode) mode=$2; shift 2;;
-p|--db-path) dbpath=$2; shift 2;;
-d|--dump-dir) dumpdir=$2; shift 2;;
-l|--list-file) tables=$2
# test file existence
shift 2
;;
-t|--tenant-name) tenant=$2
# test tenant
shift 2
;;
-s|--shared) shared=YES; shift;;
--) shift; break ;;
*) echo "Error..." >&2; exit 2 ;;
esac
done
I just wrote a script in bash, which work expect for multi long option:
#!/bin/bash
OPTS=`getopt -q -o fdhl: -l free,df,help,log: -- "$*"`
#Check if error with getopt
if [ $? != 0 ]
then
echo -e "error: parameter could not be found\n\nUsage:\n supervision [options]\n\n Try 'supervision --help'\n or 'supervision -h'\n for additional help text." ;
exit 1
fi
eval set -- "$OPTS"
while true ; do
case "$1" in
-f|--free)
free -h ;
shift;;
-d|--df)
df -h ; #Run df system command
shift;;
-l|--log)
case "$2" in
"") echo "miss file" ;
shift 2;; #No file passed as parameter
*)
df -h >> "$2" ;
shift 2;;
esac ;;
-h|--help) #Display help
shift;;
--) #End of parsed parameters list
shift ; break ;;
*)
break ;;
esac
done
I don't get why i'm supposed to, when I use more than 1 long option, for example:
sh myscript --free --df
And when I use --log:
sh myscript --log logfile
Both case exit on the if [ $? != 0 ], seems like the element which follow the 1st long option doesn't get parsed.
Ok, I figured out and it's all due to the using of "$*" instead of "$#" in the getopt call. I don't exactly why, i guessed both do the same thing, but it turns out to be the one which causes the problem.
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