Bash script with named parameter containing equal sign - bash

I am trying to pass some values to my bash script using named parameters similar to the following:
./script.sh --username='myusername' --password='superS3cret!' --domainou="OU=Groups with Space,OU=subou,DC=mydomain,DC=local"
I have the following code:
#!/bin/bash
while [ "$1" != "" ]; do
PARAM=`echo $1 | awk -vFPAT='([^=]*)|("[^"]+")' -vOFS="=" '{print $1}'`
VALUE=`echo $1 | awk -vFPAT='([^=]*)|("[^"]+")' -vOFS="=" '{print $2}'`
case $PARAM in
-u | --username)
username=$VALUE
;;
-p | --password)
password=$VALUE
;;
-ou | --domainou)
domainou=$VALUE
;;
*)
echo "ERROR: unknown parameter \"$PARAM\""
exit 1
;;
esac
shift
done
echo $username
echo "$password"
echo "$domainou"
What I get when I run my script is:
myusername
superS3cret!
OU
Now the first two lines are correct but obviously I don't want OU...
I want:
OU=Groups with Space,OU=subou,DC=mydomain,DC=local
Awk seems to be matching the = inside the quote. As best as I can tell the way to solve that is using
-vFPAT='([^=]*)|("[^"]+")' -vOFS="="
But clearly that's not working so I am just wondering if any awk gurus can help me understand what's wrong with my awk statement.
Thanks
Brad

You can do it like this:
#!/bin/bash
while [ $# -gt 0 ]; do
case "$1" in
-u=* | --username=*)
username="${1#*=}"
;;
-p=* | --password=*)
password="${1#*=}"
;;
-ou=* | --domainou=*)
domainou="${1#*=}"
;;
*)
printf "Error: unknown option: $1\n"
exit 1
esac
shift
done
printf "username: $username\n"
printf "password: $password\n"
printf "domainou: $domainou\n"

For parsing command line options that include both long and short optoins, consider using GNU getopt, which has support for long options. While it is possible to build-your-own parser replacement, using the getopt provides for more robust parsing:
Abbreviation of options (e.g., accepting --user for --username).
Checking for required/optional values
Error handling
See also: Using getopts to process long and short command line options
set $(getopt --long 'username:,password:,ou:,domain:' -o 'u:p:' -- "$0" "$#")
while [ "$#" -gt 0 ] ; do
OP=$1
shift
case "$OP" in
--) PROG=$1 ; shift ; break ;;
-u | --username) username=$1 ; shift ;;
-p | --password) password=$1 ; shift ;;
--ou | --domain) domainou=$1 ; shift ;;
esac
done
# Positional arguments are set ...

