我制作了一个 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
语句都不会出现这种情况。
我已经尝试过getopts
了case
,但我根本无法让它发挥作用。这是迄今为止我所拥有的唯一没有损坏的代码版本。
不过,我要辩解一下,我是 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