【Day 42】Shell-expect和sed

一、expect

(一)简单介绍

**定义:**expect 是一款专门用于处理交互式命令的工具,其核心作用是通过预先定义规则,自动捕获命令的交互提示并作出响应,从而实现交互式操作的自动化。

  • yum install -y expect // 安装

**命令:**在expect脚本中,spawn、expect、send是三个核心命令,三者配合可实现对交互式命令的自动化控制。以下是它们的详细介绍:

1、spawn:启动交互进程

**作用:**启动一个子进程(通常是需要交互的命令,如ssh、passwd、ftp等),expect只能对通过spawn启动的进程进行后续的交互控制(捕获提示、发送响应)。

  • spawn 命令 [参数]
  • spawn ssh root@192.168.1.1# 启动ssh连接进程
  • spawn passwd user # 启动修改用户密码的进程

spawn是expect控制交互的 "入口",所有需要自动响应的命令必须通过spawn启动,否则expect无法捕获其输出。启动后,进程的输出(如提示信息)会被expect监控。

2、expect:等待交互提示

**作用:**监控spawn启动的进程输出,等待匹配指定的 "交互提示字符串"(如密码提示"password:"、确认提示"(yes/no)?"等)。当匹配到目标字符串时,脚本会继续执行后续命令。

bash 复制代码
# 基本用法:等待单个提示
expect "目标提示字符串"

# 高级用法:多分支匹配(匹配多个可能的提示,执行对应操作)
expect {
    "提示1" { 操作1; exp_continue }  # exp_continue表示继续等待其他提示
    "提示2" { 操作2 }
    timeout { 超时操作 }            # 超过预设timeout未匹配时执行
}

// 支持正则表达式匹配(如"password:.*"匹配以password:开头的任意字符串)。

示例:

bash 复制代码
spawn ssh root@192.168.1.1
# 多分支匹配:首次连接的确认提示 或 密码提示
expect {
    "Are you sure you want to continue connecting (yes/no)?" {
        send "yes\n"
        exp_continue  # 发送yes后,继续等待后续的密码提示
    }
    "password:" {
        send "123456\n"  # 匹配到密码提示时发送密码
    }
    timeout {
        puts "连接超时!"  # 10秒未匹配到任何提示时执行
        exit 1
    }
}

3、send:自动发送响应内容

**作用:**当expect匹配到目标提示后,send用于向spawn启动的进程发送指定内容。

  • send "需要发送的内容\n" # \n表示回车,模拟人工输入后按回车

// 必须在expect匹配到提示后使用,否则发送的内容可能 "时机不对"(如在提示出现前发送,导致无效)。

// 结尾的\n通常不可省略,因为大多数交互式命令需要 "回车" 确认输入(如输入密码后需按回车)。

示例

bash 复制代码
expect "New password:"    # 等待"输入新密码"提示
send "redhat\n"           # 发送新密码"redhat"并回车
expect "Retype new password:"  # 等待"确认密码"提示
send "redhat\n"                # 发送确认密码并回车

4.三者配合流程

一个典型的expect自动化交互流程是:

  1. spawn启动交互命令(如ssh);
  2. expect等待命令输出的交互提示(如"password:");
  3. send向命令发送预设的响应内容(如密码);
  4. 重复步骤 2-3,直到完成所有交互;
  5. (可选)expect eof等待命令执行结束。

(例)自动修改 sylvia 用户密码为Sylvia@548165

bash 复制代码
#!/usr/bin/expect
set timeout 10
spawn passwd Sylvia
expect "New password:"
send "Sylvia@548165\n"
expect "Retype new password:"
send "Sylvia@548165\n"
expect eof

(2)自动登录 SSH 的完整流程:

bash 复制代码
#!/usr/bin/expect
set timeout 10
spawn ssh root@192.168.1.1  # 1. 启动ssh进程
expect "password:"          # 2. 等待密码提示
send "123456\n"             # 3. 发送密码
expect "#"                  # 2. 等待登录后的命令提示符(如root@host#)
send "ls -l\n"              # 3. 发送命令(如查看目录)
expect eof                  # 等待命令执行结束

(二)使用 Expect 实现 SSH 免密登录自动化配置

本脚本的核心功能是:自动为当前主机配置对多台目标主机的 SSH 免密登录,主要包含以下步骤:

  1. 清理旧密钥(避免冲突)
  2. 生成新的 SSH 密钥对
  3. 批量向目标主机分发公钥(自动处理交互确认)
  4. 验证免密登录是否成功
