Skip to main content
deleted ' > "$file"' from first printf - dumb mistake, that should be to stdout, not write to a file.
Source Link
cas
  • 84k
  • 8
  • 136
  • 205
#!/bin/bash

usage() {
  # this function prints a usage summary, optionally printing an error message
  local ec=0

  if [ $# -ge 2 ] ; then
    # if printing an error message, the first arg is the exit code, and
    # all remaining args are the message.
    ec="$1" ; shift
    printf "%s\n\n" "$*" >&2
  fi

  cat <<EOF
Usage:
       $(basename $0) [-p | -s file | -u <user> | -d <dir> ] <[-l] length>

Generates a random password of specified length, containing only
alpha-numeric characters.

Required arguments:

  -l <length>     Length of password.  Just the length without '-l' also works.

Options:

  -p              Allow some punctuation characters in password.
  -s <file>       Save output to filename.
  -d <dir>        Output directory for the save file
  -u <user>       Username.  At present this does nothing.

  -h              This help message.
EOF

  exit $ec
}

# Initialise variables
length=''
file=''
username=''
pattern='A-Za-z0-9'
outdir=$(dirname "$0")

# -s, -l, -u and -d require an arg. -h and -p don't. The leading ':' in
# the getopts string enables 'silent' error mode (i.e. we handle
# any errors ourselves)
while getopts ':hps:l:u:d:' opt; do
  case "$opt" in 
    p) pattern='A-Za-z0-9.-@' ;;
    s) file="$OPTARG" ;;
    l) length="$OPTARG" ;;
    u) username="$OPTARG" ;;
    d) outdir="$OPTARG" ;;

    h) usage ;;
    :) usage 1 "-$OPTARG requires an argument" ;;
    ?) usage 1 "Unknown option '$opt'" ;;
  esac
done

shift $((OPTIND -1))

# Length can be specified as either `-l nnn` or just `nnn` on the cmd line
# this code exits with an error message if no length is specified.
#
# It would probably be better to give length a default value instead.
# Just change `length=''` under the Initialise variables comment above
# to whatever you want as the default, and then delete the '[ -z "$1" ]'
# line below. Remember to update the usage message - documentation should
# always match what the code does.
if [ -z "$length" ] ; then
  [ -z "$1" ] && usage 1 "Length option is required"
  length="$1"
  shift
fi

# if there are any remaining args on the line, we don't know what to do
# with them, so exit with an error message.
[ -n "$*" ] && usage 1 "Unknown arguments: '$*'"

password=$(cat /dev/urandom | tr -dc "$pattern" | head -c "$length")

# I don't know why you want an extra newline in the output, but do it anyway.
printf "$password\n\n" > "$file" 

if [ -n "$file" ] ; then
   # if $file doesn't have a /, pre-pend "$outdir"
   [[ $file =~ '/' ]] || file="$outdir/$file"
   printf "$password\n\n" > "$file" 
fi
#!/bin/bash

usage() {
  # this function prints a usage summary, optionally printing an error message
  local ec=0

  if [ $# -ge 2 ] ; then
    # if printing an error message, the first arg is the exit code, and
    # all remaining args are the message.
    ec="$1" ; shift
    printf "%s\n\n" "$*" >&2
  fi

  cat <<EOF
Usage:
       $(basename $0) [-p | -s file | -u <user> | -d <dir> ] <[-l] length>

Generates a random password of specified length, containing only
alpha-numeric characters.

Required arguments:

  -l <length>     Length of password.  Just the length without '-l' also works.

Options:

  -p              Allow some punctuation characters in password.
  -s <file>       Save output to filename.
  -d <dir>        Output directory for the save file
  -u <user>       Username.  At present this does nothing.

  -h              This help message.
EOF

  exit $ec
}

# Initialise variables
length=''
file=''
username=''
pattern='A-Za-z0-9'
outdir=$(dirname "$0")

# -s, -l, -u and -d require an arg. -h and -p don't. The leading ':' in
# the getopts string enables 'silent' error mode (i.e. we handle
# any errors ourselves)
while getopts ':hps:l:u:d:' opt; do
  case "$opt" in 
    p) pattern='A-Za-z0-9.-@' ;;
    s) file="$OPTARG" ;;
    l) length="$OPTARG" ;;
    u) username="$OPTARG" ;;
    d) outdir="$OPTARG" ;;

    h) usage ;;
    :) usage 1 "-$OPTARG requires an argument" ;;
    ?) usage 1 "Unknown option '$opt'" ;;
  esac
