使用Expect在Bash脚本中为SSH命令提供密码

164

我正在尝试在Bash脚本中使用expect来提供SSH密码。提供密码可以成功,但我没有像应该的那样进入SSH会话。它直接返回到Bash。

我的脚本:

#!/bin/bash

read -s PWD

/usr/bin/expect <<EOD
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr@$myhost.example.com'
expect "password"
send "$PWD\n"
EOD
echo "you're out"

我的脚本的输出:

spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr@$myhost.example.com
usr@$myhost.example.com's password: you're out

我想要在SSH会话中,只有当我退出时,才能回到我的Bash脚本。

我在使用expect之前使用Bash的原因是因为我必须使用菜单。我可以选择连接到哪个设备/单元。

对于那些想要回复我应该使用SSH密钥的人,请自重。


56
对于那些想回复我应该使用SSH密钥的人,请自行放弃。 - Max
59
我会编辑你的第一行,让它更友好一些。你可以考虑使用类似于“由于限制,我无法使用SSH密钥,必须找到一种使用expect使其正常工作的方法”的说法。你应该预料到人们可能会自然地好奇为什么你不使用密钥,并且只是想提供帮助 :) @Ignacio并没有建议你使用它们,他只是确认这是一个限制而不是疏忽。 - Tim Post
我会尝试在这种情况下使用Kermit。它有一个非常强大的脚本语言http://www.columbia.edu/kermit/skermit.html#scripts - f3xy
这个页面对我帮助很大。 (https://hostadvice.com/how-to/how-to-automate-tasks-in-ssh/) - Cadoiz
9个回答

101

混合使用Bash和Expect不是实现期望效果的好方法。我建议只使用Expect:

#!/usr/bin/expect
eval spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr@$myhost.example.com

# Use the correct prompt
set prompt ":|#|\\\$"
interact -o -nobuffer -re $prompt return
send "my_password\r"
interact -o -nobuffer -re $prompt return
send "my_command1\r"
interact -o -nobuffer -re $prompt return
send "my_command2\r"
interact

针对bash的示例解决方案可能是:

#!/bin/bash
/usr/bin/expect -c 'expect "\n" { eval spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr@$myhost.example.com; interact }'

这将等待用户按下 Enter 键,然后返回(暂时)到交互式会话。


1
它运行得很好,谢谢。如果我想在通过SSH登录后输入命令,我需要做什么? - Max
这个脚本应该返回已登录用户的交互式 shell。我不明白问题。如果你一定要在 bash 中使用相同的脚本,请查看编辑后的条目。 - Piotr Król
@Richard 在 http://wiki.tcl.tk/3914 上提供了最好的解释,“主要区别在于interact设置了一个后台匹配事件循环。expect在其匹配事件循环期间阻止前台处理(并忽略用户输入)。interact还有一组开关,允许程序员生成两个会话并连接它们的pty,从而让它们相互驱动。” - Piotr Król
看一下 sexpect。你可以只用bash编写expect脚本。 - pynexj
你可以考虑使用 send -- 来实现更广泛的应用。 - i_want_more_edits
显示剩余6条评论

58

最简单的方法是使用sshpass。这在Ubuntu/Debian仓库中可用,您无需处理将expect与Bash集成的问题。

一个例子:

sshpass -p<password> ssh <arguments>
sshpass -ptest1324 ssh user@192.168.1.200 ls -l /tmp

以上命令可轻松与 Bash 脚本集成。

注意:请阅读 man sshpass 中的 安全注意事项 部分,以充分了解其安全影响。


12
从安全角度来看非常危险——命令行参数可以被系统上的任何其他进程读取。虽然可能可以覆盖它们,希望sshpass能够做到这一点,但即使如此,在它启动之前的一段时间内,密码仍然可供任何/每个进程查看。 - Charles Duffy
1
@CharlesDuffy 当然,您是正确的。sshpass用于在本地网络环境中执行简单测试脚本的情况下,其中安全性不是最重要的考虑因素。实际上,在man sshpass中有一个完整的“安全注意事项”部分进行了解释。将此添加到答案中,谢谢。 - dotnix
通常,软件包会为您处理所有繁琐的事情,但在某些情况下,这些脚本是专门为特定用途构建的,因此最好不要添加自己的内容。这个评论是关于不要重复造轮子的。只有在没有人处理它时才处理困难的问题。sshpass是一个很好的功能。 - m3nda
@erm3nda 您的评论适用于使用OpenSSL而不是实现自己的SSL库。细节可能很棘手,所以让专家来处理它。但是 sshpass 不仅是一个非常小的工具,而且它正在做一些本质上错误的事情。您不应该这样做。如果您不想使用密码,请改用RSA密钥文件。问题不在于恶劣的细节,而在于在bash行中编写明文密码的固有问题。避免使用此类工具!(当然,我也会使用它,因为我很懒,但您真的不应该这样做) - erikbstack
2
为了完整起见,我提到“man sshpass”向潜在用户提供了适当的安全警告,指出“-p”是使用它最不安全的方式,并提供了“-e”选项以通过环境变量获取密码,这至少可以避免将其放在命令行上。 - Ron Burk
显示剩余4条评论

36
在您的EOD之前添加“interact”Expect命令。
#!/bin/bash

read -s PWD

/usr/bin/expect <<EOD
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr@$myhost.example.com
expect "password"
send -- "$PWD\r"
interact
EOD
echo "you're out"

这应该让你与远程机器交互,直到你退出登录。然后你会回到Bash。


它进去然后立即出来。打印了“你出局了”。 - Emmanuel
10
我已经用"expect eof"替换了"interact"和"EOD",这对我来说可行。这是在Mac上进行的。 - Emmanuel
3
这个页面上的所有答案都对我没用,但是@Emmanuel建议使用expect eof解决了问题。 - moertel
usr@$myhost.example.com' 中删除 ',它就可以工作了。也许你需要用 \r 替换 \n,但效果可能因人而异。 - Tino
请考虑添加此修复程序,因为否则某些密码可能无法正常工作。修复程序链接 - i_want_more_edits
在我遵循 @Emmanuel 的建议使用 expect eof 之后,我成功登录到服务器。但是我无法输入任何命令,因为它不是交互式会话并且很快就会自动退出。 - MaThMaX

28

经过数月寻找答案之后,我终于找到了最好的解决方案:编写一个简单的脚本。

#!/usr/bin/expect

set timeout 20

set cmd [lrange $argv 1 end]
set password [lindex $argv 0]

eval spawn $cmd
expect "assword:"   # matches both 'Password' and 'password'
send -- "$password\r"; # -- for passwords starting with -, see https://dev59.com/jW445IYBdhLWcg3wmLdd#21280372
interact

将其放置于/usr/bin/exp,然后您可以使用:

  • exp <password> ssh <anything>
  • exp <password> scp <anysrc> <anydst>

完成!


使用两个参数进行scp也很好。当然,ssh也可以使用两个参数。 - Timo

12

一个简单的Expect脚本:

文件 Remotelogin.exp

    #!/usr/bin/expect
    set user [lindex $argv 1]
    set ip [lindex $argv 0]
    set password [lindex $argv 2]
    spawn ssh $user@$ip
    expect "password"
    send "$password\r"
    interact

例子:

./Remotelogin.exp <ip> <user name> <password>

11

同时确保使用

send -- "$PWD\r"

否则,以破折号(-)开头的密码将会失败。

以上代码不会将以破折号开头的字符串解释为send命令的选项。


9
使用辅助工具fd0ssh(来自hxtools,Ubuntu的源代码openSUSE的源代码,而不是pmt)。它可以在不需要期望从ssh程序中特定的提示的情况下工作。
根据Charles Duffy的评论,它也比通过命令行传递密码更安全。

太好了!在Ubuntu服务器上运行有点困难,但是很流畅!我们所做的配置如下: echo "yoursshpass" | fd0ssh ssh -c -L$port:$ip:$remote_port user@yourserver.com &谢谢! - JP Illanes

6

我发现在Bash脚本中使用一个小的Expect脚本的另一种有用的方法如下。

...
Bash script start
Bash commands
...
expect - <<EOF
spawn your-command-here
expect "some-pattern"
send "some-command"
...
...
EOF
...
More Bash commands
...

这是因为如果文件名指定为“-”,那么会从标准输入中读取。

在这里您不需要-,因为如果您没有参数调用它,expect默认从stdin读取。但是,如果您想要交互式地运行它而不使用命令提示符(expect vs. expect -),或者在执行类似于expect -f "$@"的操作时,第一个参数应该是文件,即使它看起来像选项(以-开头),那么使用-是有用的。在这种情况下,如果$1(给定的文件)是-,则会从stdin读取。 - Tino
问题:1)'some-pattern'可以使用通配符吗?2)如果'your-command-here'类似于'git push',它应该加引号吗? - Erwann

3

sshpass 是一个破损的工具,如果你尝试在 Sublime Text 的构建目标或 Makefile 中使用它。相比于 sshpass你可以使用 passh

使用 sshpass 时,你需要执行以下命令:

sshpass -p pa$$word ssh user@host

使用 passh,您可以执行以下操作:
passh -p pa$$word ssh user@host

注意:不要忘记使用-o StrictHostKeyChecking=no。否则,第一次使用它时连接会挂起。例如:
passh -p pa$$word ssh -o StrictHostKeyChecking=no user@host

参考资料:

  1. 使用 Expect 脚本在 SSH 连接中发送密码命令无效
  2. 如何在 ssh 中禁用严格主机密钥检查?
  3. 如何禁用 SSH 主机密钥检查
  4. 在没有已知主机检查的情况下进行 scp 文件传输
  5. pam_mount 和带有密码验证的 sshfs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接