Here Document
是一个特殊用途的代码块。它在 Linux Shell 中使用 I/O 重定向的方式将命令列表提供给交互式程序或命令,比如 ftp、cat 或 read 命令。Here Document 是标准输入的一种替代品,可以帮助脚本开发人员不必使用临时文件来构建输入信息,而是直接就地生产出一个文件并用作命令的标准输入
特殊字符“<<”在标记和命令之前,这样做的目的是将命令块的输出重定向到程序或命令的 stdin。标记的选择要确保不会出现在其他地方,避免出现混淆;两个标记之间的内容被当做是一个文件并用作“命令”的标准输入。另外 Here Document 也可以与非交互式程序和命令一起使用
基本语法
命令 << 标记
...
...
标记
注意事项
- 标记可以使用任意合法字符
- 结尾的标记一定要顶格写,前面不能有任何字符
- 结尾的标记后面也不能有任何字符(包括空格)
- 开头标记前后的空格会被省略掉
- 在第一行标记前‘-’,这个表示要抑制各行首 TAB 的作用
- 将<<后的标记用单引号引起来会使两个标记之间的特殊字符失去效果被当成普通字符
示例 配置Java环境变量
cat >> /etc/profile << 'EOF' //给/etc/profile内写入以下三行内容
export JAVA_HOME=/usr/local/java/
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
EOF
变量设定
Here Document 也支持使用变量,如果标记之间有变量被使用,会先替换变量值。如果想要将一些内容写入文件,除了常规的方法外,也可以使用 Here Document。如果写入的内容中包含变量,在写入文件时要先将变量替换成实际值,在结合 cat 命令完成写入
示例
[root@localhost ~]# vim here_var_replace.sh
#!/bin/bash
doc_file="2019.txt"
i="company"
cat > $doc_file << HERE
Take him from home to $i
HERE
[root@localhost ~]# chmod +x here_var_replace.sh
[root@localhost ~]# ./here_var_replace.sh
[root@localhost ~]# cat 2019.txt
Take him from home to company
在上述执行的过程中,标记内变量 i 的值被替换成了“company”,最终结果输出到
$doc_file 内,其值为 2019.txt。
除了变量替换,还可以使用 Here Document 来进行变量的设定。Here Document 不光可以将标记内容传给命令来执行,还可以将整体赋值给一个变量,然后通过 echo 命令将变量值打印出来
[root@localhost ~]# vim here_var_set.sh
#!/bin/bash ivar="Great! Beautyful!"
myvar=$(cat <<EOF //将Here Document 整体赋值给变量
This is Line 1.
That are Sun,Moon and Stars.
$ivar //输出时会进行变量替换
EOF
)
echo $myvar
[root@localhost ~]# sh here_var_set.sh
This is Line 1. That are Sun,Moon and Stars. Great! Beautyful!
在上述操作过程中,$ivar 先进行了替换,之后再转向输出,交由 cat 显示出来,其结果放置到$()中,最后得到上述结果
在脚本内批量注释
:<< 标记
注释内容
标记
expect 免交互
expect 概述
expect 是建立在 tcl 语言基础上的一个工具,它可以让一些需要交互的任务自动化地完成,相当于模拟了用户和命令行的交互操作。expect 是用来进行自动化控制和测试的工具。主要解决 shell 脚本中不可交互的问题。对于大规模的 Linux 运维很有帮助。
在 Linux 运维和开发中,经常需要远程登录服务器进行操作,登录的过程是一个交互的过程,可能会需要输入 yes/no, password 等信息。为了模拟这种输入,可以使用 expect 脚本。
在实际的生产环境中,有一个常用的场景就是批量配置集群无密钥登录。如果集群的机器数量很多,手动一台一台地去每台机器去配置无密钥是非常糟糕的事情。使用 expect 功能,可以远程登录机器,并通过交互方式进行无密钥登录
安装expect
yum -y install expect
脚本解释器
expect 脚本中首先引入文件,表明使用的是哪一个 shell。
#!/usr/bin/expect
expect/send
expect
- 判断上次输出结果中是否包含指定的字符串,如果有则立即返回,否则就等待超时时间后返回
- 只能捕捉由spawn启动的进程的输出
- ·用于接收命令执行后的输出,然后和期望的字符串匹配
send
- 向进程发送字符串,用于模拟用户的输入
- 该命令不能自动回车换行,一般要加(回车);回车使用\r表示
下述语法结构中$case1 代表检测命令的输出结果,如果输出内容和$case1 一致,通过send 命令模拟用户发送内容到终端
方式一
expect"$case1" {send "$respond1\r"}
方式二
expect "$case1"
send"$response1\r"
方式三
expect 支持多个分支。相当于if判断
expect
{
"$case1"{send "$response1\r"}
"$case2" {send"$response2\r"}
"$case3" {send "$response3\r"}
}
spawn
spawn 后面通常跟一个命令,表示开启一个会话、启动进程,并跟踪后续交互信息
语法结构
spawn Linux执行命令
例如,如果想要跟踪切换用户的交互信息,可以执行以下命令。
spawn su root
结束符
- expect eof :等待执行结束,若没有这一句,可能导致命令还没执行,脚本就结束了
- interact : 执行完成后保持交互状态, 把控制权交给控制台,这时可以手动输入信息
- 需要注意的是,expect eof 与 interact 只能二选一
超时时间
expect 默认的超时时间是 10 秒,通过 set 命令可以设置会话超时时间, 若不限制超时时间则应设置为-1。例如执行以下命令即可将超时时间设置为 30 秒
set timeout 30
exp_continue
exp_continue 表示允许 expect 继续向下执行指令
send_user
send_user 表示回显命令,相当于 echo。
接收参数
expect 脚本可以接受从 bash 传递的参数,使用[lindex $argv n]获得。其中 n 从 0 开始, 分别表示第一个,第二个,第三个参数
参数存在 argv 中,$argc 表示参数个数
if {$argc< 1} {
#do something
send_user "usage: $argv0 <param1><param2> ... " exit
}
在上述脚本中$argv0 是脚本名,但[lindex $argv 0]是第一个参数 param1, [lindex
$argv 1]是第二个参数 param2, 以此类推。send_user 用来显示信息到父进程(一般为用户的 shell)的标准输出
expect执行方式
直接执行
需要将默认命令解释器设置为#!/usr/bin/expect
示例
通过 SSH 方式登录远程服务器,需要输入用户名和密码,比较繁琐。如果服务器比较多,手动输入用户名和密码会耗费大量时间,expect 命令可以实现自动登录远程服务器, 并进入交互模式
#!/usr/bin/expect
# expect 将超时时间设置为 60 秒
set timeout 60
# 记录日志文件
log_file test.log
# 开启控制台输出,为0时,控制台不输出
log_user 1
spawn ssh root@192.168.78.129 -p 22
expect {
"(yes/no)?" { //匹配(yes/no)?
send "yes\r" //代替用户输入yes \r表示回车
exp_continue //代码继续向下执行
}
"*password:*" { //匹配*password:*
send "root\r" //代替用户输入密码
}
}
expect {
"*# " { send "ls -l\r" } //匹配以#号结尾的行,代替用户输入ls -l命令
"*\$ " { send "ls -l\r" }
}
expect {
"*# " { send "exit\r" } //匹配以#号结尾的行,代替用户输入exit命令
"*\$ " { send "exit\r" }
}
expect eof
嵌入执行
将expect嵌入到Shell脚本进行执行
在脚本中使用
expect << 标记
expect命令
标记
可以在脚本内嵌套expect
示例
通过 SSH 方式登录远程服务器,需要输入用户名和密码,比较繁琐。如果服务器比较多,手动输入用户名和密码会耗费大量时间,expect 命令可以实现自动登录远程服务器, 并进入交互模式
#!/bin/bash
main()
{
if [ $# -ge 5 ];then
check-ip-survival $1
if [ $? -eq 0 ];then
ssh-connection $@
fi
else
echo "请输入合适的参数:IP地址 用户名 密码 端口 需要执行的命令"
fi
}
check-ip-survival() {
local IP=$1
echo "正在检查IP地址$1..."
if [[ $IP =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]];then
#awk
local IPs=$(echo "$IP" | tr '.' ' ' )
local IP1=$(echo "$IPs" | awk '{print $1}')
local IP2=$(echo "$IPs" | awk '{print $2}')
local IP3=$(echo "$IPs" | awk '{print $3}')
local IP4=$(echo "$IPs" | awk '{print $4}')
if [ $IP1 -gt 0 ] && [ $IP1 -le 254 ] && [ $IP2 -le 254 ] && [ $IP3 -le 254 ] && [ $IP4 -le 254 ];
then
echo -e "IP地址$IP正在连接..."
else
echo -e "IP地址$IP不合法"
exit 1
fi
else
echo "IP地址${IP}不合法"
exit 1
fi
}
ssh-connection() {
local IP=$1
local name=$2
local password=$3
local prot=$4
for ((i=5;i<=$#;i++)) //循环获取到用户输入的命令拼接到一起
do
local single=$(echo "$@" | cut -d ' ' -f$i )
local commend+=$(echo " ${single}")
done
expect << EOF //嵌入执行expect
# expect 将超时时间设置为 60 秒
set timeout 60
# 记录日志文件
log_file test.log
# 开启控制台输出,为0时,控制台不输出
log_user 1
spawn ssh ${name}@${IP} -p ${prot}
expect {
"(yes/no)?" {
send "yes\r"
exp_continue
}
"*password:*" {
send "${password}\r"
}
}
expect {
"*# " { send "${cmd}\r" }
"*\$ " { send "${cmd}\r" }
}
expect {
"*# " { send "exit\r" }
"*\$ " { send "exit\r" }
}
expect eof
EOF
}
main $@
exit