done

shift $((OPTIND -1))

# Length can be specified as either `-l nnn` or just `nnn` on the cmd line
# this code exits with an error message if no length is specified.
#
# It would probably be better to give length a default value instead.
# Just change `length=''` under the Initialise variables comment above
# to whatever you want as the default, and then delete the '[ -z "$1" ]'
# line below. Remember to update the usage message - documentation should
# always match what the code does.
if [ -z "$length" ] ; then
  [ -z "$1" ] && usage 1 "Length option is required"
  length="$1"
  shift
fi

# if there are any remaining args on the line, we don't know what to do
# with them, so exit with an error message.
[ -n "$*" ] && usage 1 "Unknown arguments: '$*'"

password=$(cat /dev/urandom | tr -dc "$pattern" | head -c "$length")

# I don't know why you want an extra newline in the output, but do it anyway.
printf "$password\n\n" > "$file" 

if [ -n "$file" ] ; then
   # if $file doesn't have a /, pre-pend "$outdir"
   [[ $file =~ '/' ]] || file="$outdir/$file"
   printf "$password\n\n" > "$file" 
fi
#!/bin/bash

usage() {
  # this function prints a usage summary, optionally printing an error message
  local ec=0

  if [ $# -ge 2 ] ; then
    # if printing an error message, the first arg is the exit code, and
    # all remaining args are the message.
    ec="$1" ; shift
    printf "%s\n\n" "$*" >&2
  fi

  cat <<EOF
Usage:
       $(basename $0) [-p | -s file | -u <user> | -d <dir> ] <[-l] length>

Generates a random password of specified length, containing only
alpha-numeric characters.

Required arguments:

  -l <length>     Length of password.  Just the length without '-l' also works.

Options:

  -p              Allow some punctuation characters in password.
  -s <file>       Save output to filename.
  -d <dir>        Output directory for the save file
  -u <user>       Username.  At present this does nothing.

  -h              This help message.
EOF

  exit $ec
}

# Initialise variables
length=''
file=''
username=''
pattern='A-Za-z0-9'
outdir=$(dirname "$0")

# -s, -l, -u and -d require an arg. -h and -p don't. The leading ':' in
# the getopts string enables 'silent' error mode (i.e. we handle
# any errors ourselves)
while getopts ':hps:l:u:d:' opt; do
  case "$opt" in 
    p) pattern='A-Za-z0-9.-@' ;;
    s) file="$OPTARG" ;;
    l) length="$OPTARG" ;;
    u) username="$OPTARG" ;;
    d) outdir="$OPTARG" ;;

    h) usage ;;
    :) usage 1 "-$OPTARG requires an argument" ;;
    ?) usage 1 "Unknown option '$opt'" ;;
  esac
done

shift $((OPTIND -1))

# Length can be specified as either `-l nnn` or just `nnn` on the cmd line
# this code exits with an error message if no length is specified.
#
# It would probably be better to give length a default value instead.
# Just change `length=''` under the Initialise variables comment above
# to whatever you want as the default, and then delete the '[ -z "$1" ]'
# line below. Remember to update the usage message - documentation should
# always match what the code does.
if [ -z "$length" ] ; then
  [ -z "$1" ] && usage 1 "Length option is required"
  length="$1"
  shift
fi

# if there are any remaining args on the line, we don't know what to do
# with them, so exit with an error message.
[ -n "$*" ] && usage 1 "Unknown arguments: '$*'"

