【Linux Shell 编程基础学习与实践作业】

Linux Shell 编程基础学习与实践作业

一、前置学习内容

1.基础框架和权限

第一步:创建脚本文件

#创建目录(如果没有的话)

mkdir -p ~/shell_demo

#进入目录

cd ~/shell_demo

#用vim创建并编辑脚本文件(如果不会vim,也可以用nano,命令是nano first_script.sh)

vim first_script.sh

第二步:写入基础脚本内容

#!/bin/bash

#这是我的第一个Shell脚本(单行注释)

#功能:演示最基础的变量定义和输出

#定义变量(注意:等号前后不能有空格!)

greeting="你好,Shell编程!"

today="2026年1月18日"

#输出变量内容(用echo,引用变量加$)

echo "---------- 基础脚本演示 ----------"

echo g r e e t i n g e c h o " 今天的日期: greeting echo "今天的日期: greetingecho"今天的日期:today"

#也可以用 变量名的方式引用(更规范) e c h o " 再次输出: {变量名}的方式引用(更规范) echo "再次输出: 变量名的方式引用(更规范)echo"再次输出:{greeting}"

第三步:保存并退出 vim

• 按Esc键(确保退出编辑模式)

• 输入:wq(意思是保存(write)并退出(quit)),按回车

第四步:赋予执行权限并运行

#赋予脚本执行权限(+x就是添加可执行权限)

chmod +x first_script.sh

#运行脚本(用相对路径,当前目录下的脚本)

./first_script.sh

PS:

你可以把 Linux 系统想象成一个严格的图书馆 / 档案室:

• 你创建的first_script.sh文件,默认就是一张 "写满字的纸"(纯文本文件),系统只允许你「看(读权限 r)」、「改(写权限 w)」,但不允许你把这张纸当成 "指令单" 去执行;

• chmod +x 就相当于给这张纸盖了一个「"可执行" 公章」------ 告诉图书馆管理员(系统内核):"这张纸不是普通的笔记,是可以照做的指令清单,允许按上面的内容执行"。

第五步:输出结果

2. read 输入功能+ if 条件判断

步骤 1:创建新脚本 input_demo.sh

vim input_demo.sh

步骤 2:写入脚本内容

#!/bin/bash

#功能:演示read读取用户输入

#重点:-p 参数可以直接加提示语,不用单独echo

#用read读取用户输入的名字,-p 后面跟提示文字

read -p "请输入你的名字:" username

#输出欢迎语,拼接变量

echo "="
echo " 你好呀,${username}!"
echo " 恭喜你掌握了read输入功能~"
echo "="

#进阶:读取多个输入(比如名字+年龄)

read -p "再输入你的名字和年龄(用空格隔开):" name age

echo "你输入的名字: n a m e ,年龄: {name},年龄: name,年龄:{age}岁"

骤 3:保存退出 + 加权限 + 运行

按老规矩操作:

  1. Esc → :wq 保存退出 vim
  2. 加执行权限:chmod +x input_demo.sh
  3. 运行脚本:./input_demo.sh

步骤 4:结果

步骤 1:创建脚本 if_demo.sh

vim if_demo.sh

步骤 2:写入脚本内容

#!/bin/bash

#功能:演示if-else条件判断(数字判断+范围判断)

#第一步:读取用户输入的年龄

read -p "请输入你的年龄:" age

#第二步:先判断输入的是否是纯数字(新手必学的正则判断)

#[[ 表达式 ]] 是更友好的判断方式,=~ 是正则匹配

if ! [[ " a g e " = [ 0 − 9 ] + age" =~ ^[0-9]+ age"= [0−9]+ ]]; then

echo "❌ 错误!你输入的不是有效数字,请重新运行脚本输入数字。"

退出脚本,返回错误码1(表示执行失败)

exit 1

fi

#第三步:判断年龄范围(用 -lt(小于)、-ge(大于等于) 等数字判断符)

