为 bash 密码脚本创建标志

为 bash 密码脚本创建标志

我制作了一个 bash 脚本来生成随机密码(如果“标志”正确)将其存储在 txt 文件中。我已在脚本的路径中添加了一个别名,以便可以将其作为单个命令运行。我称之为“兰德”。

我已经拥有的东西可以工作,但它一点也不优雅。首先,我希望这些标志可以按任意顺序包含,而不是像现在这样固定。目前我只有两个。 -p 表示“安全密码”,-s 表示“保存”。我想至少再添加一个 -u 来表示“用户名”,但由于我对代码的当前状态不满意,所以我暂时推迟了这一点。

我想像这样运行命令:

rand -FLAGS_IN_ANY_ORDER -NUMBER -FILE_NAME

这是代码的当前版本:

#!/bin/bash

num=$#
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

if [[ "$1" = "-p" ]]; then
    cat /dev/urandom \
    | tr -dc A-Za-z0-9.-@ \
    | head -c $2 ; echo

elif [[ "$1" = "-ps" ]] && [[ "$3" = * ]]; then
    cat /dev/urandom \
    | tr -dc A-Za-z0-9.-@ \
    | head -c $2 > $DIR/$3.txt && echo >> $DIR/$3.txt

    cat $DIR/$3.txt

elif [[ "$1" = "-s" ]] && [[ "$3" = * ]]; then
    cat /dev/urandom \
    | tr -dc A-Za-z0-9 \
    | head -c $2 > $DIR/$3.txt && echo >> $DIR/$3.txt

    cat $DIR/$3.txt

else
    cat /dev/urandom \
    | tr -dc A-Za-z0-9 \
    | head -c $1 ; echo
fi

理想情况下,我想要尽可能少的代码行,但所有这些if语句都不会出现这种情况。

我已经尝试过getoptscase,但我根本无法让它发挥作用。这是迄今为止我所拥有的唯一没有损坏的代码版本。

不过,我要辩解一下,我是 UNIX 类系统的新手,几周前才开始编写 bash 脚本。

你会如何解决这个问题?

答案1

以下脚本比您的脚本长,但所有额外的长度都是因为我添加了一个usage打印使用帮助消息的函数(它还用于在检测到错误条件时打印错误消息)并添加了很多注释来解释什么代码确实如此以及为什么。

解析选项并完成工作的代码要短得多,更简单,并且更容易扩展(例如,它当前需要一个-u username选项,但不会对其执行任何操作,因为您没有提到应该如何使用它)。所有的特殊情况处理都被消除了,关于做什么和如何做的“决定”是在 case 语句和紧随其后的一些 if/then 测试中做出的。

我还添加了一个-d选项,以便您可以指定输出目录...但这除了如何添加另一个选项的示例之外都是多余的,因为我还添加了检查输出文件是否包含字符的代码/,如果没有,则仅在前面添加$outdir(从您的名称中重命名- 在脚本中使用小写变量名称是一个好习惯,为标准实用程序保留大写字母)。$DIR即您可以使用-f /path/to/file-d /path/to -f file

顺便说一句,我建议不要使用此脚本的完整路径创建别名,而是将其放入或在主目录中/usr/local/bin创建子目录并将其添加到 $PATH 中。然后您可以在其中编写并保存任意数量的自定义脚本。bin/~/bin

#!/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

顺便说一句,已经存在许多密码生成程序。例如普世根makepasswd

我曾经用来pwgen生成 16 位以上的密码,直到我编写了自己的密码生成器,该生成器随机选择一些单词/usr/share/dict/words并将它们与随机生成的 1-3 位数字和/或标点符号连接在一起。 /usr/share/dict/words都是小写的,所以我的脚本随机将一些字母大写。这会生成很长但易于记住的密码。随机数字、标点符号和大写字母增加了暴力破解的搜索空间,并有助于确保密码的强度不会因单词词典的可预测性而受到损害。

例如

$ random-password.sh 
31 surVeying % deRAngement 6 ratiocinations 51 sunDowns
$ printf '%s' '31 surVeying % deRAngement 6 ratiocinations 51 sunDowns' | wc -c
55

密码长度比复杂性重要得多 - 使用现代硬件可以在很短的时间内破解任何长度小于 10 个字符的密码(<= 7 个字符需要一秒或更短时间,8 个字符需要几个小时,10 个字符需要几个月)人物)。通过当前技术,55 个字符的密码在宇宙的生命周期内实际上是无法破解的(因此至少在 10 年内应该是安全的)。

问题在于,密码越长,人们记住它就越需要简单。我发现我只需要记住密码的开头,通过输入几次,我会自动关联(从而记住)无意义短语中的下一个数字和单词。

答案2

这就是我通常执行选项的方式,但我认为这最终会让你的脚本变得更长......不过它确实以任何顺序接受选项。

while (( $# )); do
    case $1 in
        -n)
            shift
            number=$1
        ;;
        -f)
            shift
            file_name=$1
        ;;
        -p)
            shift
            password=$1
            run=pass
        ;;
        -s)
            run=save
        ;;
        -u)
            shift
            username=$1
        ;;
        *)
            thing=$1
            save=what
        ;;
        shift
    esac
done
case $save in
    save)
        cat /dev/urandom \
            | tr -dc A-Za-z0-9.-@ \
            | head -c "$number" > "${DIR}/${file_name}.txt" && echo >> "${DIR}/${file_name}.txt"
        cat "${DIR}/${file_name}.txt"
    ;;
    pass)
        cat /dev/urandom \
        | tr -dc A-Za-z0-9.-@ \
        | head -c "$number" ; echo
    ;;
    *)
        cat /dev/urandom \
        | tr -dc A-Za-z0-9 \
        | head -c "$thing" ; echo
    ;;
esac

while只要还有选项,循环就会运行 。shift移动到下一个选项,以便它将循环遍历您传递的每个参数,并通过 case 语句运行它们以正确处理它们。

答案3

这是您的脚本的变体:

#!/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

我更改了一些标志的名称,以便它们更符合标准工具的使用。您可以向用户输出一些使用信息,而不是在发现不受支持的标志时打印错误消息。

主要是getopts这里的使用。它采用一个规范作为您正在使用的选项的字符串。字符串中的:表示前面的选项字符带有参数(其他选项是布尔值)。

while循环中,如果标志带有参数,$opt则 will 是选项,并且will 是选项参数。$OPTARG

通常也在shift "$(( OPTIND - 1 ))"循环之后执行此操作,但由于该脚本除了选项及其参数之外不接受命令行上的其他操作数,因此不需要这样做。这shift将确保额外的操作数可用作等$1$2

您可以将该脚本用作

$ ./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-ubob -s与and -subob、 and as相同-u alice -s -u bob(后面的选项覆盖前面的选项)。

pwgen从中删除并使用另一个密码生成器将相当容易。为了简化这一点,可以将密码生成器放在自己的函数中,这意味着在替换生成器时无需修改主脚本:

#!/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

例如,这或多或少是你的生成器:

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"
}

...以下内容使用来自的单词生成密码短语/usr/share/dict/words(单词数从-l选项中获取)。如果-s使用该选项,它会在末尾添加一个随机数而不是最后一个单词。

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/ $//'
}

运行最后一个passgen函数,命令

./script.sh -s -u bob -l 3

可能会生成类似的东西

bob: cassis befluster 22625

相关内容