bash 复制代码
#!/bin/bash
# 清理旧密钥(可选)
if [ -f "$HOME/.ssh/id_rsa" ]; then
    rm -f "$HOME/.ssh/id_rsa" "$HOME/.ssh/id_rsa.pub"
fi
# 生成新的SSH密钥对(无密码) -t密钥类型 -f私钥存储路径 -P密钥的密码
echo "012"
ssh-keygen -t rsa -f "$HOME/.ssh/id_rsa" -P "" &> /dev/null
echo "123"
# 清空known_hosts避免主机密钥变化导致的问题
> ~/.ssh/known_hosts
echo "234"
for i in 11 12; do
    /usr/bin/expect << EOF
set timeout 10
spawn ssh-copy-id root@192.168.140.$i
expect "(yes/no)?"
send "yes\n"
expect "password:"
send "548165\n"
expect eof
EOF
done
echo  "345"
# 验证免密登录是否成功
echo  "----------------------------------------"
echo "开始验证免密登录..."
for i in 11 12; do
    ssh root@192.168.140.$i " date"
done
echo -e "\n所有操作完成!"
bash 复制代码
#!/bin/bash
# 在 Shell 脚本中,$HOME 是一个预定义的环境变量,它指向当前用户的主目录(家目录)可直接使用
KEY_FILE="$HOME/.ssh/id_rsa"
TARGET_IPS=(192.168.140.10)
SSH_USER="root"                              # SSH登录用户名
SSH_PASS="548165634"                            # 目标主机密码
SSH_PORT="12345"
if ! command -v expect &> /dev/null; then
    echo "错误:未安装expect工具,正在尝试安装..."
    yum install -y expect &> /dev/null || {
        echo "安装失败,请手动执行:yum install -y expect";
        exit 1;
    }
else
    echo "已安装!"
fi
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo ".ssh目录存在!"
if [ -f "$KEY_FILE" ]; then
    echo "发现旧密钥,正在清理..."
    rm -f "$KEY_FILE" "$KEY_FILE.pub"
fi
echo "正在生成SSH密钥对..."
ssh-keygen -t rsa -f "$KEY_FILE" -P "" &> /dev/null
> ~/.ssh/known_hosts
echo "开始向目标主机分发公钥..."
for ip in "${TARGET_IPS[@]}"; do
    echo "正在处理主机:$ip"   

 # Bash 的here 文档语法,用于在 Bash 脚本中直接嵌入 Expect 脚本内容。
   /usr/bin/expect << EOF  
        set timeout 10
        spawn ssh-copy-id ${SSH_USER}@$ip -p${SSH_PORT}
        expect {
            "Are you sure you want to continue connecting (yes/no)?" {
                send "yes\n"
                exp_continue 
            }
            "password:" {
                send "$SSH_PASS\n"
            }
            timeout {
                puts "错误:连接$ip超时"
                exit 1
            }
        }
        expect eof
EOF
done
echo -e "\n----------------------------------------"
echo "开始验证免密登录..."
for ip in "${TARGET_IPS[@]}"; do
    echo -n "验证$ip:"
    ssh ${SSH_USER}@$ip -p${SSH_PORT} "echo 连接成功" &> /dev/null
    if [ $? -eq 0 ]; then
        echo "成功"
    else
        echo "失败"
    fi
done

echo -e "\n所有操作完成!"

二、sed 文本编辑工具

(一)介绍

**作用:**sed(Stream Editor,流编辑器)是一款命令行文本处理工具,主要用于对文本进行批量修改、过滤、替换等操作。

核心特点:

  • 按行处理文本(逐行读取、处理、输出);
  • 默认仅在内存中处理数据,不修改原文件(需通过-i选项强制修改);
  • 支持正则表达式,可实现复杂的文本匹配与处理。

使用格式:

  • sed [选项] '行选择 操作' 目标文件

| 选项 | 说明 | 示例 |
| -i[备份后缀] | 直接修改原文件,指定后缀则创建备份 | sed -i.bak 's/old/new/' file (修改 file 并生成 file.bak) |
| -r | 支持扩展正则表达式,+,?,{} | `sed -r 's/(ab)/c/g' file`(替换 a 或 b 为 c) |
| -n | 取消默认输出,仅显示匹配内容 | sed -n '3p' file(仅显示第 3 行) |
| -e | 执行多个操作(按顺序) | sed -e '1d' -e '/^$/d' file(先删第 1 行,再删空行) |
| --follow-symlinks | 处理软链接指向的原文件 | sed -ri --follow-symlinks 's/old/new/' /tmp/link |