echo "✅ 你输入的年龄是: a g e 岁 " i f [ " age 岁" if [ " age岁"if["age" -lt 18 ]; then

echo "👉 你还是未成年人,要注意保护自己哦~"

elif [ "KaTeX parse error: Expected 'EOF', got '&' at position 15: age" -ge 18 ] &̲& [ "age" -lt 60 ]; then

echo "👉 你是成年人啦,要对自己的选择负责~"

else

echo "👉 你已经到了退休的年纪,好好享受生活吧~"

fi

PS:

fi 就是 if 的反向拼写,唯一作用:标记 if 条件判断结构的结束,必须和 if 成对出现。

步骤 3:保存退出 + 加权限 + 运行

• Esc → :wq 保存退出

• 加权限:chmod +x if_demo.sh

• 运行脚本,测试 4 种场景:

• 场景 1:输入非数字(比如abc)→ 报错提示

• 场景 2:输入 16 → 提示未成年

• 场景 3:输入 30 → 提示成年

• 场景 4:输入 65 → 提示退休

3. case判断-网络命令 + 文本过滤脚本

步骤 1:创建脚本 nic_status.sh

vim nic_status.sh

步骤 2:写入脚本内容

#!/bin/bash

#功能:网卡状态查询工具(case语句+网络命令实战)

#第一步:打印菜单,让用户选择操作

echo "==================== 网卡查询工具 "
echo "1. 查看所有网卡列表"
echo "2. 检查指定网卡是否启动"
echo "3. 退出"
echo "=================================="

read -p "请输入你的选择(1/2/3):" choice

#第二步:用case处理多值选择(适合固定选项的场景)

case "$choice" in

查所有网卡列表:ip link show 取第2列(网卡名),grep -v 排除回环网卡lo

echo -e "\n📋 系统所有网卡列表(排除回环网卡):"

ip link show | awk '{print $2}' | grep -v "lo:" | cut -d ":" -f1

;; # 每个分支结束必须加;;

复制代码
2)
    # 查指定网卡状态
    read -p "请输入要检查的网卡名(如ens33/eth0):" nic
    # 判断网卡是否存在:ip link show 网卡名 重定向输出到黑洞(不显示)
    if ip link show "$nic" > /dev/null 2>&1; then
        # 过滤网卡是否UP(grep -q 静默过滤,只返回结果不输出)
        if ip addr show "$nic" | grep -q "UP"; then
            echo -e "\n✅ 网卡$nic 状态:已启动(UP)"
        else
            echo -e "\n❌ 网卡$nic 状态:已关闭(DOWN)"
        fi
    else
        echo -e "\n❌ 错误:网卡$nic 不存在!"
    fi
    ;;

3)
    echo -e "\n👋 脚本退出,再见!"
    exit 0  # 正常退出
    ;;

*)  # 匹配所有未定义的输入(比如4/abc)
    echo -e "\n❌ 错误:输入无效,请输入1/2/3!"
    exit 1
    ;;

esac # case结束标记(case反过来)

步骤3:逐个分析

1:先看 ip link show

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 00:0c:29:xx:xx:xx brd ff:ff:ff:ff:ff:ff

ip link show | awk -F ':' '/^ [0-9]+:/ {print $2}' | grep -v "lo" | tr -d ' '

咱们拆解一下这个精准版:

  1. awk -F ':':把每一行按冒号 : 分割成多列。
  2. 2/^ [0-9]+:/:只匹配以 "数字 + 冒号" 开头的行(这些行才是网卡的定义行,比如 1: lo:、2: ens33:)。
  3. {print $2}:提取分割后的第二列,也就是网卡名(比如 lo、 ens33)。
  4. grep -v "lo":排除回环网卡。
  5. tr -d ' ':去掉网卡名前面的空格,得到干净的结果。
    ens33
    ens36
    virbr0
    virbr0-nic

1.先看开头:读取用户输入的网卡名

read -p "请输入要检查的网卡名(如 ens33/eth0):" nic

2.核心判断 1:网卡是否存在?

if ip link show "$nic" > /dev/null 2>&1; then

... 存在的逻辑...

else

echo -e "\n❌ 错误:网卡 $nic 不存在!"

fi

  1. 核心判断 2:网卡是否已启动(UP)?

if ip addr show "✅网卡nic 状态:已启动(UP)"

else

echo -e "\n❌ 网卡 $nic 状态:已关闭(DOWN)"

fi

步骤4:测试


PS:

  1. ens33/ens36(物理 / 虚拟机虚拟网卡)
    通常是 UP 状态
    这是你的主力网卡,负责和外部网络通信(比如连路由器、上互联网、和其他电脑传数据)。
    系统启动时会自动把它设为 UP,除非你手动关闭(比如 sudo ip link set ens33 down)。
  2. virbr0(KVM 桥接网卡)
    可能是 UP,也可能是 DOWN
    这个网卡是 KVM 虚拟机的 "虚拟交换机",作用是让多个虚拟机之间能互相通信,或者虚拟机和宿主机通信
  3. virbr0-nic(virbr0的辅助网卡)
    几乎都是 DOWN 状态
    它是 virbr0 的 "配套马甲",本身不直接参与数据转发,只是为 virbr0 提供底层支持。
    系统设计时就把它设为默认 DOWN,哪怕 virbr0 是 UP,它也不用启动------ 就像交换机的 "内部接口",不用对外暴露

4.其余琐碎知识点

-- 多行注释的写法验证;

#单行注释(已会)

<<EOF

这是多行注释第一行

这是多行注释第二行

可以写任意内容,不会被执行

EOF

-- test命令和[ ]的等价性验证;

-- ifconfig/netstat/ss的基础执行;

--脚本绝对路径执行的验证

二、基础作业:查询指定网口的启用状态

  • 需求:编写脚本 check_port_status.sh ,接收一个网口名称作为参数(如 eth0 、 wlan0 ),输出该网口是 UP 还是 DOWN 状态
  • 要求:参数为空时给出提示;兼容 ip link 和 ifconfig 两种命令的输出结果
  • 练习环境:虚拟机 + 开发板

脚本如下

#!/bin/bash

#脚本名称:check_port_status.sh

#功能:查询指定网口的启用状态(兼容ip link和ifconfig)

#用法:./check_port_status.sh <网口名> (如:./check_port_status.sh ens33)

#第一步:检查参数是否为空