password=$(cat /dev/urandom | tr -dc "$pattern" | head -c "$length")

# I don't know why you want an extra newline in the output, but do it anyway.
printf "$password\n\n"

if [ -n "$file" ] ; then
   # if $file doesn't have a /, pre-pend "$outdir"
   [[ $file =~ '/' ]] || file="$outdir/$file"
   printf "$password\n\n" > "$file" 
fi
corrected typo and edited to fake the randomisation of letters - the example randomly didn't capitalise any.
Source Link
cas
  • 84k
  • 8
  • 136
  • 205

I used to use pwgen to generate 16+ digit passwords until I wrote my own passphrase generator that randomly selects a number of words from /usr/share/dict/words and joins them together with randomly-generated 1-3 digit numbers and/or punctuation. /usr/share/dict/words is all-lowercase, so my script randolyrandomly capitalises some of the letters. This generates very long but easy to remember passwords. The random digits and punctuation and capitalisation increases the search space for brute-force cracking and helps to ensure that the password's strength isn't compromised by the predictability of the word dictionary.

$ random-password.sh 
31 surveyingsurVeying % derangementdeRAngement 6 ratiocinations 51 sundownssunDowns
$ printf '%s' '31 surveyingsurVeying % derangementdeRAngement 6 ratiocinations 51 sundowns'sunDowns' | wc -c
55

I used to use pwgen to generate 16+ digit passwords until I wrote my own passphrase generator that randomly selects a number of words from /usr/share/dict/words and joins them together with randomly-generated 1-3 digit numbers and/or punctuation. /usr/share/dict/words is all-lowercase, so my script randoly capitalises some of the letters. This generates very long but easy to remember passwords. The random digits and punctuation and capitalisation increases the search space for brute-force cracking and helps to ensure that the password's strength isn't compromised by the predictability of the word dictionary.

$ random-password.sh 
31 surveying % derangement 6 ratiocinations 51 sundowns
$ printf '%s' '31 surveying % derangement 6 ratiocinations 51 sundowns' | wc -c
55

I used to use pwgen to generate 16+ digit passwords until I wrote my own passphrase generator that randomly selects a number of words from /usr/share/dict/words and joins them together with randomly-generated 1-3 digit numbers and/or punctuation. /usr/share/dict/words is all-lowercase, so my script randomly capitalises some of the letters. This generates very long but easy to remember passwords. The random digits and punctuation and capitalisation increases the search space for brute-force cracking and helps to ensure that the password's strength isn't compromised by the predictability of the word dictionary.

$ random-password.sh 
31 surVeying % deRAngement 6 ratiocinations 51 sunDowns
$ printf '%s' '31 surVeying % deRAngement 6 ratiocinations 51 sunDowns' | wc -c
55
Source Link
cas
  • 84k
  • 8
  • 136
  • 205

The following script is longer than yours, but all of the extra length is because I added a usage function to print a usage help message (it also serves to print an error message when an error condition is detected) and added lots of comments to explain what the code does and why.

The code that parses the options and does the work is a lot shorter and simpler, and easier to expand (e.g. it currently takes a -u username option but doesn't do anything with it because you didn't mention how it should be used). All the special-case handling is eliminated, the "decisions" about what to do and how to do it are made in the case statement and in a few if/then tests immediately after it.

I've also added a -d option so that you can specify an output directory...but that's redundant as anything except an example of how to add another option, because I've also added code that checks if the output file contains a / character, and only prepend $outdir (renamed from your $DIR - it's a good habit to use lowercase variables names in your script, reserving uppercase for standard utilities) if it doesn't. i.e. you can use either -f /path/to/file or -d /path/to -f file.

BTW, rather than make an alias with the full path to this script, I recommend just putting it in /usr/local/bin or creating a bin/ sub-directory in your home dir and adding ~/bin to your $PATH. Then you can just write and save as many custom scripts as you want in there.

#!/bin/bash