-f 脚本文件 从文件读取操作命令 sed -f script.sed file(执行 script.sed 中的命令)

行选择规则(指定对哪些行操作,省略则默认所有行):

格式 说明 示例
行号 单个行号(如第 3 行) '3d'(删除第 3 行)
起始行号,终止行号 连续行范围(如第 2 到第 5 行) '2,5p'(显示第 2-5 行)
/ 正则表达式 / 匹配正则的行 '/^#/d'(删除注释行)
/ 正则 1/,/ 正则 2/ 匹配正则 1 中的行中找到匹配正则 2 的行 '/^start/,/^end/d'(删除指定区间行)

(二)sed 常用操作

1、删除整行

删除匹配行,语法:'行选择d'

// 并非在文件中删除,只在显示时删掉,加 -i会在原文件中删除。

bash 复制代码
# 删除df -hT输出的第1行(标题行)
df -hT | sed '1d'
# 删除/etc/fstab中的所有空行(^$匹配空行)
sed '/^$/d' /etc/fstab
# 删除包含tmpfs的行
df -hT | sed '/tmpfs/d'

2、显示整行

显示匹配行,需配合-n选项(取消默认输出,仅显示匹配内容),语法:-n '行选择 p'

  • 默认显示:sed 不使用 -n 时,自动输出所有行(处理前 / 处理后)。
  • 重复显示:在默认显示的基础上使用 p 操作,会导致匹配行被输出两次。sed按输入文本的行顺序逐行处理,输出顺序与原文件一致。
  • 控制显示 :加 -n 选项可取消默认显示,仅通过 p 操作显式输出需要的内容。
bash 复制代码
# 显示/etc/hosts的第2行
sed -n '2p' /etc/hosts
# 显示/etc/passwd中以root开头的行
sed -n '/^root/p' /etc/passwd

3、统计行数

输出指定行的行号,结合$(最后一行)可统计总行数,语法:-n '行选择 ='

bash 复制代码
# 统计/etc/passwd的总行数($表示最后一行,=输出行号)
sed -n '$=' /etc/passwd
# 查找以 etc 开头的行,并输出这些行的行号
sed -n '/^etc/=' /etc/passwd

4、整行替换

将匹配行整体替换为新内容,语法:'行选择 c \新内容'

bash 复制代码
# 将ssh配置文件中含#Port的行替换为Port 44444(-i直接修改原文件)
sed -i '/#Port/c \Port 44444' /etc/ssh/sshd_config

5、追加内容

在匹配行后追加新内容,语法:'行选择 a \新内容'

bash 复制代码
# 在/etc/profile最后一行后追加JAVA_HOME配置
sed '$a \export JAVA_HOME=/opt/jdk' /etc/profile

# 在/etc/hosts中含127.0.0.1的行后追加主机解析
sed '/127.0.0.1/a \192.168.140.10    node01.linux.com' /etc/hosts

6、插入内容

在匹配行前插入新内容(补充操作),语法:'行选择 i \新内容'

bash 复制代码
# 在/etc/profile第1行前插入注释说明
sed '1i \# 系统环境变量配置文件' /etc/profile

7、查找替换

替换行内部分内容,核心语法:'行选择 s/旧内容/新内容/[选项]'

  • 选项g:全局替换(默认仅替换第一个匹配);

  • 选项i:不区分大小写

  • 分隔符可自定义(如|),避免与内容中的/冲突。

  • 新内容为空白即可删掉旧内容

bash 复制代码
# 将/etc/fstab第4行的所有数字替换为!
sed '4s/[0-9]/!/g' /etc/fstab
# 将/etc/fstab中所有/替换为!(用|作为分隔符)
sed 's|/|!|g' /etc/fstab
# 移除行首的#和可能的空格(-r支持扩展正则,?表示0或1个空格)
sed -r 's|^#[[:space:]]?||' /etc/fstab

三、练习解答

1、删除 df -hT的首行及所有含tmpfs的行

bash 复制代码
df -hT | sed '1d; /tmpfs/d'  # 用;分隔多个操作

2、修改 ssh 配置文件(/etc/ssh/sshd_config)

bash 复制代码
# 修改端口为55555
sed -i '/^Port/c \Port 55555' /etc/ssh/sshd_config
# 关闭DNS反解(将#UseDNS yes改为UseDNS no)
sed -i '/^#UseDNS/c \UseDNS no' /etc/ssh/sshd_config
# 禁止root远程登录(将#PermitRootLogin yes改为PermitRootLogin no)
sed -i '/^#PermitRootLogin/c \PermitRootLogin no' /etc/ssh/sshd_config
systemctl restart sshd.servi