if [ $# -ne 1 ]; then #传入参数不是ens33这种规范输入

echo "❌ 错误:请传入网口名称作为参数!"

echo "✅ 使用方法:$0 <网口名>"

echo "🔍 示例:0 ens33 或 0 eth0"

exit 1

fi

#定义变量:接收传入的网口名参数

nic_name=$1

#第二步:优先使用ip link命令(新版)获取状态

if command -v ip > /dev/null 2>&1; then #判断系统里是否有 ip 命令

if ip link show "KaTeX parse error: Expected 'EOF', got '&' at position 25: ... > /dev/null 2>&̲1; then# 检查网口是否...(ip link show "$nic_name" | grep -oP '(UP|DOWN)(?=,|>)')

echo " 网口 n i c n a m e 状态: nic_name 状态: nicname状态:nic_status"

else

echo " 错误:网口 $nic_name 不存在!"

exit 1

fi

#第三步:如果没有ip命令,使用ifconfig命令(老版兼容)

elif command -v ifconfig > /dev/null 2>&1; then

检查网口是否存在

if ifconfig "KaTeX parse error: Expected 'EOF', got '&' at position 25: ... > /dev/null 2>&̲1; then ...nic_name" | grep -q "UP"; then

echo "📌 网口 $nic_name 状态:UP"

else

echo "📌 网口 $nic_name 状态:DOWN"

fi

else

echo "❌ 错误:网口 $nic_name 不存在!"

exit 1

fi

#第四步:两种命令都没有(极端情况)

else

echo "❌ 错误:系统中未找到ip和ifconfig命令,无法查询网口状态!"

exit 1

fi

虚拟机端测试

开发板端测试

三、基础作业:列出所有活跃网口及 IP 地址

  • 需求:编写脚本 list_active_ports.sh ,自动筛选出所有状态为 UP 的网口,输出格式为 网口名称: IP地址
  • 要求:无活跃网口时提示"当前无可用网口";忽略 lo 回环接口
  • 练习环境:虚拟机 + 开发板

先确定格式,脚本格式匹配部分用AI生成

要是从ip addr里抠出「非 lo、UP、有 IPv4」的网口名 + 纯 IP

c 复制代码
#!/bin/bash
#脚本名称:list_active_ports.sh
#功能:列出所有活跃(UP)网口及IP地址
#用法:./list_active_ports.sh

#初始化临时变量存储活跃网口信息
active_ports=""

#优先使用ip addr
if command -v ip > /dev/null 2>&1; then
    #精准匹配ip addr输出格式
    active_ports=$(
        ip addr | awk '
        # 1. 匹配网口行(如2: ens33: ...),提取网口名并重置UP标记
        /^[0-9]+: [a-zA-Z0-9]+:/ {
            split($2, nic_arr, ":");  # 按冒号分割,提取ens33/lo
            current_nic = nic_arr[1];
            is_up = 0;  # 初始化为非UP状态
        }
        # 2. 匹配网口行中的UP状态(仅标记当前网口为UP)
        /^[0-9]+: .*<.*UP,.*>/ && current_nic != "lo" {
            is_up = 1;  # 仅非lo的UP网口标记为1
        }
        # 3. 匹配IPv4地址行,且当前网口是UP状态
        /inet [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ && is_up == 1 {
            split($2, ip_arr, "/");  # 按/分割,提取纯IP(去掉/24)
            ip_addr = ip_arr[1];
            print current_nic ":" ip_addr;  # 按"网口:IP"格式输出
            is_up = 0;  # 重置标记,避免重复匹配
        }'
    )

#兼容ifconfig(备用,开发板环境可用,但这块板子ip addr啥的也行)
elif command -v ifconfig > /dev/null 2>&1; then
    active_ports=$(
        ifconfig | awk '
        /^[a-zA-Z0-9]+:/ {
            nic_name = substr($1, 1, length($1)-1);
            nic_up = 0;
        }
        /UP,BROADCAST,RUNNING/ && nic_name != "lo" {
            nic_up = 1;
        }
        /inet / && nic_up == 1 {
            print nic_name ":" $2;
            nic_up = 0;
        }'
    )

#极端情况:无网络命令
else
    echo "❌ 错误:系统未找到ip/ifconfig命令,无法查询网口信息!"
    exit 1
fi

#输出最终结果
if [ -z "$active_ports" ]; then
    echo "⚠️ 当前无可用网口"
else
    echo "✅ 活跃网口及IP地址:"
    echo "$active_ports"
fi

虚拟机测试

开发板测试

这倒没错,本身就没在用。

接上网线重新测试

结果没网口

原因:

开发板默认不会自动获取 IPv4,必须手动执行udhcpc或配静态 IP;

IPv6 是系统自动生成的链路本地地址(fe80 开头),不算 "可用的 IPv4 网口",所以脚本之前没识别。

修改脚本的核心匹配逻辑,让它能识别到eth1的<UP,LOWER_UP>状态(开发板插线后的 UP 格式),同时兼容 IPv6(可选)或优先识别 IPv4 配置后的状态

c 复制代码
#!/bin/bash
#脚本名称:list_active_ports.sh
#功能:列出所有活跃(UP)网口及IP地址(适配虚拟机+IMX6ULL开发板)
#用法:./list_active_ports.sh

#初始化临时变量存储活跃网口信息
active_ports=""

#优先使用ip addr(适配开发板的UP状态格式)
if command -v ip > /dev/null 2>&1; then
    # 核心修改:适配开发板eth1的<UP,LOWER_UP>状态,放宽UP匹配规则
    active_ports=$(
        ip addr | awk '
        # 1. 匹配网口行(如3: eth1: ...),提取网口名并重置UP标记
        /^[0-9]+: [a-zA-Z0-9]+:/ {
            split($2, nic_arr, ":");  # 按冒号分割,提取eth1/lo
            current_nic = nic_arr[1];
            is_up = 0;  # 初始化为非UP状态
        }
        # 2. 匹配UP状态(兼容两种格式:虚拟机<UP,xxx> / 开发板<xxx,UP,xxx>)
        /^[0-9]+: .*<.*UP.*>/ && current_nic != "lo" {
            is_up = 1;  # 只要包含UP且非lo,就标记为活跃
        }
        # 3. 匹配IPv4地址行,且当前网口是UP状态
        /inet [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ && is_up == 1 {
            split($2, ip_arr, "/");  # 按/分割,提取纯IP(去掉/24)
            ip_addr = ip_arr[1];
            print current_nic ":" ip_addr;  # 按"网口:IP"格式输出
            is_up = 0;  # 重置标记,避免重复匹配
        }
        # 可选:如果想显示IPv6(开发板eth1默认有IPv6),取消下面注释
        # /inet6 fe80:/ && is_up == 1 {
        #     split($2, ip6_arr, "/");
        #     ip6_addr = ip6_arr[1];
        #     print current_nic ":IPv6-" ip6_addr;
        #     is_up = 0;
        # }'
    )

#兼容ifconfig(备用,开发板环境可用)
elif command -v ifconfig > /dev/null 2>&1; then
    active_ports=$(
        ifconfig | awk '
        /^[a-zA-Z0-9]+:/ {
            nic_name = substr($1, 1, length($1)-1);
            nic_up = 0;
        }
        /UP,BROADCAST/ && nic_name != "lo" {
            nic_up = 1;
        }
        /inet / && nic_up == 1 {
            print nic_name ":" $2;
            nic_up = 0;
        }'
    )

#极端情况:无网络命令
else
    echo "❌ 错误:系统未找到ip/ifconfig命令,无法查询网口信息!"
    exit 1
fi
#输出最终结果
if [ -z "$active_ports" ]; then
    # 补充:提示用户网口UP但无IP的情况(开发板插线后常见)
    up_nics=$(ip addr | awk '/^[0-9]+: .*<.*UP.*>/ && $2 !~ /lo:/ {split($2,n,":"); print n[1]}')
    if [ -n "$up_nics" ]; then
        echo "⚠️ 以下网口状态为UP,但未配置IPv4地址:"
        echo "$up_nics"
        echo "💡 建议执行:udhcpc -i 网口名(如udhcpc -i eth1)获取IP"
    else
        echo "⚠️ 当前无可用网口"
    fi
else
    echo "✅ 活跃网口及IP地址:"
    echo "$active_ports"
fi

说明脚本没问题,现在解决IPv4的问题

udhcpc 执行失败

虚拟机查得IP为192.168.190.133

#核心:IP前三位和虚拟机一致(192.168.190),最后一位换101(避开虚拟机的133) ifconfig eth1 192.168.190.101 netmask 255.255.255.0 up

直接输还权限不够

这样配置一下就好了

四、进阶作业:网口流量实时监控(简易版)

  • 需求:编写脚本 monitor_port_traffic.sh ,指定网口后,每隔2秒输出一次该网口的接收(RX)、发送(TX)字节数
  • 要求:支持按 Ctrl+C 终止脚本;输出结果做格式化排版,清晰易读
  • 练习环境:虚拟机 + 开发板

脚本内容如下

c 复制代码
#!/bin/bash
#脚本名称:monitor_port_traffic.sh
#功能:实时监控指定网口的RX/TX字节数(每隔2秒输出,支持Ctrl+C终止)
#用法:./monitor_port_traffic.sh <网口名> (如:./monitor_port_traffic.sh ens33/eth0)

#定义优雅退出函数(捕获Ctrl+C)
exit_gracefully() {
    echo -e "\n\n✅ 监控已停止!"
    exit 0
}

#捕获SIGINT信号(Ctrl+C),触发退出函数
trap exit_gracefully 2
#2就是SIGINT,SIGINT就是按下 Ctrl+C 时系统给脚本发的 "中断指令, 替代默认行为,转而执行你指定的 exit_gracefully 函数
**#第一步:参数校验**
if [ $# -ne 1 ]; then
    echo "❌ 错误:请传入唯一的网口名参数!"
    echo "✅ 使用方法:./monitor_port_traffic.sh <网口名>"
    echo "🔍 示例:./monitor_port_traffic.sh ens33  或  ./monitor_port_traffic.sh eth0"
    exit 1
fi

#提取传入的网口名
nic_name="$1"

**#第二步:检查网口是否存在**
if ! ip link show "$nic_name" > /dev/null 2>&1; then
  **加粗样式**  echo "❌ 错误:网口 $nic_name 不存在!"
    echo "🔍 可用网口列表:"
ip link show | awk -F ': ' '/^[0-9]+: /{print "   - " $2}' | sed 's/://'
 
    exit 1
fi
#! ip link show "$nic_name"判断网口存在,> /dev/null 2>&1静默处理

**#第三步:输出监控表头(格式化)**
echo "======================================== "
echo "📊 实时监控网口:$nic_name"
echo "⏱ 刷新间隔:2秒(按Ctrl+C终止)"
echo "======================================== "
printf "%-10s %-15s %-15s\n" "时间" "接收字节(RX)" "发送字节(TX)"
echo "----------------------------------------"

#第四步:实时循环采集流量
while true; do
    # 获取当前时间(格式:HH:MM:SS)
    current_time=$(date +"%H:%M:%S")
    
    # 优先用ip -s link
    if command -v ip > /dev/null 2>&1; then
        # 提取RX/TX字节数(过滤指定网口,只取第一行字节数)
        rx_bytes=$(ip -s link show "$nic_name" | awk '/RX:/{getline; print $1; exit}')
        tx_bytes=$(ip -s link show "$nic_name" | awk '/TX:/{getline; print $1; exit}')
    
    # 兼容ifconfig(开发板标配)
    elif command -v ifconfig > /dev/null 2>&1; then
        # 提取RX/TX字节数(ifconfig输出的RX/TX bytes)
        rx_bytes=$(ifconfig "$nic_name" | awk '/RX bytes/{split($2, b, ":"); print b[2]}')
        tx_bytes=$(ifconfig "$nic_name" | awk '/TX bytes/{split($2, b, ":"); print b[2]}')
    
    # 极端情况:无网络命令
    else
        echo "❌ 错误:系统未找到ip/ifconfig命令,无法采集流量!"
        exit 1
    fi

    # 格式化输出(对齐字段,确保美观)
    printf "%-10s %-15s %-15s\n" "$current_time" "$rx_bytes Bytes" "$tx_bytes Bytes"
    
    # 每隔2秒刷新一次
    sleep 2
done

虚拟机测试

开发板测试

五、进阶作业:网口状态异常告警

  • 需求:编写脚本 port_alert.sh ,定时检查指定网口状态,若状态变为 DOWN 或 IP 地址丢失,立即输出告警信息
  • 要求:可设置检查间隔时间(如每10秒检查一次);在开发板环境中,可结合板载蜂鸣器或 LED 实现声光告警(若硬件支持)
  • 练习环境:虚拟机(日志告警) + 开发板(日志+硬件告警)

这是给虚拟机的,开发板版本的在图片下面

c 复制代码
#!/bin/bash
#网口状态异常告警脚本(纯日志版)
#使用说明:./port_alert.sh <网口名> <检查间隔(秒)>
#示例:./port_alert.sh eth1 10

#======================== 配置项 ========================
ALERT_LOG="/home/book/shell_demo/port_alert.log"
 ======================== 参数校验 ========================
#检查参数数量是否为2个
if [ $# -ne 2 ]; then
    echo -e "\033[31m用法错误!\033[0m 正确用法:$0 <网口名> <检查间隔(秒)>"
    echo "示例:$0 eth1 10  (检查eth1,每10秒一次)"
    exit 1
fi

PORT_NAME=$1       # 要检查的网口(如eth1、ens33)
CHECK_INTERVAL=$2  # 检查间隔(秒)

#校验检查间隔是否为正整数
if ! [[ "$CHECK_INTERVAL" =~ ^[1-9][0-9]*$ ]]; then
    echo -e "\033[31m错误:\033[0m 检查间隔必须是大于0的数字(如5、10)!"
    exit 1
fi

#======================== 核心函数 ========================
#1. 检查网口是否存在
check_port_exist() {
    if ! ip link show "$PORT_NAME" >/dev/null 2>&1; then
        echo -e "\033[31m[$(date +'%Y-%m-%d %H:%M:%S')] 错误:\033[0m 网口$PORT_NAME不存在!"
        exit 1
    fi
}

#2. 检查网口状态(UP/DOWN)+ IP地址是否存在
check_port_status() {
    # 获取网口状态(state后的字段,正常为UP)
    PORT_STATUS=$(ip link show "$PORT_NAME" | grep -Eo 'state [A-Z]+' | awk '{print $2}')
    # 获取网口IPv4地址(空则表示IP丢失)
    PORT_IP=$(ip addr show "$PORT_NAME" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)
    
    # 初始化告警信息
    ALERT_MSG=""
    # 判定异常场景
    if [ "$PORT_STATUS" != "UP" ]; then
        ALERT_MSG="网口$PORT_NAME状态异常:当前为$PORT_STATUS(正常应为UP)"
    elif [ -z "$PORT_IP" ]; then
        ALERT_MSG="网口$PORT_NAME IP地址丢失!"
    fi

    # 异常:输出告警(终端+日志)
    if [ -n "$ALERT_MSG" ]; then
        local log_msg="[$(date +'%Y-%m-%d %H:%M:%S')] 告警:$ALERT_MSG"
        # 终端红色高亮输出
        echo -e "\033[31m$log_msg\033[0m"
        # 写入日志文件(追加模式)
        echo "$log_msg" >> "$ALERT_LOG"
    # 正常:输出普通日志
    else
        local normal_msg="[$(date +'%Y-%m-%d %H:%M:%S')] 网口$PORT_NAME状态正常(UP),IP:$PORT_IP"
        echo "$normal_msg"
        # 正常日志也可写入文件(可选,取消下面注释即可)
        # echo "$normal_msg" >> "$ALERT_LOG"
    fi
}

#======================== 主程序 ========================
# 前置检查:网口是否存在
check_port_exist

#初始化日志文件(确保可写)
touch "$ALERT_LOG" >/dev/null 2>&1
if [ $? -ne 0 ]; then
    echo -e "\033[33m警告:\033[0m 日志文件$ALERT_LOG创建失败,仅输出终端告警!"
    ALERT_LOG="/tmp/port_alert.log"  # 降级到临时目录
    touch "$ALERT_LOG"
fi

#启动提示
echo -e "\033[32m[$(date +'%Y-%m-%d %H:%M:%S')] 启动网口告警脚本:\033[0m"
echo "  - 检查网口:$PORT_NAME"
echo "  - 检查间隔:$CHECK_INTERVAL秒"
echo "  - 告警日志:$ALERT_LOG"
echo "----------------------------------------"

#循环检查(Ctrl+C停止)
while true; do
    check_port_status
    sleep "$CHECK_INTERVAL"
done

虚拟机测试

开发板测试(更改脚本)


从韦东山的代码里找对应引脚

让AI更改适配

有led_ctrl.sh和port_alert.sh

更改之后的版本如下

led_ctrl.sh:

c 复制代码
#!/bin/bash
# IMX6ULL LED控制脚本(适配BusyBox版devmem)
# 用法:/tmp/led_ctrl.sh on/off

# 寄存器物理地址(和你C代码完全一致)
CCM_CCGR1=0x20C406C
IOMUXC_SNVS_MUX=0x2290014
GPIO5_GDIR=0x020AC004
GPIO5_DR=0x020AC000

# 初始化LED(适配BusyBox devmem语法)
led_init() {
    # 1. 读取CCM_CCGR1当前值
    CCM_VAL=$(devmem $CCM_CCGR1 32)
    # 使能GPIO5时钟(bit31:30 = 0b11)
    NEW_CCM_VAL=$((CCM_VAL | (3<<30)))
    devmem $CCM_CCGR1 32 $NEW_CCM_VAL

    # 2. 读取IOMUXC_SNVS_MUX当前值
    IOMUX_VAL=$(devmem $IOMUXC_SNVS_MUX 32)
    # 配置引脚为GPIO功能(bit3:0 = 0b0101)
    NEW_IOMUX_VAL=$(((IOMUX_VAL & ~0xf) | 5))
    devmem $IOMUXC_SNVS_MUX 32 $NEW_IOMUX_VAL

    # 3. 读取GPIO5_GDIR当前值
    GDIR_VAL=$(devmem $GPIO5_GDIR 32)
    # 设置GPIO5_IO03为输出(bit3 = 1)
    NEW_GDIR_VAL=$((GDIR_VAL | (1<<3)))
    devmem $GPIO5_GDIR 32 $NEW_GDIR_VAL
}

# 控制LED
case "$1" in
    on)
        led_init  # 初始化(首次执行生效)
        # 读取GPIO5_DR当前值,低电平点亮(bit3清0)
        DR_VAL=$(devmem $GPIO5_DR 32)
        NEW_DR_VAL=$((DR_VAL & ~(1<<3)))
        devmem $GPIO5_DR 32 $NEW_DR_VAL
        ;;
    off)
        led_init
        # 读取GPIO5_DR当前值,高电平熄灭(bit3置1)
        DR_VAL=$(devmem $GPIO5_DR 32)
        NEW_DR_VAL=$((DR_VAL | (1<<3)))
        devmem $GPIO5_DR 32 $NEW_DR_VAL
        ;;
    *)
        echo "用法:$0 <on|off>"
        exit 1
        ;;
esac

exit 0

port_alert.sh:

#!/bin/bash
# IMX6ULL开发板网口状态告警脚本(日志+LED告警)
# 功能:定时检查网口状态,DOWN/IP丢失时日志告警+点亮LED,恢复后熄灭LED
# 使用说明:./port_alert.sh <网口名> <检查间隔(秒)>
# 示例:./port_alert.sh eth0 10  (检查eth1,每5秒一次)

ALERT_LOG="/tmp/port_alert.log"  # 日志路径(/tmp无需权限,重启丢失;想持久化改到/root)
LED_CTRL_TOOL="/tmp/led_ctrl.sh" # LED控制脚本路径(必须和你的实际路径一致)
LED_ENABLE=1                     # LED告警开关:1=启用,0=禁用

# ======================== 参数校验(必做) ========================
# 1. 检查参数数量
if [ $# -ne 2 ]; then
    echo -e "\033[31m[ERROR] 用法错误!正确用法:$0 <网口名> <检查间隔(秒)>\033[0m"
    echo -e "\033[33m[INFO] 示例:$0 eth0 10\033[0m"
    exit 1
fi

# 2. 提取参数
PORT_NAME=$1       # 要检查的网口(IMX6ULL通常是eth0)
CHECK_INTERVAL=$2  # 检查间隔(秒)

# 3. 校验检查间隔是否为正整数
if ! [[ "$CHECK_INTERVAL" =~ ^[1-9][0-9]*$ ]]; then
    echo -e "\033[31m[ERROR] 检查间隔必须是大于0的整数!\033[0m"
    exit 1
fi

# 4. 校验网口是否存在
if ! ip link show "$PORT_NAME" >/dev/null 2>&1; then
    echo -e "\033[31m[ERROR] 网口$PORT_NAME不存在!\033[0m"
    exit 1
fi

# 5. 校验LED脚本是否存在(不存在则禁用LED告警)
if [ $LED_ENABLE -eq 1 ] && [ ! -f "$LED_CTRL_TOOL" ]; then
    echo -e "\033[33m[WARN] LED控制脚本$LED_CTRL_TOOL不存在,禁用LED告警!\033[0m"
    LED_ENABLE=0
fi

# ======================== 核心函数 ========================
# 1. LED告警控制(点亮/熄灭)
led_alert() {
    local status=$1  # on=点亮,off=熄灭
    if [ $LED_ENABLE -eq 1 ]; then
        $LED_CTRL_TOOL $status >/dev/null 2>&1
    fi
}

# 2. 网口状态检查+告警逻辑
check_port_status() {
    # 获取网口状态(UP/DOWN)
    PORT_STATUS=$(ip link show "$PORT_NAME" | grep -Eo 'state [A-Z]+' | awk '{print $2}')
    # 获取网口IPv4地址(仅保留IP,去掉掩码)
    PORT_IP=$(ip addr show "$PORT_NAME" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)

    # 场景1:网口DOWN → 告警+点亮LED
    if [ "$PORT_STATUS" != "UP" ]; then
        LOG_MSG="[$(date +'%Y-%m-%d %H:%M:%S')] [ALERT] 网口$PORT_NAME状态异常:当前为$PORT_STATUS(正常应为UP)"
        echo -e "\033[31m$LOG_MSG\033[0m"  # 终端红色告警
        echo "$LOG_MSG" >> "$ALERT_LOG"     # 写入日志
        led_alert "on"                     # 点亮LED

    # 场景2:网口UP但IP丢失 → 告警+点亮LED
    elif [ -z "$PORT_IP" ]; then
        LOG_MSG="[$(date +'%Y-%m-%d %H:%M:%S')] [ALERT] 网口$PORT_NAME状态为UP,但IP地址丢失!"
        echo -e "\033[31m$LOG_MSG\033[0m"
        echo "$LOG_MSG" >> "$ALERT_LOG"
        led_alert "on"

    # 场景3:网口正常 → 提示+熄灭LED
    else
        LOG_MSG="[$(date +'%Y-%m-%d %H:%M:%S')] [INFO] 网口$PORT_NAME状态正常(UP),IP地址:$PORT_IP"
        echo -e "\033[32m$LOG_MSG\033[0m"  # 终端绿色提示
        # 正常日志可选写入(取消注释启用)
        # echo "$LOG_MSG" >> "$ALERT_LOG"
        led_alert "off"                    # 熄灭LED
    fi
}

# ======================== 主程序(定时循环检查) ========================
# 启动提示
echo -e "\033[32m[$(date +'%Y-%m-%d %H:%M:%S')] [INFO] IMX6ULL网口告警脚本启动!\033[0m"
echo -e "\033[32m[INFO] 检查网口:$PORT_NAME | 检查间隔:$CHECK_INTERVAL秒 | LED告警:$(if [ $LED_ENABLE -eq 1 ]; then echo "启用"; else echo "禁用"; fi)\033[0m"
echo -e "\033[32m[INFO] 按Ctrl+C停止脚本...\033[0m"
echo "--------------------------------------------------------"

# 无限循环检查(定时)
while true; do
    check_port_status
    sleep "$CHECK_INTERVAL"
done

六、综合作业:网口信息汇总报告生成

  • 需求:编写脚本 port_info_report.sh ,一次性收集所有网口的名称、状态、IP 地址、MAC 地址、接收/发送字节数,并将结果写入 port_report_$(date +%F).txt 报告文件
  • 要求:报告文件按日期命名,避免覆盖;内容按网口分类整理,添加标题和生成时间
  • 练习环境:虚拟机 + 开发板
c 复制代码
#!/bin/bash
# 网口信息汇总报告脚本(适配虚拟机+IMX6ULL开发板)
# 功能:采集所有网口信息,生成按日期命名的结构化报告
# 使用:./port_info_report.sh(无需参数,直接运行)

# ======================== 基础配置 ========================
# 报告文件命名:port_report_YYYY-MM-DD.txt(按日期命名,避免覆盖)
REPORT_FILE="port_report_$(date +%F).txt"
# 报告生成时间(精确到秒)
REPORT_TIME=$(date +'%Y-%m-%d %H:%M:%S')

# ======================== 核心函数:采集单网口信息 ========================
get_port_info() {
    local port_name=$1  # 传入网口名(如eth0/ens33/lo)
    
    # 1. 优化核心:精准识别网口状态(UP/DOWN/未激活)
    if ip link show "$port_name" | grep -q 'UP'; then
        port_status="UP"
    elif ip link show "$port_name" | grep -q 'DOWN'; then
        port_status="DOWN"
    else
        port_status="未激活"  # 无UP/DOWN标记时标注,替代原UNKNOWN
    fi

    # 2. 获取IPv4地址(无则标注"无",仅取第一个IP)
    port_ip=$(ip addr show "$port_name" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1 | head -1)
    [ -z "$port_ip" ] && port_ip="无"

    # 3. 获取MAC地址(lo无MAC则标注"无")
    port_mac=$(ip link show "$port_name" | grep -Eo 'link/ether [0-9a-f:]+' | awk '{print $2}')
    [ -z "$port_mac" ] && port_mac="无"

    # 4. 获取接收/发送字节数(从sysfs读取,适配嵌入式系统)
    port_rx_bytes=$(cat "/sys/class/net/$port_name/statistics/rx_bytes" 2>/dev/null || echo "0")
    port_tx_bytes=$(cat "/sys/class/net/$port_name/statistics/tx_bytes" 2>/dev/null || echo "0")

    # 5. 将该网口信息写入报告(结构化排版)
    echo "------------------------------" >> "$REPORT_FILE"
    echo "网口名称:$port_name" >> "$REPORT_FILE"
    echo "状态:$port_status" >> "$REPORT_FILE"
    echo "IP地址:$port_ip" >> "$REPORT_FILE"
    echo "MAC地址:$port_mac" >> "$REPORT_FILE"
    echo "接收字节数:$port_rx_bytes" >> "$REPORT_FILE"
    echo "发送字节数:$port_tx_bytes" >> "$REPORT_FILE"
}

# ======================== 主程序 ========================
# 1. 清空/创建报告文件(按日期命名,无需担心覆盖)
> "$REPORT_FILE"

# 2. 写入报告标题和生成时间(提升可读性)
echo "========== 网口信息汇总报告 ==========" >> "$REPORT_FILE"
echo "报告生成时间:$REPORT_TIME" >> "$REPORT_FILE"
echo "======================================" >> "$REPORT_FILE"
echo "" >> "$REPORT_FILE"

# 3. 获取所有有效网口列表(过滤无用虚拟网口,保留物理/回环口)
port_list=$(ip link show | grep -E '^[0-9]+: ' | awk -F': ' '{print $2}' | grep -v '^br-' | grep -v '^veth' | grep -v '^docker')

# 4. 遍历所有网口,逐个采集信息
for port in $port_list; do
    get_port_info "$port"
done

# 5. 补充报告结尾,完善结构
echo "" >> "$REPORT_FILE"
echo "========== 报告结束 ==========" >> "$REPORT_FILE"

# 6. 终端友好提示(告知用户报告生成结果)
echo -e "\033[32m[SUCCESS] 网口报告已生成:$(pwd)/$REPORT_FILE\033[0m"

开发板测试

开发板测试

Device "sit0@NONE" does not exist. 是脚本遍历网口时,系统中存在一个无效的 / 未正确初始化的sit0网口条目,导致ip link show命令报的警告,本质是系统层面的小问题,不是脚本错误。介意的话可以用grep过滤掉

#优化:过滤无效网口(sit0)和特殊字符(@NONE),仅保留有效网口

port_list=(ip link show \| grep -E '^[1](#1)^+: ' \| awk -F': ' '{print 2}' |

grep -v '^br-' | grep -v '^veth' | grep -v '^docker' |

grep -v '^sit' | grep -v '@' | tr -d ' ') # 新增:过滤sit开头+@字符+空格



  1. 0-9 ↩︎
相关推荐
会算数的⑨2 小时前
Spring AI Alibaba 学习(三):Graph Workflow 深度解析(下篇)
java·人工智能·分布式·后端·学习·spring·saa
犽戾武2 小时前
在 Quest 上用 OpenXR + MediaCodec + OES 外部纹理做一个“低延迟视频面板”(48小时的编码复盘)
linux·c++·嵌入式硬件·vr
之歆2 小时前
磁盘分区与文件系统管理
linux·文件系统·磁盘分区
犽戾武2 小时前
准备工作:OpenXR Sample 示例工程“去掉 UI 渲染”& RK3588→Windows 低延迟 UDP 视频链路
linux·c++·ubuntu·vr
生活很暖很治愈2 小时前
Linux——线程异常
linux·c++·ubuntu
市安2 小时前
基于Centos构建Nginx镜像(Dokerfile)
linux·运维·nginx·docker·容器·centos·镜像
生活很暖很治愈2 小时前
Linux——线程概念&控制&创建&等待
linux·服务器·c++·ubuntu
invicinble2 小时前
梳理docker的提供机制
运维·docker·容器
PPPPPaPeR.2 小时前
深入理解 Linux 文件系统:元数据、inode 与 block 核心原理
linux·运维·服务器