在 Shell 脚本中进行交互的一些心得
我们在写 shell 脚本时经常会遇到一些需要交互的操作,比如修改某个文件,或是使用 yum install
ssh-keygen
certbot --nginx
等操作时,需要输入一些指令如 "y", "Enter" 和其他的一些信息。
我们写脚本就是为了自动操作,怎么可以等命令执行一会之后在按个回车进行下一步呢?既然我知道接下来要输入什么命令,我告诉你你帮我输入了不就得了?
聪明的我们想到了一些办法来避免这种无谓的等待,记录下来分享给大家
1. 自动化输入
1.1 输入单个指令
这里用 yum install
来进行演示 (假设不使用 -y
参数)
echo "y" | yum install wget
如果要求输入回车,可以使用 echo 指令的参数 -e
+ \n
进行操作
echo -e "\n" | yum remove wget
**`echo -e` 的小知识**
若字符串中出现以下字符,则特别加以处理,而不会将它当成一般文字输出:\a
发出警告声;\b
删除前一个字符;\c
最后不加上换行符号;\f
换行但光标仍旧停留在原来的位置;\v
与\f 相同;\n
换行且光标移至行首;\r
光标移至行首,但不换行;\t
插入 tab
符号;\\
插入 '' 字符;\nnn
插入 nnn(八进制)所代表的 ASCII 字符;
1.2 输入多行指令
输入多行指令我们需要借助输入重定向操作符 <<
以下面这个脚本为例
#!/bin/bash read -p "enter number:" no read -p "enter name:" name echo "you have entered $no, $name"
借助 <<
符号进行自动化输入
#!/bin/bash sh multi.sh << EOF 1 mutoe EOF
但是有时候这种方法并不生效,比如 ssh-keygen
命令,那只有借助强大的 expect
命令了
expect
进行交互
1.3 借助 在使用 expect
前需要进行安装,方法很简单,以 CentOS 为例,只需要运行 yum install -y expect
即可
expect 有两种用法,一种是直接写 expect 解释器的脚本,和 bash 类似,以 #!/usr/bin/expect
开头
下面是一个合格的 expect 脚本示例
#!/bin/expect set IP [lindex $argv 0] # 读取第1个参数设置为 IP 变量 set PASSWD [lindex $argv 1] # 读取第2个参数设置为 PASSWD 变量 set CMD [lindex $argv 2] # 读取第3个参数设置为 CMD 变量 spawn ssh $IP $CMD # spawn 来给命令加壳,以便于断言输出 expect { # expect 是断言命令 # 如果读取到屏幕上输出 (yes/no) 信息,则输入 "yes" 并按下回车键 # exp_continue 是继续等待花括号内的断言, 如果不加这一句会直接跳出 expect "(yes/no)?" { send "yes\r"; exp_continue } "password:" { send "$PASSWD\r" } # 如果读取到屏幕上输出 password 信息,则输入 PASSWD 变量中的内容 "*host " { exit 1 } # 如果读取到 "No route to host" 等内容, 就以非0状态退出 } expect eof # 等待命令执行结束
需要注意的是,在 expect 解释器内, 除了几个特定关键字的命令,其他命令都不可用,这种方式适用于执行命令较少,单次需要交互较多的自动化脚本
第二种用法是在 bash 脚本中执行 expect 配合重定向操作符, 在有大量脚本需要执行的情况下推荐使用该方式
下面是我在 certbot
命令时使用的 shell 脚本,以供参考
#!/bin/bash sudo expect << EOF spawn certbot --nginx expect { "Enter email address" { send "mutoe@foxmail.com\n";exp_continue} "Please read the Terms of Service" {send "A\n";exp_continue} "Would you be willing to share your email address" {send "N\n";exp_continue} "Which names would you like to activate HTTPS for" {send "\n";exp_continue} "You have an existing certificate that has exactly the same domains" {send "1\n";exp_continue} "Please choose whether or not to redirect HTTP traffic to HTTPS" {send "2\n";exp_continue} eof }
2. 修改文件
2.1 在文件中增加内容
我们想要将某个命令的输出写入到文件中进行保存,比如日志、新增一行配置,可以借助输出重定向符号 >
>>
来实现
who > log.txt # > 符号将会先清空 log.txt 然后以 who 的输出写入到文件中 echo "append" >> log.txt # >> 符号会追加字符串 "append" 到文件的尾行
那如果需要写入多行该怎么办呢?虽然也可以借助 echo -e
+ \n
来实现,但是我们还有其他更优雅的办法
借助 cat
命令配合 <<
符号也可以达到我们的目的
cat > log.txt << EOF 我有很多行 很多行 行 EOF
WHY?
cat
命令如果不接受任何参数,将会进入交互式界面,输入什么就会输出什么;
配合>
符号将cat
的输出写入到文件log.txt
中;
而<<
我们前面介绍过,会将多行输入重定向到前面的命令cat
中
2.2 修改文件的内容
关于修改文件的内容,如何在不借助 vim 等工具情况下进行呢?
有请我们强大的 sed
命令登场!!
sed [选项] 指令 文件
其中选项有
-n
忽略没有修改的内容-i
原地修改文件而不输出-r
拓展正则表达式
指令为一个字符串,由 2 个部分组成 条件
命令
,条件用于约束,指令进行操作
一个正常的替换命令长这样
sed -i "/^user/ s/nginx/mutoe/" /paht/to/nginx.conf
其中/^user/
为条件,查找以 "user" 开头的一行;s/nginx/mutoe/
为命令,s
是替换,这条命令意思是将 nginx
替换为 mutoe
sed 支持的命令有很多,约有 15 中,列举一些常见的命令
i
插入a
追加d
删除行c
替换行s
替换指定内容
关于
sed
的更多用法,可以参考我的 《Linux 学习笔记》 "sed 非交互式编辑" 部分
以下面的 nginx 配置片段为例
user nginx; worker_processes 1; http { include /etc/nginx/mime.types; default_type application/octet-stream; #gzip on; include /etc/nginx/conf.d/*.conf; } # delete me
首先我们将 nginx 的启动用户改为自定义用户
sed -i "1c user mutoe;" /path/to/nginx.conf
-i
意思是将输出 "in-placed" 直接替换文件内容而不是作为输出
"1c user mutoe;" 是指将第 1 行替换(c)为"user mutoe;"
接下来我们去除 #gzip on;
前的空格,难度在于我们并不知道改行配置位于哪一行,没关系,我们借助正则表达式先找到这一行
sed -i "/#gzip\s+on;/s/#//" /path/to/nginx.conf
最后我们删除文件的最后一行
sed -i "$d" /path/to/nginx.conf
好了,所有内容到这里就结束啦,如果看的不过瘾,这里有我在实际使用中的一些脚本,以供参考。
如果你有任何疑问或是支持,欢迎在下方留言。
参考资料
转自:https://github.com/mutoe/blog/blob/master/source/_posts/interactive-command-in-shell.md