Reputation: 526
CODE: cmd.sh
#!/bin/bash
if [ $# -ne 4 ]; then
echo not 4
exit 1
fi
while [[ $# > 1 ]]
do
key="$1"
case $key in
-f|--file)
FILE="$2"
shift
;;
-t|--type)
TYPE="$2"
shift
;;
--default)
DEFAULT=YES
;;
*)
# unknown option
;;
esac
shift
done
echo FILE = "${FILE}"
echo TYPE = "${TYPE}"
GOAL
How do i ensure that the user always has to enter both file
and type
arguments and ensure that they always enter 2 arguments
, not more and not less.
EDIT
Updated the code above, ne 4 because of the flags.
ONLY VALID RESULT
$ ./cmd.sh -f asd -t def
FILE = asd
TYPE = def
CURRENT ISSUE
1.
$ ./cmd.sh -f asd asd asd
FILE = asd
TYPE =
I didn't include the -t flag but it still considers it as valid.
2.
$ ./cmd.sh -f asd -g asd
FILE = asd
TYPE =
I included an invalid flag but it still considers it as valid.
SOLUTION
#!/bin/bash
if [ $# -ne 4 ]; then
echo not 4
exit 1
fi
while [[ $# > 1 ]]
do
key="$1"
case $key in
-f|--file)
FILE="$2"
shift
;;
-t|--type)
TYPE="$2"
shift
;;
--default)
DEFAULT=YES
;;
*)
echo invalid option $key
exit 1
;;
esac
shift
done
echo FILE = "${FILE}"
echo TYPE = "${TYPE}"
LAST ISSUE
How do i ignore the length requirement and check if they type -h or --help? I want to print out the usage in that case.
MY ATTEMPT
#!/bin/bash
# Ensure that environment variables are not used by accident
FILE=
TYPE=
arg0="$(basename "$0" .sh)"
usage() { echo "Usage: $arg0 -f file -t type" >&2; exit 1; }
error() { echo "$arg0: $*" >&2; usage; }
help() { echo "Help: some help file" >&1; exit 0; }
version() { echo "Version: 1.0" >&1; exit 0; }
while [ $# -gt 1 ]
do
case "$1" in
-h|--help)
help
;;
-v|--version)
version
;;
-f|--file|-t|--type)
;;
-*) error "unrecognized option $1";;
*) error "unexpected non-option argument $1";;
esac
shift
done
[ $# -eq 4 ] || usage
while [ $# -gt 1 ]
do
case "$1" in
-f|--file)
[ -z "$FILE" ] || error "already specified file name $FILE"
[ -z "$2" ] && error "empty file name specified after $1"
FILE="$2"
shift
;;
-t|--type)
[ -z "$TYPE" ] || error "already specified file type $TYPE"
[ -z "$2" ] && error "empty type name specified after $1"
TYPE="$2"
shift
;;
-*) error "unrecognized option $1";;
*) error "unexpected non-option argument $1";;
esac
shift
done
# Should never execute either of these errors
[ -z "$FILE" ] && error "no file name specified"
[ -z "$TYPE" ] && error "no type name specified"
echo FILE = "${FILE}"
echo TYPE = "${TYPE}"
This broke the code and started throwing the following error:
$ bash a2.sh -f typename -t asda
a2: unexpected non-option argument typename
Usage: a2 -f file -t type
The solution below is much more elegant than what i tried to do!
UPDATE - TESTING
$ bash d2.sh -t type -f filename -V
FILE = filename
TYPE = type
$ bash d2.sh -h
d2.sh: no file name specified (and no --default)
Usage: d2 [-d|--default][-h|--help][-V|--version][{-f|--file} file] [{-t|--type} type]
$ bash d2.sh -V
d2.sh: no file name specified (and no --default)
Usage: d2 [-d|--default][-h|--help][-V|--version][{-f|--file} file] [{-t|--type} type]
Upvotes: 1
Views: 911
Reputation: 754450
Does this meet your requirements? I think it covers most of the bases, though if you elect to specify a string of blanks or tabs as a file name or a type name, the script will not object.
a2.sh
#!/bin/bash
# Ensure that environment variables are not used by accident
FILE=
TYPE=
arg0="$(basename "$0" .sh)"
usage() { echo "Usage: $arg0 -f file -t type" >&2; exit 1; }
error() { echo "$arg0: $*" >&2; usage; }
[ $# -eq 4 ] || usage
while [ $# -gt 1 ]
do
case "$1" in
-f|--file)
[ -z "$FILE" ] || error "already specified file name $FILE"
[ -z "$2" ] && error "empty file name specified after $1"
FILE="$2"
shift
;;
-t|--type)
[ -z "$TYPE" ] || error "already specified file type $TYPE"
[ -z "$2" ] && error "empty type name specified after $1"
TYPE="$2"
shift
;;
-*) error "unrecognized option $1";;
*) error "unexpected non-option argument $1";;
esac
shift
done
# Should never execute either of these errors
[ -z "$FILE" ] && error "no file name specified"
[ -z "$TYPE" ] && error "no type name specified"
echo FILE = "${FILE}"
echo TYPE = "${TYPE}"
Note that if FILE
and TYPE
were not initialized in the script, running:
FILE=henry TYPE=ford bash a2.sh -f file -t type
would generate errors because FILE
would already have a value (or TYPE
already has a value with different arguments). Making sure you don't accidentally pick up an environment variable is important. There's also a case for saying that variables in your script should be lower-case (or mixed-case), leaving upper-case for environment variables. However, you'd still need to set lower-case variables — you can just as easily have lower-case environment variables as upper-case, though upper-case is conventional.
Also note the careful, deliberate and correct use of $*
in the error
function. While using $@
wouldn't cause trouble, this time the desired result is a single string, and $*
gives that. (Very often, it is correct to use "$@"
, but this is a case where "…$*…"
is better.)
$ bash a2.sh
Usage: a2 -f file -t type
$ bash a2.sh a
Usage: a2 -f file -t type
$ bash a2.sh a b
Usage: a2 -f file -t type
$ bash a2.sh a b c
Usage: a2 -f file -t type
$ bash a2.sh a b c d
a2.sh: unexpected non-option argument a
Usage: a2 -f file -t type
$ bash a2.sh a b c d e
Usage: a2 -f file -t type
$ bash a2.sh -f file -f file
a2: already specified file name file
Usage: a2 -f file -t type
$ bash a2.sh -t type -t typename
a2: already specified file type type
Usage: a2 -f file -t type
$ bash a2.sh -t type t typename
a2: unexpected non-option argument t
Usage: a2 -f file -t type
$ bash a2.sh -t type -k typename
a2: unrecognized option -k
Usage: a2 -f file -t type
$ bash a2.sh -t type -f filename
FILE = filename
TYPE = type
$ bash a2.sh --file firstname -f filename
a2: already specified file name firstname
Usage: a2 -f file -t type
$ bash a2.sh --type typename -t type
a2: already specified file type typename
Usage: a2 -f file -t type
$ bash a2.sh -f '' -t ''
a2: empty file name specified after -f
Usage: a2 -f file -t type
$ bash a2.sh -t '' -f ''
a2: empty type name specified after -t
Usage: a2 -f file -t type
$
--help
, --version
, --default
etc.See also the comment chain (laziness at work). A revised version of the code might be:
#!/bin/bash
# Ensure that environment variables are not used by accident
FILE=
TYPE=
arg0="$(basename "$0" .sh)"
usemsg="Usage: $arg0 [-d|--default][-h|--help][-V|--version][{-f|--file} file] [{-t|--type} type]"
usage() { echo "$usemsg" >&2; exit 1; }
error() { echo "$0: $*" >&2; usage; }
while [ $# -gt 0 ]
do
case "$1" in
-f|--file)
[ -z "$FILE" ] || error "already specified file name $FILE"
[ -z "$2" ] && error "empty file name specified after $1"
FILE="$2"
shift
;;
-t|--type)
[ -z "$TYPE" ] || error "already specified file type $TYPE"
[ -z "$2" ] && error "empty type name specified after $1"
TYPE="$2"
shift
;;
-h|--help)
echo "$usemsg"
echo ""
echo " [-f|--file] filename Use the specified file name"
echo " [-t|--type] typename Use the specified type name"
echo " [-h|--help] Print this help message and exit"
echo " [-V|--version] Print the version information and exit"
echo " [-d|--default] Use the defaults (default-file and default-type)"
exit 0
;;
-V|--version)
echo "$arg0 Version 1.02 (2016-03-18)"
exit 0
;;
-d|--default)
[ -z "$FILE" ] && FILE="default-file"
[ -z "$TYPE" ] && TYPE="default-type"
;;
-*) error "unrecognized option $1";;
*) error "unexpected non-option argument $1";;
esac
shift
done
[ -z "$FILE" ] && error "no file name specified (and no --default)"
[ -z "$TYPE" ] && error "no type name specified (and no --default)"
echo FILE = "${FILE}"
echo TYPE = "${TYPE}"
I seldom test the number of arguments until after an option processing loop. You should also look at using getopts
or the GNU extended version of getopt(1)
.
Upvotes: 1
Reputation: 191844
You already are checking if there are any arguments given, so have you tried checking if there are any more or less than 2?
if [ $# -ne 2 ]; then
# TODO: print usage
exit 1
fi
# rest of code here
Upvotes: 2