Below is what ultimately worked best for me.
#dash-o definitely got me pointed in the right direction but the script you provided was printing out extraneous info:
set: usage: set [-abefhkmnptuvxBCHP] [-o option-name] [--] [arg ...]
I believe the offending line was this:
set --long 'username:,password:,ou:,domain:' -o 'u:p:' -- "$0" "$#"
Here's the code that accomplished what I needed. I can't take credit for this. I stole it from here Using getopts to process long and short command line options but I never would have found that if not for dash-o so a big thank you!
#!/bin/bash
die() { echo "$*" >&2; exit 2; } # complain to STDERR and exit with error
needs_arg() { if [ -z "$OPTARG" ]; then die "No arg for --$OPT option"; fi; }
while getopts ab:c:-: OPT; do
# support long options: https://stackoverflow.com/a/28466267/519360
if [ "$OPT" = "-" ]; then # long option: reformulate OPT and OPTARG
OPT="${OPTARG%%=*}" # extract long option name
OPTARG="${OPTARG#$OPT}" # extract long option argument (may be empty)
OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=`
fi
case "$OPT" in
u | username ) needs_arg; username="$OPTARG" ;;
p | password ) needs_arg; password="$OPTARG" ;;
o | domainou ) needs_arg; domainou="$OPTARG" ;;
??* ) die "Illegal option --$OPT" ;; # bad long option
\? ) exit 2 ;; # bad short option (error reported via getopts)
esac
done
shift $((OPTIND-1)) # remove parsed options and args from $# list
echo "$username"
echo "$password"
echo "$domainou"

Related

Print bash script argument passed into a new script generated by your current script

Title might be confusing -- I'm writing up a bash script that creates another bash script in the same relative directory, and I want to print an argument passed into the second script.
Layout:
#!/bin/bash
set -e
while [[ $# -gt 1 ]]; do
key="$1"
if [[ ! "$2" =~ ^-[^-].* ]];
case $key in
--arg1 | -t)
arg1=$2
shift #shift past argument
;;
--arg2 | -k)
arg2=$2
shift #shift past argument
;;
--arg3 | -i)
arg3=$2
break
;;
*)
;;
esac
fi
done
cat <<'EOF' > secondScript.sh
#!/bin/bash
set -e
while [[ $# -gt 1 ]]; do
key="$1"
if [[ ! "$2" =~ ^-[^-].* ]]; then # don't gobble up next key if this key doesn't have a value
case $key in
--finalArg1 | -t)
finalArg1=$2
shift #shift past argument
;;
--finalArg2 | -k)
finalArg2=$2
shift #shift past argument
;;
--finalArg3 | -i)
finalArg3=$2
break
;;
*)
;;
esac
fi
done
echo ${finalArg1}
EOF
chmod +x secondScript.sh
./secondScript.sh --finalArg1 {arg1} --finalArg2 {arg2} --finalArg3 {arg3}
so the end result is arg1 printed to the console. Instead, this code just prints
{finalArg1}. Any way to do this?
Ah nvm this was just an oversight -- Forgot to add $ to substitute the variables on the second script call. Should look like this:
./secondScript.sh --finalArg1 ${arg1} --finalArg2 ${arg2} --finalArg3 ${arg3}

Using getopt to parse second argument into a variable

How do I parse the second argument into a variable using bash and getopt on the following script.
I can do sh test.sh -u and get "userENT" to display. But if I do sh test.sh -u testuser on this script I get an error.
#!/bin/sh
# Now check for arguments
OPTS=`getopt -o upbdhrstv: --long username,password,docker-build,help,version,\
release,remote-registry,stage,develop,target: -n 'parse-options' -- "$#"`
while true; do
case "$1" in
-u | --username)
case "$2" in
*) API_KEY_ART_USER="$2"; echo "userENT" ;shift ;;
esac ;;
-- ) shift; break ;;
* ) if [ _$1 != "_" ]; then ERROR=true; echo; echo "Invalid option $1"; fi; break ;;
esac
done
echo "user" $API_KEY_ART_USER
How can I pass the -u testuser and not have an Invalid option testuser error?
Output:
>sh test3.sh -u testuser
userENT
Invalid option testuser
user testuser
man getopt would tell you that a colon following the option indicates that it has an argument. You only have a colon after the v. You also weren't shifting within your loop, so you'd be unable to parse any options past the first one. And I'm not sure why you felt the need to have a second case statement that only had a single default option. In addition, there were a number of poor practices in your code including use of all caps variable names and backticks instead of $() for executing commands. And you've tagged your question bash but your shebang is /bin/sh. Give this a try, but you shouldn't be using code without understanding what it does.
#!/bin/sh
# Now check for arguments
opts=$(getopt -o u:pbdhrstv: --long username:,password,docker-build,help,version,\
release,remote-registry,stage,develop,target: -n 'parse-options' -- "$#")
while true; do
case "$1" in
-u|--username)
shift
api_key_art_user="$1"
echo "userENT"
;;
--)
shift;
break
;;
*)
if [ -n "$1" ]; then
err=true
echo "Invalid option $1"
fi
break
;;
esac
shift
done
echo "user $api_key_art_user"

Why do I have error with multi long options using getopt in bash?

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.

How to sort a file with a bash script using sort and getopts?

This is my bash script that sorts a file by columns.
while getopts "123456" flag
do
sort -t: -k $flag names.txt
done
Right now it does exactly what I need, but I need to have the filename be a parameter too.
The input right now is ./sortScrpt -2.
I need the input to look like ./sortScript -2 names.txt
Any help would be great. Thanks
Change your bash script to:
while getopts "123456" flag
do
sort -t: -k $flag "${2}"
done
Getting parameters you can use "${2}" for the second parameter
Something to get you started:
#!/bin/bash
while getopts ":2:" opt; do
case $opt in
2)
echo "-2 was triggered, Parameter: $OPTARG" >&2
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
output:
$ ./a.sh -2 names.txt
-2 was triggered, Parameter: names.txt
If you want multiple sort parameters then you can update your script as:
#first parse -f fname
getopts "f:" f
fname=$OPTARG
while getopts "123456" flag
do
sort -t: -k $flag $fname
done
You can run this script as
$ ./script.sh -f names.txt -2
or for multiple sorts
$ ./script.sh -f names.txt -2 -3
EDIT revised for "sort-arg then file" requirement + error checking:
#!/bin/bash
sflag=""
sfile=""
while getopts "123456" flag
do
[ -z "$sflag" ] && sflag="$flag"
done
shift $(($OPTIND - 1))
sfile="$1"
if [[ "$sflag" ]] && [[ "$sfile" ]];
then
sort -t: -k $sflag $sfile
else
# error - bail out
echo "usage: $0 -[123456] file.txt" >&2
exit 1
fi
OPTIND is set by getopts and will either point to $#+1 (e.g. no extra parameters) or to the first parameter not matching your getopts-flags.

Best way to parse command line args in Bash?

After several days of research, I still can't figure out the best method for parsing cmdline args in a .sh script. According to my references the getopts cmd is the way to go since it "extracts and checks switches without disturbing the positional parameter variables.Unexpected switches, or switches that are missing arguments, are recognized and reportedas errors."
Positional params(Ex. 2 - $#, $#, etc) apparently don't work well when spaces are involved but can recognize regular and long parameters(-p and --longparam). I noticed that both methods fail when passing parameters with nested quotes ("this is an Ex. of ""quotes""."). Which one of these three code samples best illustrates the way to deal with cmdline args? The getopt function is not recommended by gurus, so I'm trying to avoid it!
Example 1:
#!/bin/bash
for i in "$#"
do
case $i in
-p=*|--prefix=*)
PREFIX=`echo $i | sed 's/[-a-zA-Z0-9]*=//'`
;;
-s=*|--searchpath=*)
SEARCHPATH=`echo $i | sed 's/[-a-zA-Z0-9]*=//'`
;;
-l=*|--lib=*)
DIR=`echo $i | sed 's/[-a-zA-Z0-9]*=//'`
;;
--default)
DEFAULT=YES
;;
*)
# unknown option
;;
esac
done
exit 0
Example 2:
#!/bin/bash
echo ‘number of arguments’
echo "\$#: $#"
echo ”
echo ‘using $num’
echo "\$0: $0"
if [ $# -ge 1 ];then echo "\$1: $1"; fi
if [ $# -ge 2 ];then echo "\$2: $2"; fi
if [ $# -ge 3 ];then echo "\$3: $3"; fi
if [ $# -ge 4 ];then echo "\$4: $4"; fi
if [ $# -ge 5 ];then echo "\$5: $5"; fi
echo ”
echo ‘using $#’
let i=1
for x in $#; do
echo "$i: $x"
let i=$i+1
done
echo ”
echo ‘using $*’
let i=1
for x in $*; do
echo "$i: $x"
let i=$i+1
done
echo ”
let i=1
echo ‘using shift’
while [ $# -gt 0 ]
do
echo "$i: $1"
let i=$i+1
shift
done
[/bash]
output:
bash> commandLineArguments.bash
number of arguments
$#: 0
using $num
$0: ./commandLineArguments.bash
using $#
using $*
using shift
#bash> commandLineArguments.bash "abc def" g h i j*
Example 3:
#!/bin/bash
while getopts ":a:" opt; do
case $opt in
a)
echo "-a was triggered, Parameter: $OPTARG" >&2
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
exit 0
I find the use of getopt to be the easiest. It provides correct handling of arguments which is tricky otherwise. For example, getopt will know how to handle arguments to a long option specified on the command line as --arg=option or --arg option.
What is useful in parsing any input passed to a shell script is the use of the "$#" variables. See the bash man page for how this differs from $#. It ensures that you can process arguments that include spaces.
Here's an example of how I might write s script to parse some simple command line arguments:
#!/bin/bash
args=$(getopt -l "searchpath:" -o "s:h" -- "$#")
eval set -- "$args"
while [ $# -ge 1 ]; do
case "$1" in
--)
# No more options left.
shift
break
;;
-s|--searchpath)
searchpath="$2"
shift
;;
-h)
echo "Display some help"
exit 0
;;
esac
shift
done
echo "searchpath: $searchpath"
echo "remaining args: $*"
And used like this to show that spaces and quotes are preserved:
user#machine:~/bin$ ./getopt_test --searchpath "File with spaces and \"quotes\"."
searchpath: File with spaces and "quotes".
remaining args: other args
Some basic information about the use of getopt can be found here
If you want to avoid using getopt you can use this nice quick approach:
Defining help with all options as ## comments (customise as you wish).
Define for each option a function with same name.
Copy the last five lines of this script to your script (the magic).
Example script: log.sh
#!/bin/sh
## $PROG 1.0 - Print logs [2017-10-01]
## Compatible with bash and dash/POSIX
##
## Usage: $PROG [OPTION...] [COMMAND]...
## Options:
## -i, --log-info Set log level to info (default)
## -q, --log-quiet Set log level to quiet
## -l, --log MESSAGE Log a message
## Commands:
## -h, --help Displays this help and exists
## -v, --version Displays output version and exists
## Examples:
## $PROG -i myscrip-simple.sh > myscript-full.sh
## $PROG -r myscrip-full.sh > myscript-simple.sh
PROG=${0##*/}
LOG=info
die() { echo $# >&2; exit 2; }
log_info() {
LOG=info
}
log_quiet() {
LOG=quiet
}
log() {
[ $LOG = info ] && echo "$1"; return 1 ## number of args used
}
help() {
grep "^##" "$0" | sed -e "s/^...//" -e "s/\$PROG/$PROG/g"; exit 0
}
version() {
help | head -1
}
[ $# = 0 ] && help
while [ $# -gt 0 ]; do
CMD=$(grep -m 1 -Po "^## *$1, --\K[^= ]*|^##.* --\K${1#--}(?:[= ])" log.sh | sed -e "s/-/_/g")
if [ -z "$CMD" ]; then echo "ERROR: Command '$1' not supported"; exit 1; fi
shift; eval "$CMD" $# || shift $? 2> /dev/null
done
Testing
Running this command:
./log.sh --log yep --log-quiet -l nop -i -l yes
Produces this output:
yep
yes
By the way: It's compatible with posix!

Resources