3、修改 selinux 配置(/etc/selinux/config)

bash 复制代码
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
grep SELINUX= /etc/selinux/config    # 查看是否添加
reboot       # 重启
getenforce   # 验证

4、安装 httpd 并修改默认端口为 8080

bash 复制代码
# 安装(以CentOS为例)
yum install -y httpd

# 修改端口(httpd配置文件通常为/etc/httpd/conf/httpd.conf)
sed -i 's/^Listen 80/Listen 8080/' /etc/httpd/conf/httpd.conf
systemctl restart httpd

5、在 /etc/hosts 中添加主机名解析

bash 复制代码
# 在文件最后追加解析(IP和主机名根据实际修改)
sed -i '$a \192.168.1.101    webserver' /etc/hosts
yum install -y bind bind-utils
ping node01.linux.com 
# 输出 PING node01.linux.com (192.168.140.40) 56(84) bytes of data.

6、从 /var/log/messages 获取指定时间范围的日志

假设日志格式含时间(如2023-10-01 08:00:00):

bash 复制代码
sed -n '/^Sep [ ]*2 16:12:[3-5][0-9]/p; /^Sep [ ]*2 16:13:00/p' /var/log/messages
# 输出示例:Sep  2 16:12:40 ca named[1149]: network unreachable resolving 'csdn.net/DS/IN': 2001:501:b1f9::30#53

7、创建文件并注释第 2、4 行

bash 复制代码
# 创建文件并写入4行内容
cat > test.txt << EOF
line1
line2
line3
line4
EOF

# 注释第2、4行(行首加#)
sed -i '2s/^/#/; 4s/^/#/' test.txt

8、显示 /etc/passwd 第 5 行内容

bash 复制代码
sed -n '5p' /etc/passwd

9、删除历史命令前的所有空白字符

bash 复制代码
history | sed 's/^[[:space:]]*//'  # [[:space:]]*匹配0或多个空白

10、将文本中的 chaoyang/Chaoyang 替换为 "朝阳"

假设文件名为chaoyang.txt:

bash 复制代码
sed -i 's/[Cc]haoyang/朝阳/g' /chaoyang.txt  # [Cc]匹配大小写
sed -i 's/chaoyang/朝阳/i' /chaoyang.txt     # i 不区分大小写

11、计算办公人数总和(假设人数在第 4 列)

bash 复制代码
# 提取第4列并求和(结合awk更高效)
cat /chaoyang.txt | sed '1d'  # 跳过标题行
| awk '{sum += $4} END {print "总人数:" sum}'

#!/bin/bash
number=($(sed '1d'  /chaoyang.txt | awk '{print $4}'))
total=0
for num in "${number[@]}"; do
    if [[ "$num" =~ ^[0-9]+$ ]]; then
        let total+=num
    fi
done
echo "$total"

12、过滤出办公地点在朝阳的行

bash 复制代码
# 方法1:grep
grep -E '^[^[:space:]]+[[:space:]]+朝阳' /chaoyang.txt

# 方法2:sed
sed -n '/^[^[:space:]]\+[[:space:]]\+朝阳/p' /chaoyang.txt

#方法3:awk
awk '$2 == "朝阳"' /chaoyang.txt
相关推荐
xx.ii8 小时前
37.Ansible循环+常用过滤器
linux·运维·服务器·ansible
施努卡机器视觉8 小时前
SNK施努卡电机生产线全自动化
运维·自动化
ajassi20008 小时前
开源 C++ QT Widget 开发(九)图表--仪表盘
linux·c++·qt·开源
火山kim8 小时前
一文速通liunx命令
java·linux·运维·服务器
Orchestrator_me9 小时前
centos目录大小查看与清理
linux·运维·centos
huangyuchi.9 小时前
【Linux系统】万字解析,进程间的信号
linux·服务器·信号处理·信号产生·linux信号·信号保存·操作系统如何运行
Joy-鬼魅9 小时前
通过 FinalShell 访问服务器并运行 GUI 程序,提示 “Cannot connect to X server“ 的解决方法
运维·服务器
几何心凉9 小时前
云电脑是什么?与普通电脑的区别在哪里?——天翼云电脑体验推荐
运维·负载均衡
电气铺二表姐1377441661510 小时前
自发自用分布式光伏电站进线柜防逆流测控保护装置
运维·能源