This is a variation on your script:
#!/bin/bash
opt_l=8 # password length is 8 by default
opt_o=/dev/stdout # write to standard output by default
opt_s=0 # insecure password by default
opt_u= # no default username
while getopts 'l:o:su:' opt; do
case "$opt" in
l) opt_l=$OPTARG ;;
o) opt_o=$OPTARG ;;
s) opt_s=1 ;;
u) opt_u=$OPTARG ;;
*) echo 'Error parsing options' >&2
exit 1
esac
done
args=( "$opt_l" 1 )
if (( opt_s )); then
args=( -s "${args[@]}" )
fi
if [ -n "$opt_u" ]; then
printf '%s: %s\n' "$opt_u" "$( pwgen "${args[@]}" )" >"$opt_o"
else
pwgen "${args[@]}" >"$opt_o"
fi
I've changed some of the names of the flags so that they are more in line with what standard tools use. Instead of printing the error message when finding an unsupported flag, you could output some usage info to the user.
The main thing is the use of getopts
here. It takes a specification as a string of the options that you're using. The :
in the string means that the preceding option character takes an argument (other options are booleans).
In the while
loop, $opt
will be the option and $OPTARG
will be the option argument, if the flag takes an argument.
One usually also does shift "$(( OPTIND - 1 ))"
after the loop, but as this script does not accept additional operands on the command line apart from the options and their arguments, this is not needed. The shift
would make sure that additional operands would be available as $1
, $2
etc.
You would use the script as
$ ./script.sh -l 4 # generates password of length 4 in the terminal
$ ./script.sh -l 4 -o file # generates password of length 4 and saves to "file"
$ ./script.sh -u bob # generates password of length 8, prepends with username "bob"
$ ./script.sh -s -u bob # generates more random password of length 8, prepends with username "bob"
-s -u bob
is the same as -ubob -s
and -subob
, and as -u alice -s -u bob
(later options override earlier ones).
It would be fairly easy to remove pwgen
from this and just use another password generator. To simplify this, one could put the password generator in its own function which means that the main script would never have to be modified when replacing the generator:
#!/bin/bash
passgen () {
local args=( "$opt_l" 1 )
if (( opt_s )); then
args=( -s "${args[@]}" )
fi
pwgen "${args[@]}"
}
opt_l=8 # password length is 8 by default
opt_o=/dev/stdout # write to standard output by default
opt_s=0 # insecure password by default
opt_u= # no default username
while getopts 'l:o:su:' opt; do
case "$opt" in
l) opt_l=$OPTARG ;;
o) opt_o=$OPTARG ;;
s) opt_s=1 ;;
u) opt_u=$OPTARG ;;
*) echo 'Error parsing options' >&2
exit 1
esac
done
if [ -n "$opt_u" ]; then
printf '%s: %s\n' "$opt_u" "$( passgen )" >"$opt_o"
else
passgen >"$opt_o"
fi
For example, this is more or less your generator:
passgen () {
local source=/dev/urandom
local chars='A-Za-z0-9'
if (( opt_s )); then
chars="$chars"'.-@'
fi
tr -dc "$chars" <"$source" | head -c "$opt_l"
}
... and the following generates a passphrase using words from /usr/share/dict/words
(the number of words is taken from the -l
option). If the -s
option is used, it adds a random number to the end instead of the last word.
passgen () {
local dict=/usr/share/dict/words
local numwords=$opt_l
if (( opt_s )); then
numwords=$(( numwords - 1 ))
fi
{
shuf -n "$numwords" "$dict"
if (( opt_s )); then
printf '%d\n' "$RANDOM"
fi
} | tr '\n' ' ' | sed 's/ $//'
}
Running with this last passgen
function, the command
./script.sh -s -u bob -l 3
may generate something like
bob: cassis befluster 22625