usage() {
  # this function prints a usage summary, optionally printing an error message
  local ec=0

  if [ $# -ge 2 ] ; then
    # if printing an error message, the first arg is the exit code, and
    # all remaining args are the message.
    ec="$1" ; shift
    printf "%s\n\n" "$*" >&2
  fi

  cat <<EOF
Usage:
       $(basename $0) [-p | -s file | -u <user> | -d <dir> ] <[-l] length>

Generates a random password of specified length, containing only
alpha-numeric characters.

Required arguments:

  -l <length>     Length of password.  Just the length without '-l' also works.

Options:

  -p              Allow some punctuation characters in password.
  -s <file>       Save output to filename.
  -d <dir>        Output directory for the save file
  -u <user>       Username.  At present this does nothing.

  -h              This help message.
EOF

  exit $ec
}

# Initialise variables
length=''
file=''
username=''
pattern='A-Za-z0-9'
outdir=$(dirname "$0")

# -s, -l, -u and -d require an arg. -h and -p don't. The leading ':' in
# the getopts string enables 'silent' error mode (i.e. we handle
# any errors ourselves)
while getopts ':hps:l:u:d:' opt; do
  case "$opt" in 
    p) pattern='A-Za-z0-9.-@' ;;
    s) file="$OPTARG" ;;
    l) length="$OPTARG" ;;
    u) username="$OPTARG" ;;
    d) outdir="$OPTARG" ;;

    h) usage ;;
    :) usage 1 "-$OPTARG requires an argument" ;;
    ?) usage 1 "Unknown option '$opt'" ;;
  esac
done

shift $((OPTIND -1))

# Length can be specified as either `-l nnn` or just `nnn` on the cmd line
# this code exits with an error message if no length is specified.
#
# It would probably be better to give length a default value instead.
# Just change `length=''` under the Initialise variables comment above
# to whatever you want as the default, and then delete the '[ -z "$1" ]'
# line below. Remember to update the usage message - documentation should
# always match what the code does.
if [ -z "$length" ] ; then
  [ -z "$1" ] && usage 1 "Length option is required"
  length="$1"
  shift
fi

# if there are any remaining args on the line, we don't know what to do
# with them, so exit with an error message.
[ -n "$*" ] && usage 1 "Unknown arguments: '$*'"

password=$(cat /dev/urandom | tr -dc "$pattern" | head -c "$length")

# I don't know why you want an extra newline in the output, but do it anyway.
printf "$password\n\n" > "$file" 

if [ -n "$file" ] ; then
   # if $file doesn't have a /, pre-pend "$outdir"
   [[ $file =~ '/' ]] || file="$outdir/$file"
   printf "$password\n\n" > "$file" 
fi

BTW, numerous password-generating programs already exist. e.g. pwgen and makepasswd.

I used to use pwgen to generate 16+ digit passwords until I wrote my own passphrase generator that randomly selects a number of words from /usr/share/dict/words and joins them together with randomly-generated 1-3 digit numbers and/or punctuation. /usr/share/dict/words is all-lowercase, so my script randoly capitalises some of the letters. This generates very long but easy to remember passwords. The random digits and punctuation and capitalisation increases the search space for brute-force cracking and helps to ensure that the password's strength isn't compromised by the predictability of the word dictionary.

e.g.

$ random-password.sh 
31 surveying % derangement 6 ratiocinations 51 sundowns
$ printf '%s' '31 surveying % derangement 6 ratiocinations 51 sundowns' | wc -c
55

Password length is far more important than complexity - any password less than 10 characters long can be cracked in a very short time with modern-ish hardware (a second or less for <= 7 characters, hours for 8 characters, a few months for 10 characters). A 55 character password is effectively uncrackable within the lifetime of the universe by current technology (so should be safe for at least 10 years).

The problem is that the longer a password is, the simpler it needs to be for a human to be able to remember it. I find that I only need to remember the start of the password and by typing it a few times, I automatically associate (and thus remember) the next digits and words in the nonsense phrase.