Shell 编程之免交互

Shell 编程之免交互

  • [一、Here Document 免交互](#一、Here Document 免交互)
    • [Here Document 概述](#Here Document 概述)
    • [Here Document 免交互](#Here Document 免交互)
    • [Here Document 变量设定](#Here Document 变量设定)
    • [Here Document 格式控制](#Here Document 格式控制)
    • [Here Document 多行注释](#Here Document 多行注释)
  • [二、expect 免交互](#二、expect 免交互)
    • [expect 概述](#expect 概述)
    • [except 安装](#except 安装)
    • 基本命令介绍
    • [expect 语法](#expect 语法)
    • [expect 执行方式](#expect 执行方式)
    • [expect 案例](#expect 案例)
  • 三、总结

在Shell脚本编程中,我们经常需要处理与用户的交互,如读取用户输入、设置密码等。然而,在自动化任务或脚本执行过程中,与用户的实时交互可能会变得繁琐甚至不可能。为了解决这个问题,Shell提供了免交互的方法,使得脚本能够在没有用户直接参与的情况下执行。本文将重点介绍Shell中的免交互技术,特别是Here Document和Expect工具的使用。

一、Here Document 免交互

Here Document 概述

  • Here Document 是一个特殊用途的代码块。
  • 它在 Linux Shell 中使用 I/O 重定向的方式将命令列表提供给交互式程序或命令,比如 ftp、cat 或 read 命令。
  • Here Document 是标准输入的一种替代品,可以帮助脚本开发人员不必使用临时文件来构建输入信息,而是直接就地生产出一个文件并用作命令的标准输入。
python 复制代码
命令 <<标记
...
内容
...
#标记之间是传入内容
标记
  • 特殊字符"<<"在标记和命令之前,这样做的目的是将命令块的输出重定向到程序或命令的 stdin。

  • 标记的选择要确保不会出现在其他地方,避免出现混淆;两个标记之间的内容被当做是一个文件并用作"命令"的标准输入。

  • 另外 Here Document 也可以与非交互式程序和命令一起使用。

  • 在实际使用过程中,有四点需要注意。

    • 标记可以使用任意的合法字符;
    • 结尾的标记一定要顶格写,前面不能有任何字符;
    • 结尾的标记后面也不能有任何字符(包括空格);
    • 开头的标记前后的空格会被省略掉。

Here Document 免交互

通过 read 命令接收输入并打印

python 复制代码
[root@bogon ~]# cat test.sh 
#!/bin/bash 
read i << EOF 
Hi
EOF
echo $i
[root@bogon ~]# . test.sh
Hi

通过 passwd 给用户设置密码

python 复制代码
[root@bogon ~]# cat test.sh 
#!/bin/bash

passwd zhangsan << EOF
123        # 这两行是输入的密码和确认密码
123
EOF
[root@bogon ~]# . test.sh 
更改用户 zhangsan 的密码 。
新的 密码:无效的密码: 密码少于 8 个字符
重新输入新的 密码:passwd:所有的身份验证令牌已经成功更新。

实现自动化挂载分区

python 复制代码
[root@bogon ~]# cat test.sh 
#!/bin/bash

fdisk /dev/sdb << EOF
n        # 创建分区,此处回车 4 次,保险起见回车 5 次





w        # 保险起见回车 2 次

 
EOF
mkfs.xfs /dev/sdb1        # 格式化
mkdir /kgc            # 创建 kgc 目录
cat >> /etc/fstab << EOF
/dev/sdb1 /kgc xfs defaults     0 0
EOF
mount -a        # 扫描
[root@bogon ~]# . test.sh 
欢迎使用 fdisk (util-linux 2.23.2)。

更改将停留在内存中,直到您决定将更改写入磁盘。
使用写入命令前请三思。

Device does not contain a recognized partition table
使用磁盘标识符 0x70afb6f5 创建新的 DOS 磁盘标签。

命令(输入 m 获取帮助):Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): Using default response p
分区号 (1-4,默认 1):起始 扇区 (2048-41943039,默认为 2048):将使用默认值 2048
Last 扇区, +扇区 or +size{K,M,G} (2048-41943039,默认为 41943039):将使用默认值 41943039
分区 1 已设置为 Linux 类型,大小设为 20 GiB

命令(输入 m 获取帮助):命令(输入 m 获取帮助):The partition table has been altered!

Calling ioctl() to re-read partition table.
正在同步磁盘。
meta-data=/dev/sdb1              isize=512    agcount=4, agsize=1310656 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=0, sparse=0
data     =                       bsize=4096   blocks=5242624, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0 ftype=1
log      =internal log           bsize=4096   blocks=2560, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0

Here Document 变量设定

  • Here Document 也支持使用变量,如果标记之间有变量被使用,会先替换变量值。
  • 如果想要将一些内容写入文件,除了常规的方法外,也可以使用 Here Document。
  • 如果写入的内容中包含变量,在写入文件时要先将变量替换成实际值,在结合 cat 命令完成写入
python 复制代码
[root@localhost ~]# vi test.sh
#!/bin/bash 
doc_file="test.txt" 
i="company"
cat > $doc_file << HERE 
Take him from home to $i 
HERE
[root@localhost ~]# chmod +x test.sh 
[root@localhost ~]# ./test.sh 
[root@localhost ~]# cat test.txt
Take him from home to company
# 在上述执行的过程中,标记内变量 i 的值被替换成了"company",最终结果输出到 $doc_file 内,其值为 test.txt。
[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 显示出来,其结果放置到$()中,最后得到上述结果

Here Document 格式控制

(1) 关闭变量替换的功能。

  • 关闭变量替换的功能,就是希望按照字符原本的样子输出,不做任何修改或替换。
python 复制代码
[root@localhost ~]# cat here_format_shut.sh
#!/bin/bash
cat <<'EOF'        # 对标记加单引号,即可关闭变量替换
This is Line 1.
$kgc 
EOF
[root@localhost ~]# sh here_format_shut.sh
This is Line 1.
$kgc    # $kgc 没有发生改变,不做变量替换

(2) 去掉每行之前的 TAB 字符。

  • 本示例的标记内,每行都有一个 TAB 字符。在第一行的标记前面加'-',这个表示要抑制各行首 TAB 的作用
python 复制代码
[root@localhost ~]# vim here_format_tab.sh
#!/bin/bash 
cat <<-'EOF'
        This is Line 1.
        $kgc
EOF
[root@localhost ~]# sh here_format_tab.sh
This is Line 1.
$kgc    # 输出结果同上一示例

Here Document 多行注释

  • Bash 的默认注释是"#",该注释方法只支持单行注释,
  • 在 Shell 脚本的工作中,"#"右侧的任何字符串,bash 都会将其忽略。
  • Here Document 的引入解决了多行注释的问题,其语法格式如下。
python 复制代码
: << DO-NOTHING
第一行注释第二行注释
......
DO-NOTHING

示例

python 复制代码
[root@localhost ~]# vi test.sh
#!/bin/bash

: <<EOF        # 多行注释the first comment.
the second comment.
test line. 
EOF
echo "exec string."
[root@localhost ~]# sh test.sh
exec string.

二、expect 免交互

expect 概述

  • expect 是建立在 tcl 语言基础上的一个工具,它可以让一些需要交互的任务自动化地完成,相当于模拟了用户和命令行的交互操作。
  • expect 是用来进行自动化控制和测试的工具。
  • 主要解决 shell 脚本中不可交互的问题。
  • 对于大规模的 Linux 运维很有帮助。

except 安装

  • Linux 系统自身并没有安装 expect 和 tcl,需要手动安装。
  • CentOS 7.3 光盘中默认包含expect 安装包,所以需要先挂载光盘,制作本地 yum 仓库,然后通过 yum 安装 expect。
  • 安装过程中,yum 会自动安装 expect 的依赖软件 tcl。具体安装步骤如下
python 复制代码
expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ]

(1) 挂载光盘

python 复制代码
[root@bogon ~]# mount /dev/sr0 /media

(2) 制作本地 YUM 源

python 复制代码
[root@bogon ~]# vi /etc/yum.repos.d/local.repo
name=localrepo 
baseurl=file:///media 
gpgcheck=0

(3) 执行安装命令

python 复制代码
[root@bogon ~]# yum -y install expect
# 安装完之后,可以通过 rpm -qa 检查一下是否安装成功
[root@bogon ~]# rpm -qa|grep expect
expect-5.45-14.el7_1.x86_64
[root@bogon ~]# rpm -qa|grep tcl
tcl-8.5.13-8.el7.x86_64

基本命令介绍

(1) 脚本解释器

  • expect 脚本中首先引入文件,表明使用的是哪一个 shell。
python 复制代码
# 查看 expect 是哪个目录
[root@bogon ~]# which expect
/usr/bin/expect
#!/usr/bin/expect

(2) expect/send

  • 判断上次输出结果中是否包含指定的字符串,如果有则立即返回,否则就等待超时时间后返回;
  • 只能捕捉有swpan启动的进程输出;
  • 用于接受命令执行后的输出,然后和期望的字符串匹配
  • 向进程发送字符串,用于模拟用户的输入:该命令不能自动回车换行,一般要加 \r (回车) 或者\ n
python 复制代码
# 方式一
expect "$case1" {send "$respond1\r"}
# 方式二
expect "$case1" 
send "$response1\r"
# 方式三
# expect 支持多个分支。
expect
{
    "$case1" {send "$response1\r"} 
    "$case2" {send "$response2\r"} 
    "$case3" {send "$response3\r"}
}

(3) spawn

  • spawn 后面通常跟一个Linux执行命令,表示开启一个会话、进程,并跟踪后续交互信息
python 复制代码
spawn Linux 执行命令
# 跟踪切换用户的交互信息
spawn su root

(4) 结束符

  • expect eof :等待执行结束,若没有这一句,可能导致命令还没执行,脚本就结束了
  • interact : 执行完成后保持交互状态, 把控制权交给控制台,这时可以手动输入信息。
  • 需要注意的是,expect eof 与 interact 只能二选一。
    (5) set
  • expect 默认的超时时间是10秒,通过set 命令可以设置会话超时时间,若不限制超时时间则应设置为-1
python 复制代码
# 将超时时间设置为 30 秒
set timeout 30

(6) exp_continue

  • exp_ continue 附加于某个expect 判断项之后,可以使该项被匹配后,还能继续匹配该expect判断语句内的其他项
  • exp_ continue 类似于控制语句中的continue 语句。
  • 表示允许expect 继续向下执行指令

(7) send_user

  • send_ user 表示回显命令,相当于echo

(8) 接收参数

  • expect脚本可以接受从bash命令行传递的参数,使用[lindex sargv n]获得。
  • 其中n从0开始,分别表示第一个 ,第二个,第三个...参数
python 复制代码
set param0 [lindex $argv 0]
# $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 语法

语法结构

(1) 单一分支语法

  • 默认情况下,send 不会向标准输入发送回车键,所以需要通过\r 手动换行
python 复制代码
expect "password:" {send "mypassword\r";}

(2) 多分支模式语法

python 复制代码
# 只要匹配了 aaa、bbb 或 ccc 中的任何一个,就执行相应的 send 语句,然后退出该 expect 语句
expect
{
    "aaa"  {send "AAA\r"}
    "bbb"  {send "BBB\r"}
    "ccc"  {send "CCC\r"}
}

# exp_continue 表示继续后面的匹配,假如配了 aaa,执行完 send 语句后还要继续向下匹配bbb。
expect
{
    "aaa" {send "AAA";exp_continue} 
    "bbb" {send "BBB";exp_continue } 
    "ccc" {send "CCC"}
}

expect 执行方式

(1) 直接执行

python 复制代码
[root@bogon ~]# cat test.sh 
#!/usr/bin/expect 
set timeout 60 
log_file test.log 
log_user 1
set hostname [lindex $argv 0] 
set password [lindex $argv 1] 
spawn ssh root@${hostname} 
expect {
    "(yes/no)"
        {send "yes\r"; exp_continue} 
        "*password"
        {send "$password\r"}
}
interact
[root@bogon ~]# chmod +x direct.sh 

[root@bogon ~]# ./direct.sh 192.168.72.145 123    # 参数为主机 ip 和密码
spawn ssh root@192.168.72.145
root@192.168.72.145's password: 
Last login: Fri Jun 21 10:54:18 2024 from 192.168.72.154
[root@bogon ~]# exit
登出
Connection to 192.168.72.145 closed.

(2) 嵌入执行

python 复制代码
[root@localhost ~]# more implant.sh
#!/bin/bash 
hostname=$1 
password=$2
/usr/bin/expect<<-EOF        # expect 开始标志
spawn ssh root@${hostname} 
expect {
    "(yes/no)"
    {send "yes\r";exp_continue} 
    "*password"
    {send "$password\r"}
}
expect "*]#" 
send "exit\r" 
expect eof
EOF           # expect 结束标志,EOF 前后不能有空格
[root@localhost  ~]# sh  implant.sh 192.168.72.145 123456    # 参数为主机 ip 和密码

expect 案例

创建用户并设置密码

python 复制代码
#!/bin/bash 
user=$1 
password=$2 
useradd $user 
expect << EOF 
# 开启一个进程跟踪 passwd 命令,expect 只能捕捉该进程信息
spawn passwd $user 
# 匹配输出信息"New password:" 
expect "New password:"
# 自动输入密码 
send "${password}\r"
# 匹配输出信息"Retype new password:"
expect "Retype new password:" 
# 自动输入密码
send "${password}\r" 
# 等待结束标记
expect eof; 
EOF

实现 ssh 自动登录

python 复制代码
#!/usr/bin/expect
# expect 将超时时间设置为 60 秒
set timeout 60
# 记录日志文件
log_file test.log
# 开启控制台输出,为0时,控制台不输出
log_user 1

# $argc 表示参数个数
if {$argc<1} { 
# send_user相当于shell的echo
send_user "usage: $argv0 <param1><param2> ... " 
exit 
}

set hostname [lindex $argv 0]
set password [lindex $argv 1]
# spawn 后面通常跟一个命令,表示开启一个会话、启动进程,并跟踪后续交互信息
spawn ssh root@${hostname}
expect {
  "(yes/no)"
  # 匹配的(yes/no)时自动输入yes,exp_continue 表示允许 expect 继续向下执行指令
  {send "yes\r"; exp_continue}
  "*password"
  {send "$password\r"}
}

expect "*#" {send "ifconfig\r"}

# expect eof :等待执行结束,若没有这一句,可能导致命令还没执行,脚本就结束了
# interact : 执行完成后保持交互状态, 把控制权交给控制台,这时可以手动输入信息。 需要注意的是,expect eof 与 interact 只能二选一。
interact

利用 expect 完成 FTP 登录过程

python 复制代码
#!/usr/bin/expect -f 
set timeout 10
spawn ftp 192.168.72.145 
expect "Name*"
send "ftp\r"
expect "Password:*" 
send "\r"
expect "ftp>*" 
interact 
expect eof

根据这些参数登录到目标主机执行指定的shell命令

python 复制代码
[root@localhost ~]# cat zuoye.sh 
#!/bin/bash
main() {
    if [ $# -ge 5 ];then
    check-ip-survival $1
        if [ $? -eq 0 ];then
            echo "$*"
            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 将超时时间设置为 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 "$commend\r"}
        send "exit\r"
        expect eof
EOF
}
main $@
exit
# 添加可执行权限
[root@localhost ~]# chmod +x zuoye.sh 
[root@localhost ~]# ./zuoye.sh 192.168.72.145 root 200596 22 ls
正在检查IP地址192.168.72.145...
IP地址192.168.72.145正在连接...
192.168.72.145 root 200596 22 ls
spawn ssh root@192.168.72.145 -p 22
root@192.168.72.145's password: 
Last login: Sun Jun 23 15:33:16 2024 from 192.168.72.154
[root@localhost ~]#  ls
anaconda-ks.cfg
[root@localhost ~]# exit
登出
Connection to 192.168.72.145 closed.

三、总结

通过Here Document和Expect工具,我们可以轻松实现Shell脚本的免交互执行。这些技术不仅提高了脚本的自动化程度,还使得脚本的编写和维护变得更加简单和高效。当然,在实际应用中,我们还需要根据具体需求选择合适的技术,并结合其他Shell编程技巧来实现更复杂的功能。

相关推荐
TPBoreas1 小时前
清理服务器日志空间
linux·运维·服务器
Howrun7772 小时前
Linux进程通信---1---匿名管道
linux
天骄t2 小时前
HTML入门:从基础结构到表单实战
linux·数据库
大聪明-PLUS2 小时前
了解 Linux 系统中用于流量管理的 libnl 库
linux·嵌入式·arm·smarc
食咗未2 小时前
Linux USB HOST EXTERNAL VIRTUAL COM PORT
linux·驱动开发
没有啥的昵称2 小时前
linux下用QLibrary载入动态库
linux·qt
飞Link2 小时前
【CentOS】Linux(CentOS7)安装教程
linux·运维·服务器·centos
知识分享小能手2 小时前
Ubuntu入门学习教程,从入门到精通,Ubuntu 22.04中的过滤器知识点详解(13)
linux·学习·ubuntu
牛奔2 小时前
Linux 的日志分析命令
linux·运维·服务器·python·excel
飞Link3 小时前
【Linux】Linux(CentOS7)配置SSH免密登录
linux·运维·服务器