我需要配置几台服务器,所有服务器都有相同的用户,我需要让用户输入用户名和新密码,这样我就可以运行此脚本来配置所有服务器。这是我目前面临的错误,抱歉,我是 Linux 脚本的新手,请帮帮我,谢谢!
$ cat serverlist.txt
192.168.1.8
192.168.1.7
192.168.1.6
$ cat changepasswd.sh
#!/bin/bash
read -p "Enter username: " username
read -p "Enter new password: " new_password
for server in 'cat serverlist.txt'
do echo -e "Server IP is: $server"
ssh $server 'echo "new_password\nnew_password" | passwd username
done
$ chmod 777 changepasswd.sh
$ sh changepasswd.sh
Enter username: vncuser
Enter new password: abc12345
changepasswd.sh: 9: changepasswd.sh: Syntax error: Unterminated quoted string
答案1
就像错误所说的那样,有一个未终止的引号字符串。不成对的单引号 ( '
) 位于$server
和之间echo
。如果你只像这样修复它
ssh $server 'echo "new_password\nnew_password" | passwd username'
你将会遇到更多问题。
我注意到的问题(从一般问题开始,不一定是最严重的问题):
您使用了 shebang (
#!/bin/bash
),然后通过 调用了脚本sh changepasswd.sh
。在这种情况下,shebang 被完全忽略,解释器是sh
。通常最好依靠 shebang 并以 运行脚本./the_script
;这样,调用者就不需要考虑使用哪个解释器,正确的解释器就在 shebang 中。您使脚本可执行(使用
chmod
),但sh changepasswd.sh
不需要它是可执行的。另一方面,调用 则./changepasswd.sh
需要它是可执行的。您使用
sh
作为解释器,但read -p
它不可移植。您的sh
可能支持它,其他实现可能支持也可能不支持。使用bash
不仅允许您-p
可靠地使用,而且-s
(不回显密码)。无论如何,您都应该使用-r
和IFS=''
(空字符串)尤其是在读取密码时。在 Bash 中查看help read
以了解这些选项的作用。如果您决定使用 shebang,最好去掉后缀
.sh
。如果您将脚本移植到另一个解释器(甚至编译二进制可执行文件),该名称changepasswd
仍然合适,但changepasswd.sh
需要更改,否则会造成混淆。chmod 777
使任何人都可以写入该脚本。如果脚本位于其他用户无法写入的目录中享有一定的权利,他们将能够更改脚本。例如,他们可以注入一条将为$new_password
他们发送/存储的线路。744
可能是正确的模式。建议两次询问新密码,如果两个字符串不匹配则中止。
for server in 'cat serverlist.txt'
将cat serverlist.txt
(string) 赋值给变量。你的意思是`cat serverlist.txt`
或$(cat serverlist.txt)
。后者更好。一般来说 你应该双引号
$( )
以防止单词拆分和文件名生成;但在这种情况下,您确实需要单词拆分(但您不想生成文件名)。循环遍历文件的行(serverlist.txt
在您的例子中)可以使用read
(但存在陷阱)。但是,如果您的文件仅包含 IP 地址或简单主机名,则for server in $(cat serverlist.txt)
似乎没问题。只需记住这不是一个好的通用方法。您仍然应该用双引号括住诸如这样的变量
$server
。"new_password\nnew_password"
只是一个字符串。您想要扩展变量:"$new_password\n$new_password"
。一般来说,在连接时,您可能想要使用${new_password}
。“已修复” 片段
ssh $server 'echo "$new_password\n$new_password" | passwd $username'
不会扩大$new_password
,也不会$username
局部扩大,因为这些是单引号。远程 shell 将扩展它们(最有可能扩展为空字符串)。若要在本地扩展,应将变量用双引号引起来:
ssh "$server" "echo '$new_password\n$new_password' | passwd '$username'"
但这仍然容易出错,请继续阅读。
- SSH 服务器将获取整个命令字符串作为字符串。远程 shell 将对其进行解释。这意味着 (locally expand)
$new_password
(或$username
) 中的单引号字符将破坏代码。这样你甚至可以注入代码。这与可能的代码注入无关(我知道你可以执行任何代码);而是与设置意外密码有关。有几种方法可以解决这个问题:
- 一般来说,
ssh
如果服务器允许,你可以告诉将一些变量传递到远程端。然后远程 shell 可以安全地扩展它们。这取决于服务器配置;此外,在客户端,ssh
动态定义变量很困难(而且不优雅)。在这种情况下,你不应该这样做,但如果你想了解更多信息,请PermitUserEnvironment
在 中进行调查man 5 sshd_config
。 - Bash 允许以特殊方式扩展变量,因此在 shell 中解析结果后,它会再次变为原始字符串。这是通过 实现的
${variable@Q}
。扩展的字符串被正确引用和/或转义。 - 由于您正在从远程端的管道读取,因此您可以通过管道传输到
ssh
本地,并避免将(本地扩展)$new_password
作为即将被解析的字符串传递。
无论你选择什么,请记住你需要正确引用本地 shell 和分别地用于远程 shell(如果${variable@Q}
本地Q
操作员处理此部分)。
- 不要用来
echo
传递“随机”字符串,更喜欢printf
。如果你决定在 Bash 中将变量通过管道传输到ssh
,那么这里是字符串可能是一个好方法。对于你的情况,你需要传递$new_password
两次(加上换行符),所以无论如何我都会选择这个方法printf
。
该脚本可能如下所示:
#!/bin/bash
IFS= read -rp "Enter username: " username
IFS= read -rsp "Enter new password: " new_password
echo # just to get a newline
IFS= read -rsp "Repeat new password: " new_password_2
echo
[ "$new_password" = "$new_password_2" ] || { echo "Mismatch. Aborting."; exit 1; }
for server in $(cat serverlist.txt)
do echo "Server IP is: $server"
printf '%s\n' "$new_password" "$new_password" | ssh "$server" "passwd -- ${username@Q}"
done
您需要使其可执行(chmod u+x changepasswd
)并使用运行它./changepasswd
。
笔记: