运行shell脚本自动输入密码

最近因为在研究云服务器相关的部署问题,所以会经常连接远程服务器的时候输入密码,这也算是一个机械化操作,所以就想着写个shell脚本,这样可以一键执行一系列操作,提升开发效率。

不过在这中间遇到一个问题,就是如何添加一些交互动作,例如密码的输入或者选项的选择,这样可以让shell脚本更加强大和便捷。

目前实现方法主要有三种

  1. 使用管道(上一个命令的 stdout 接到下一个命令的 stdin)
  2. 使用文本块输入重定向
  3. 使用 expect 脚本(使用的是expect解释器,不是bash了)

下面进行详细分析

使用管道

可以这样写shell文件

#!/bin/bash
echo yourpassword | sudo -S apt-get update

通过使用管道符“|”,可以将上一个命令的 stdout 作为下一个命令的 stdin。这样就可以实现自动为 sudo 命令填写用户密码了。

使用文本块输入重定向

可以这样写shell文件

#!/bin/bash
sudo -S apt-get update << EOF 
yourpassword
EOF

说明:在shell脚本中,通常将EOF与 << 结合使用,表示后续的输入作为子命令或子Shell的输入,直到遇到EOF为止,再返回到主Shell,即将‘你的密码’当做命令的输入

-S参数的作用:加上-S参数 sudo才会从标准输入中读取密码,不加-S参数以上命令将起不到作用

可以通过man sudo命令找到对应的手册解释

Write the prompt to the standard error and read the password from the standard inputinstead of using the terminal device. 
The password must be followed by a newline character.

使用expect脚本

上面两种方法很常用,但是都有一个问题,就是一条命令只能输入一次。如果多次的话就无能为力。

例如使用sudo命令使用ssh登录远程服务器时,需要先输入用户root密码,再输入远程服务器的密码。这是再使用上面两种方法就很难实现了,需要用到我们接下来介绍的expect脚本

首先我们要知道一些shell脚本的基础

shell脚本的运行方式

首先要说一下shell的几种启动方式,同时也使得我们运行shell,知其所以然。

通过文件名执行

shell脚本可以直接通过文件名执行,需要注意的是文件需要执行权限。如果直接运行shell脚本显示没有权限,可以通过 sudo chmod +x ./file_name.sh 来给文件添加执行权限;

# 给文件添加执行权限
sudo chmod +x ./file_name.sh

指定脚本解释器来执行文件

我们常用的 sh file_name.sh 就是指定了脚本解释器 /bin/sh来解释执行脚本;常见的脚本解释器还有:/bin/bash等,我们可以使用ls -l /bin/*sh命令来查看当前可用的脚本解释器;

使用 ./file_name或source命令执行脚本

这种方式不会像前两种方式一样fork一个子进程去执行脚本,而是使用当前shell环境执行,用于 .bashrc或者.bash_profile被修改的时候,我们不必重启shell或者重新登录系统,就能使当前的更改生效。

例如修改.bashrc, .zshrc, .bash_profile 文件之后,需要执行一下下面的source命令才能生效

source ~/.zshrc

shell脚本的shebang属性

我们写一个shell脚本时,总是习惯在最前面加上一行 #!/binbash,它就是脚本的shebang,至于为什么叫这么个奇怪的名字,C语言和Unix的开发者丹尼斯·里奇称它为可能是类似于”hash-bang”的英国风描述性文字;

贴一段wiki上的解释:

在计算机科学中,Shebang是一个由井号和叹号构成的字符串行,其出现在文本文件的第一行的前两个字符。 在文件中存在Shebang的情况下,类Unix操作系统的程序载入器会分析Shebang后的内容,将这些内容作为解释器指令,并调用该指令,并将载有Shebang的文件路径作为该解释器的参数。

简单的说,它指示了此脚本运行时的解释器,所以,使用文件名直接执行shell脚本时,必须带上shebang; 此外,我们还可以在shebang后面直接附加选项,执行时我们默认使用选项执行;

test.shshebang#!/bin/sh -x,那我们执行脚本时:

./test.sh hello

相当于:

bin/sh -x ./test.sh hello;

而编写一个ssh自动登陆脚本,需要用到的shebang(解释器)为 /usr/bin/expect;

需要注意的是:在指定脚本解释器来执行脚本时,shebang会被指定的脚本解释器覆盖,即优先使用指定的脚本解释器来执行脚本(习惯性地用sh ./test.sh却提示command not found)

expect解释器

expect是一个能实现自动和交互式任务的解释器,它也能解释常见的shell语法命令,其特色在以下几个命令:

spawn命令:

spawn command命令会fork一个子进程去执行command命令,然后在此子进程中执行后面的命令;

在ssh自动登陆脚本中,我们使用 spawn ssh user_name@ip_str,fork一个子进程执行ssh登陆命令;

expect命令:

expect命令是expect解释器的关键命令,它的一般用法为 expect “string”,即期望获取到string字符串,可在在string字符串里使用 * 等通配符;

string与命令行返回的信息匹配后,expect会立刻向下执行脚本;

set timeout命令:

set timeout n命令将expect命令的等待超时时间设置为n秒,在n秒内还没有获取到其期待的命令,expect 为false,脚本会继续向下执行;

send命令:

send命令的一般用法为 send “string”,它们会我们平常输入命令一样向命令行输入一条信息,当然不要忘了在string后面添加上 \r 表示输入回车;

interact命令:

interact命令很简单,执行到此命令时,脚本fork的子进程会将操作权交给用户,允许用户与当前shell进行交互;

完整脚本实例

#!/usr/bin/expect
set timeout 5
set passwd "123456"
set pin 123456

spawn sudo -S ssh username@192.***.***.***
expect {
    "(yes/no)?" {
        send "yes\n"
        expect "password:"
        send "$passwd\n"
    }
    "Password:" {
        send "$pin\n"
        expect "password:"
        send "$passwd\n"
    }
    "password:" {
        send "$passwd\n"
    }
}
interact

运行 ./test.sh命令,一键登陆成功!

简单的几个命令,,搭配起来解决了与命令行的交互问题后,很多复杂的功能也不在话下了~

expect脚本的一些注意事项

  1. 可能不支持一些基本的shell命令,例如echo, 可能是因为expect解释器支持的命令有限,一般的做法是会有一个主shell文件使用bash等主流解释器,在主shell文件中调用一下对应的expect脚本,就可以用到bash命令了
  2. expect字段后面的参数支持正则匹配,并且默认区分大小写
  3. 使用ssh登录功能的时候最后的interect是不可少的,不然进不去子shell,会直接执行完毕退出

参考

  1. ubuntu/linux运行shell脚本sudo自动输入密码(亲测可以)
  2. 脚本链接 ssh 自动输入密码
  3. shell实现SSH自动登陆

本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 (CC BY-NC-ND 4.0) 进行许可。