I would like to write a script that requires -c and -f where each requires an option.
When I run my script below I get some unexpected errors:
$ ./user.sh -c
./user.sh: option requires an argument -- c
Usage: user.sh -c username -f filename
-c username
-f SSH public key
$ ./user.sh -c gg
Error: You have not given a filename.
In the first case, I would have liked it said I am missing the option for -c and in the second case I would have liked it said I am missing -f.
Question
How do I make such error handing, and what am I doing wrong?
user.sh
#!/bin/bash
usage () {
echo "Usage: user.sh -c username -f filename"
echo " -c username"
echo " -f SSH public key"
echo ""
}
if ! [ "$*" ]; then
usage
exit 1
fi
while getopts "c:f:" opt; do
case $opt in
c) user=$OPTARG;;
f) filename=$OPTARG;;
\?)
echo
usage
exit 1;;
*) echo "Internal error: Unknown option.";;
esac
done
if ! [ $filename ]; then
echo "Error: You have not given a filename."
exit 1
fi
if ! [ $user ]; then
echo "Error: You have not given an username."
exit 1
fi
The c: says 'the -c option must be followed by a username'.
The error message says 'the -c option was not followed by a username'.
Granted, it didn't mention 'username' but that's because it doesn't know what it is that follows the option -c.
The getopts built-in cannot handle mandatory options; you have to code that for yourself by checking that the mandatory options were in fact passed. It also doesn't worry if the same option is specified twice; your code has to deal with that if it matters. (It's easy to let the last specified value take effect.)
Modern style is to avoid option letters before mandatory arguments. I'm not wholly in favour of the change; it means that the ordering of the arguments becomes critical in a way that using option letters to indicate what follows does not. Without option letters, you'd write: ./user.sh username filename, but with option letters, you can write either of these and expect it to work:
./user.sh -c username -f filename
./user.sh -f filename -c username
Note that the onus is on you to worry about extra arguments too. You'll typically use:
shift $(($OPTIND - 1))
to remove the processed arguments, and you can then do:
case "$#" in
(0) : No extra arguments - OK;;
(*) echo "$0: Too many arguments" >&2; exit 1;;
esac
And variations on that theme. Note that the error report is sent to standard error, not to standard output — the >&2 redirection sends standard output (file descriptor 1) to standard error (file descriptor 2) instead.
To avoid ambiguity, I'd code your usage function a little differently:
usage()
{
{
echo "Usage: user.sh -c username -f filename"
echo " -c username Name of user to connect as"
echo " -f filename SSH public key file"
echo ""
} >&2
}
The inner braces do I/O redirection en masse, without starting a subshell. That can be useful when you need to send a number of echo commands to the same place. I've also presented the detail information a little differently, so that a user isn't confused into thinking that 'SSH public key' is three arguments to follow the -f. If there were any pure-option flags, they'd be followed by blanks:
echo " -V Print version information and exit"
Related
I would like to put my getopt call into a function so I can make my script a bit more tidy. I've read a few guides Using getopts inside a Bash function but they seem to be for getopts not getopt and cannot get my head round it.
I have the following getopt call at the start of my script
#-------------------------------------------------------------------------------
# Main
#-------------------------------------------------------------------------------
getopt_results=$( getopt -s bash -o e:h --long ENVIRONMENT:,HELP:: -- "$#" )
if test $? != 0
then
echo "Failed to parse command line unrecognized option" >&2
Usage
exit 1
fi
eval set -- "$getopt_results"
while true
do
case "$1" in
-e | --ENVIRONMENT)
ENVIRONMENT="$2"
if [ ! -f "../properties/static/build_static.${ENVIRONMENT}.properties" -o ! -f "../properties/dynamic/build_dynamic.${ENVIRONMENT}.properties" ]; then
echo "ERROR: Unable to open properties file for ${ENVIRONMENT}"
echo "Please check they exist or supply a Correct Environment name"
Usage
exit 1
else
declare -A props
readpropsfile "../properties/dynamic/dynamic.${ENVIRONMENT}.properties"
readpropsfile "../properties/static/static.${ENVIRONMENT}.properties"
fi
shift 2
;;
-h | --HELP)
Usage
exit 1
;;
--)
shift
break
;;
*)
echo "$0: unparseable option $1"
Usage
exit 1
;;
esac
done
when I put the whole lot in function , say called parse_command_line ()
and call it with parse_command_line "$#"
my script dies because it cannot work out the parameters it was called with. I have tried making OPTIND local as per some of the guides. Any advice? Thanks.
getopt shouldn't be used, but the bash-aware GNU version works fine inside a function, as demonstrated below:
#!/usr/bin/env bash
main() {
local getopt_results
getopt_results=$(getopt -s bash -o e:h --long ENVIRONMENT:,HELP:: "$#")
eval "set -- $getopt_results" # this is less misleading than the original form
echo "Positional arguments remaining:"
if (( $# )); then
printf ' - %q\n' "$#"
else
echo " (none)"
fi
}
main "$#"
...when saved as getopt-test and run as:
./getopt-test -e foo=bar "first argument" "second argument"
...properly emits:
Positional arguments remaining:
- -e
- foo=bar
- --
- hello
- cruel
- world
I'm beginning to experiment with getopts but am running into a few errors. When I enter an invalid option such as -A the program output is not what it needs to be.
#!/bin/bash
function usage() {
echo "Usage: $0 -h [database host] -d [test database name]"
exit 1
}
while getopts “:h:d:” opt; do
case ${opt} in
h)
db_host=$OPTARG
;;
d)
test_db=$OPTARG
;;
\?)
echo "Invalid option: -$OPTARG" 1>&2
usage
exit 1
;;
:)
echo “Option -$OPTARG requires an argument.” 1>$2
usage
exit 1
;;
esac
done
shift $((OPTIND-1))
if [ -z $db_host ] || [ -z $test_db ]; then
usage
else
echo "Your host is $db_host and your test database is $test_db."
fi
Example program output:
./opt.sh: illegal option -- A
Invalid option: -
Usage: ./opt.sh -h [database host] -d [test database name]
So, basically two questions:
1) I want to get rid of this first error message altogether. I want to provide my own error messages.
2) Why isn't my script producing "Invalid option: -A" instead of just "Invalid option: -"
You have the wrong types of quotes around the options argument to getopts, they're "curly quotes" instead of ASCII double quotes. As a result, : wasn't the first character of the options, so you weren't getting silent error reporting.
Change it to
while getopts ':h:d:' opt; do
I'm creating a basic script that should take 3 mandatory command line options and each one must be followed by a value. Like this:
$ myscript.sh -u <username> -p <password> -f <hosts.txt>
I'm trying to make sure the user is passing those exact 3 options and their values and nothing else, otherwise I want to print the usage message and exit.
I've been reading on getopts and came up with this:
usage () { echo "Usage : $0 -u <username> -p <password> -f <hostsFile>"; }
if [ $# -ne 6 ]
then
usage
exit 1
fi
while getopts u:p:f: opt ; do
case $opt in
u) USER_NAME=$OPTARG ;;
p) USER_PASSWORD=$OPTARG ;;
f) HOSTS_FILE=$OPTARG ;;
*) usage; exit 1;;
esac
done
echo "USERNAME: $USER_NAME"
echo "PASS: $USER_PASSWORD"
echo "FILE: $HOSTS_FILE"
I was hoping that if I do not pass any of my 3 "mandatory" options (i.e: -u -p -f) Optargs validation would catch that via the "*)" case. While that is true for other options such "-a","-b", etc.. does not seem to be the case in this particular case:
$ myscript.sh 1 2 3 4 5 6
Getops does not treat that as invalid input and the script moves on executing the echo commands showing 3 empty variables.
How can I capture the input above as being invalid as it is not in the form of:
$ myscript.sh -u <username> -p <password> -f <hosts.txt>
Thanks!
getopts has no concept of "mandatory" options. The colons in u:p:f: mean that, if one of those options happens to be supplied, then an argument to that option is mandatory. The option-argument pairs, however, are always optional.
You can require that the user provide all three though with code such as:
if [ ! "$USER_NAME" ] || [ ! "$USER_PASSWORD" ] || [ ! "$HOSTS_FILE" ]
then
usage
exit 1
fi
Place this code after the while getopts loop.
The Role of *)
I was hoping that if I do not pass any of my 3 "mandatory" options (i.e: -u -p -f) Optargs validation would catch that via the "*)" case.
The *) case is executed only if an option other than -u, -p, or -f is supplied. Thus, if someone supplied, for example a -z argument, then that case would run.
I have been trying to make a shell script that will split text files one after the other through an entire folder and deposit every split chunk into another designated folder.
Here is what I have so far, I know its probably clunky(have never tried writing a .sh before):
#!/bin/bash
#File Split Automation
echo "Usage: split [Folder w/ Input] [Folder For Outputs] [Options] [PREFIX]
Options: -b [sizeMB]: Split by size
-l [No. of Lines]: Split by Lines
If No Output Folder is Defined Default is Set To: /Desktop/splitter-parts
If No Options Are Selected Default is Size=100MB"
inputdirc=$1
outputdirc=$2
spltion=$3
meastick=$4
prefixture=$5
if [ -d $1 ]
then
echo "You Picked The Folder $1 To Split Files From"
ls $1
else
exit
fi
if [ -d $2 ]
then
echo "Please Confirm Folder Path For Output $outputdirc"
else
cd /root/Desktop/
mkdir -p splitter-parts
fi
read -t 10 -p "Press Enter Or Wait 5 Sec. To Continue"
cd $2
for swordfile in $( ls $1);
do
command -p split $3 $4 -a 3 -d $swordfile $5
done
Anything you see going wrong? Because I am not getting the output I desired, though it functioned fine when I just had a file and a folder in the split-command string.
EDIT::::
Sorry, I apologize. Just getting a bit ahead of myself.
This is what I am seeing when I run it:
root#kali:~/Desktop/Wordlists# ./splitter.sh '/root/Desktop/Wordlists' ' /root/Desktop/Untitled Folder' s 100MB
Usage: split [Folder w/ Input] [Folder For Outputs] [Options] [PREFIX]
Options: -b [sizeMB]: Split by size
-l [No. of Lines]: Split by Lines
If No Output Folder is Defined Default is Set To: /Desktop/splitter-parts
If No Options Are Selected Default is Size=100MB
You Picked The Folder /root/Desktop/Wordlists To Split Files From
10dig10milup2.txt mixed.txt
10dig10miluplow2.txt movie-characters.txt
10dig10miluplow3.txt name1s.txt
((------------------CUT------------)
lower.lst xae2.txt
lower.txt xaf2.txt
mangled.lst xag2.txt
mangled.txt xah6.txt
misc-dictionary.txt
./splitter.sh: line 24: [: /root/Desktop/Untitled: binary operator expected
Press Enter Or Wait 5 Sec. To Continue
./splitter.sh: line 37: cd: /root/Desktop/Untitled: No such file or directory
split: extra operand `10dig10milup2.txt'
Try `split --help' for more information.
split: extra operand `10dig10miluplow2.txt'
Try `split --help' for more information.
split: extra operand `10dig10miluplow3.txt'
Try `split --help' for more information.
split: extra operand `10dig10miluplow4.txt'
Try `split --help' for more information.
...................MORE OF THE SAME.......
As far as what I am supposed to see, I haven't gotten that far yet, clearly I am missing some steps.
A quick rewrite with some notes to follow:
#!/bin/bash
#File Split Automation
usage="Usage: split [Options] [Folder w/ Input] [Folder For Outputs] [PREFIX]
Options: -b [sizeMB]: Split by size
-l [No. of Lines]: Split by Lines
If No Output Folder is Defined Default is Set To: /Desktop/splitter-parts
If No Options Are Selected Default is Size=100MB"
split_opt="-b 100MB"
while getopts hb:l: opt; do
case $opt in
h) echo "$usage"; exit ;;
b) split_opt="-b $OPTARG" ;;
l) split_opt="-l $OPTARG" ;;
esac
done
shift $((OPTIND - 1))
if [[ $# -eq 0 ]]; then
echo "$usage"
exit 1
fi
inputdirc=$1
if [[ -d $inputdirc ]]; then
ls $1
else
echo "no such directory: $inputdirc" >&2
exit 1
fi
if [[ -n $2 ]]; then
outputdirc=$2
else
outputdirc=/root/Desktop/splitter-parts
fi
prefixture=$3
mkdir -p "$outputdirc"
cd "$outputdirc"
for swordfile in "$inputdirc"/*; do
command -p split $split_opt -a 3 -d "$swordfile" $prefixture
done
Notes:
you generally want to quote all your variables. This is the cause of your error, because there was a file with whitespace and square brackets in the name.
I did not quote a couple in the split command because I specifically want the shell to perform word splitting on the values
since options are, well, optional, use getopts to collect them.
you store the positional parameters in variables, but you continue to use the positional parameters. Pick one or the other.
I am trying to make a shell script which is designed to be run like this:
script.sh -t application
Firstly, in my script I want to check to see if the script has been run with the -t flag. For example if it has been run without the flag like this I want it to error:
script.sh
Secondly, assuming there is a -t flag, I want to grab the value and store it in a variable that I can use in my script for example like this:
FLAG="application"
So far the only progress I've been able to make on any of this is that $# grabs all the command line arguments but I don't know how this relates to flags, or if this is even possible.
You should read this getopts tutorial.
Example with -a switch that requires an argument :
#!/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
Like greybot said(getopt != getopts) :
The external command getopt(1) is never safe to use, unless you know
it is GNU getopt, you call it in a GNU-specific way, and you ensure
that GETOPT_COMPATIBLE is not in the environment. Use getopts (shell
builtin) instead, or simply loop over the positional parameters.
Use $# to grab the number of arguments, if it is unequal to 2 there are not enough arguments provided:
if [ $# -ne 2 ]; then
usage;
fi
Next, check if $1 equals -t, otherwise an unknown flag was used:
if [ "$1" != "-t" ]; then
usage;
fi
Finally store $2 in FLAG:
FLAG=$2
Note: usage() is some function showing the syntax. For example:
function usage {
cat << EOF
Usage: script.sh -t <application>
Performs some activity
EOF
exit 1
}
Here is a generalized simple command argument interface you can paste to the top of all your scripts.
#!/bin/bash
declare -A flags
declare -A booleans
args=()
while [ "$1" ];
do
arg=$1
if [ "${1:0:1}" == "-" ]
then
shift
rev=$(echo "$arg" | rev)
if [ -z "$1" ] || [ "${1:0:1}" == "-" ] || [ "${rev:0:1}" == ":" ]
then
bool=$(echo ${arg:1} | sed s/://g)
booleans[$bool]=true
echo \"$bool\" is boolean
else
value=$1
flags[${arg:1}]=$value
shift
echo \"$arg\" is flag with value \"$value\"
fi
else
args+=("$arg")
shift
echo \"$arg\" is an arg
fi
done
echo -e "\n"
echo booleans: ${booleans[#]}
echo flags: ${flags[#]}
echo args: ${args[#]}
echo -e "\nBoolean types:\n\tPrecedes Flag(pf): ${booleans[pf]}\n\tFinal Arg(f): ${booleans[f]}\n\tColon Terminated(Ct): ${booleans[Ct]}\n\tNot Mentioned(nm): ${boolean[nm]}"
echo -e "\nFlag: myFlag => ${flags["myFlag"]}"
echo -e "\nArgs: one: ${args[0]}, two: ${args[1]}, three: ${args[2]}"
By running the command:
bashScript.sh firstArg -pf -myFlag "my flag value" secondArg -Ct: thirdArg -f
The output will be this:
"firstArg" is an arg
"pf" is boolean
"-myFlag" is flag with value "my flag value"
"secondArg" is an arg
"Ct" is boolean
"thirdArg" is an arg
"f" is boolean
booleans: true true true
flags: my flag value
args: firstArg secondArg thirdArg
Boolean types:
Precedes Flag(pf): true
Final Arg(f): true
Colon Terminated(Ct): true
Not Mentioned(nm):
Flag: myFlag => my flag value
Args: one => firstArg, two => secondArg, three => thirdArg
Basically, the arguments are divided up into flags booleans and generic arguments.
By doing it this way a user can put the flags and booleans anywhere as long as he/she keeps the generic arguments (if there are any) in the specified order.
Allowing me and now you to never deal with bash argument parsing again!
You can view an updated script here
This has been enormously useful over the last year. It can now simulate scope by prefixing the variables with a scope parameter.
Just call the script like
replace() (
source $FUTIL_REL_DIR/commandParser.sh -scope ${FUNCNAME[0]} "$#"
echo ${replaceFlags[f]}
echo ${replaceBooleans[b]}
)
Doesn't look like I implemented argument scope, not sure why I guess I haven't needed it yet.
Try shFlags -- Advanced command-line flag library for Unix shell scripts.
https://github.com/kward/shflags
It is very good and very flexible.
FLAG TYPES: This is a list of the DEFINE_*'s that you can do. All flags take
a name, default value, help-string, and optional 'short' name (one-letter
name). Some flags have other arguments, which are described with the flag.
DEFINE_string: takes any input, and intreprets it as a string.
DEFINE_boolean: typically does not take any argument: say --myflag to set
FLAGS_myflag to true, or --nomyflag to set FLAGS_myflag to false.
Alternately, you can say
--myflag=true or --myflag=t or --myflag=0 or
--myflag=false or --myflag=f or --myflag=1
Passing an option has the same affect as passing the option once.
DEFINE_float: takes an input and intreprets it as a floating point number. As
shell does not support floats per-se, the input is merely validated as
being a valid floating point value.
DEFINE_integer: takes an input and intreprets it as an integer.
SPECIAL FLAGS: There are a few flags that have special meaning:
--help (or -?) prints a list of all the flags in a human-readable fashion
--flagfile=foo read flags from foo. (not implemented yet)
-- as in getopt(), terminates flag-processing
EXAMPLE USAGE:
-- begin hello.sh --
! /bin/sh
. ./shflags
DEFINE_string name 'world' "somebody's name" n
FLAGS "$#" || exit $?
eval set -- "${FLAGS_ARGV}"
echo "Hello, ${FLAGS_name}."
-- end hello.sh --
$ ./hello.sh -n Kate
Hello, Kate.
Note: I took this text from shflags documentation