I'm trying to better understand how to write bash scripts using positional parameters as arguments to the script. Specifically I want to understand how to make a script as user friendly as possible, capable of handling multiple arguments from any position. Much in that ls -lst
is the same as ls -stl
. I decided to attempt a password generator. I'm not very familiar with writing scripts that take arguments. After a while I just decided to do the whole thing using if
statements.
Note: to give credit where credit is due, the rand_string
function in the script below came directly from this answer.
#!/bin/bash
## grep -v '##' will remove all the comments if you're so inclined :)
usage(){
echo "usage: $0 [n][-c]||[-h]"
}
## Character set used to create random strings
chars=( {a..z} {A..Z} {0..9} \! \" \# \$ \% \& \( \) \* \+ \, \- \. \/ \: \; \< \= \> \? \@ \[ \] \^ \_ \` \{ \| \} \~ \\ )
## Create random string from character set
rand_string() {
local c=$1 ret=
while((c--)); do
ret+=${chars[$((RANDOM%${#chars[@]}))]}; done
printf '%s\n' "$ret"
}
## get a random number between 14-50
length() {
python -S -c "import random; print random.randrange(14,50)"
}
## regular expression to test for valid numbers
re='^[0-9]+$'
## if no options specified
## create a random string of charcters
## of a random length between 14-50 characters
## display password on screen and exit
if [ ! "$1" ]; then
set - "$(length)"
password="$(rand_string "$1")"
echo "$password"
exit
fi
## if option 1 or option 2 is -h or -help
## display usage and exit
if [[ "$1" == -h || "$1" == -help || "$2" == -h || "$2" == -help ]]; then
usage
exit
fi
## if more than 2 options
## exit with error code 1
if [[ $# -gt 2 ]]; then
echo "Invalid number of options specified"
usage
exit 1
fi
## if exactly 2 options
if [[ $# -eq 2 ]]; then
## test if option 1 is a number
## if option 1 is NOT a number
if [[ ! "$1" =~ $re ]]; then
## test if option 1 is -c or -copy
if [[ "$1" == -c || "$1" == -copy ]]; then
## if option 1 is -c or -copy
## test if option 2 is a number
## if 2 is a number and 1 is -c or -copy
## execute the command
if [[ "$2" =~ $re ]]; then
set - "$(length)"
rand_string "$1" | pbcopy
echo "Password copied to clipboard"
exit
## if option 1 is -c or -copy
## but option 2 is NOT a number
## exit script with error code 1
elif [[ ! "$2" =~ $re ]]; then
echo "Unrecognized option \"$2\""
usage
exit 1
fi
else
echo "Unrecognized option \"$1\""
exit 1
fi
## if option 1 is a number
elif [[ "$1" =~ $re ]]; then
## and option 2 is -c or -copy
## execute the command
if [[ "$2" == -c || "$2" == -copy ]]; then
rand_string "$1" | pbcopy
echo "Password copied to clipboard"
exit
## if option 1 is a number
## but option 2 is not -c or -copy
## exit script with error code 1
else
echo "Unrecognized option \"$2\""
usage
exit 1
fi
fi
## if exactly one option specified
elif [[ $# -eq 1 ]]; then
## if option is NOT a number
## check if option is -c or -copy
if [[ ! "$1" =~ $re ]]; then
## if option is -c or -copy
## execute the command
if [[ "$1" == -c || "$1" == -copy ]]; then
set - "$(length)"
rand_string "$1" | pbcopy
echo "Password copied to clipboard"
exit
## if option is neither a number nor -c or -copy
## exit script with error code 1
else
echo "Unrecognized option \"$1\""
usage
exit 1
fi
## if option is a number
## execute the command
elif [[ "$1" =~ $re ]]; then
rand_string "$1"
fi
fi
This script takes two optional arguments:
any whole number e.g..
10
,29
,54
to determine the length of string-c
or-copy
to copy the string to the clipboard-h
or-help
to display theusage
function
Any of the above arguments can be called from the $1
or $2
position. Any of these arguments may also be omitted.
Assuming the script is called password
any of the following are valid uses:
password
password 25
password -c
password 16 -copy
password -c 42
password -help
But the script itself is a giant mess of if
statements. It's long, messy and hard to read.
So my question is: How could this be written to take the same arguments shown in valid uses without using so many if
statements?
I'm not asking anyone to rewrite my script. I'm just looking for some guidance to gain better understanding of how to do this correctly.