当前位置: 首页 > news >正文

把 shell 脚本里的「后台接收」-- 以 UART/CAN 双总线监听为例


#!/bin/bash
set -euo pipefail
shopt -s lastpipe############################
# 1. 固定配置
############################
UART_DEV=(/dev/ttyAS{1..4})
UART_BAUD=(9600 115200 57600 921600)
UART_TX_LINE=("UART1_LOOP_9600""UART2_LOOP_115200""UART3_LOOP_57600""UART4_LOOP_921600"
)
CAN_BUS=(can0 can1)
CAN_FRAME=("12345678#AABBCCDDEEFF0011""87654321#FFEEDDCCBBAA9988"
)############################
# 2. 清理函数
############################
cleanup() {echo; echo ">>> 收到 exit,清理后台任务 ..."jobs -p | xargs -r kill -9 2>/dev/null || trueexit 0
}
trap cleanup INT TERM EXIT############################
# 3. UART 初始化
############################
init_uart() {for i in {0..3}; dostty -F "${UART_DEV[$i]}" "${UART_BAUD[$i]}" raw -echo \ignbrk -icrnl -ixon -opost -isig -icanondone
}############################
# 4. 后台:接收
############################
# UART 接收
for i in {0..3}; do{while :; doread -r -t1 line < "${UART_DEV[$i]}" && echo "[tty$((i+1))] RX $line"done} &
done
# CAN 接收
for bus in "${CAN_BUS[@]}"; do{ candump -L "$bus" | while read -r f; do echo "[$bus] RX $f"; done; } &
done############################
# 5. 主循环:定时发送
############################
main_loop() {init_uartwhile :; do# UART 循环发送for i in {0..3}; doprintf '%s\r\n' "${UART_TX_LINE[$i]}" > "${UART_DEV[$i]}"echo "[tty$((i+1))] TX ${UART_TX_LINE[$i]}"done# CAN 扩展帧循环发送for i in {0..1}; docansend "${CAN_BUS[$i]}" "${CAN_FRAME[$i]}"echo "[${CAN_BUS[$i]}] TX ${CAN_FRAME[$i]}"donesleep 2done
}############################
# 6. 唯一交互:等待 exit
############################
echo ">>> UART/CAN 已启动,输入 exit 退出 <<<"
main_loop &
read -p "" cmd
[[ "$cmd" == "exit" ]] && cleanup

  1. 背景说明
    最近写了一个全功能硬件自检脚本,需要 同时 做三件事:
  2. 周期性 发送 数据到 UART + CAN + GPIO;
  3. 实时 接收 UART/CAN 的回环数据并打印;
  4. 用户敲 exit 时瞬间 干净退出。

后台接收部分虽然只有几行,却最容易踩坑。本文把 UART 和 CAN 的接收逻辑逐行拆透,保证你下次拷代码时知道“为什么非得这么写”。


  1. 先给完整上下文(省流版)
# 1) 串口设备
UART_DEV=(/dev/ttyAS{1..4})
# 2) CAN 接口
CAN_BUS=(can0 can1)

  1. UART 接收——以 /dev/ttyAS1 为例
{while :; doif read -r -t1 line < "${UART_DEV[0]}"; thenecho "[tty1] RX $line"fidone
} &

行号 代码 作用 坑点提示
{ ... } & 整段逻辑丢进 子 shell 后台任务,主脚本继续往下跑。 不加 {} 的话 & 只会把最后一条命令后台化。
while : 死循环,保持“永远在线”。 如果写 while read,一旦串口没数据就直接退出,监听就断了。
read -r -t1 line < "${UART_DEV[0]}" -t1:最多等 1 秒,无数据立刻返回非 0;-r:禁用反斜杠转义;<:重定向文件描述符。 去掉 -t1 会导致 read 永远阻塞,脚本退出时杀不掉。
if ... then echo 只有真正读到内容才打印,避免空行刷屏。 不加判断会把空行也打出来。
echo "[tty1] RX $line" 统一前缀,方便肉眼 grep。 如果设备发 \r\n$line 末尾会带 \r,可 line=${line%$'\r'} 去掉。


  1. CAN 接收——以 can0 为例
{ candump -L can0 | while read -r frame; do echo "[can0] RX $frame"; done; } &

行号 代码 作用 坑点提示
candump -L can0 -L 单行日志格式:timestamp id#data。 不加 -L 会多行输出,不好 read
while read -r frame 从管道里逐行读。 在子 shell 里,while 循环结束后 candump 也会被杀,干净。
echo "[can0] RX $frame" 加前缀,和 UART 日志对齐。 —
④ 整体再包一次 { ... } & 整个 candump+while 一起后台化。 如果不包,管道左右可能分到不同进程组,杀不干净。


  1. 为什么“子 shell + &”是最佳实践?
  • 并发:主脚本初始化/主循环不会被阻塞。
  • 隔离:子 shell 的变量、重定向、工作目录与父脚本完全隔离。
  • 易杀:
  jobs -p | xargs -r kill -9

一条命令即可杀掉 所有 子 shell,避免残留 candump 进程。


  1. 生命周期时序图
启动脚本│├─ fork 子 shell 1 → UART 接收 (ttyAS1)├─ fork 子 shell 2 → UART 接收 (ttyAS2)├─ fork 子 shell 3 → UART 接收 (ttyAS3)├─ fork 子 shell 4 → UART 接收 (ttyAS4)├─ fork 子 shell 5 → candump + while (can0)├─ fork 子 shell 6 → candump + while (can1)│└─ 父进程进入 main_loop,做 UART/CAN/GPIO 发送↑
用户敲 exit → cleanup() → kill -9 所有子 shell

  1. FAQ 速查

问题 解决
串口读出来末尾带 ^M line=${line%$'\r'}
串口偶尔丢字节 在 stty 里加 raw -echo 关闭回显/规范模式
candump 日志太长刷屏 加 grep -v " 0000000" 过滤空帧
退出时 candump 僵死 确认整段逻辑用 {} 包起来,再 &


  1. 一行总结
    把 接收逻辑 写成 { while read ... } & 的三明治结构,

就能在 shell 里低成本实现 多总线并发监听 + 一键退出,

UART、CAN、SPI、I2C 统统适用。


http://www.xdnf.cn/news/1368973.html

相关文章:

  • 影响服务器托管费用的因素​
  • 论文阅读-CompletionFormer
  • 山中游玩播报
  • 简单聊聊光栅化技术
  • 虚拟机中kubeadim部署的k8s集群,虚拟机关机了,重新开机后集群状态能否正常恢复的两种可能(详解)
  • vue2 创建threejs场景
  • ubuntu20.04 终端安装claude
  • 事件驱动架构详解
  • .gitignore 文件相关使用配置
  • 服务器数据恢复—热备盘上线失败如何恢复数据?
  • Ansible 自动化运维工具:介绍与完整部署(RHEL 9)
  • 如何基于阿里云OpenSearch LLM搭建智能客服平台
  • 亚马逊类目合规风暴:高压清洗机品类整顿背后的运营重构与风险防御
  • 零基础构建MCP服务器TypeScriptPython双语言实战指南
  • 零基础也能照做的WordPress网站安全漏洞修复 + 高级优化保姆级教程。
  • 【JavaEE】了解volatile和wait、notify(三)
  • 算法题打卡力扣第209题:长度最小的子数组(mid)
  • 【强化学习】区分理解: 时序差分(TD)、蒙特卡洛(MC)、动态规划(DP)
  • THM El Bandito
  • 使用C++与Qt6,在windows上打造MacOS风格桌面应用窗口
  • SELinux
  • Mac测试端口连接的几种方式
  • 【制作100个Unity游戏】从零开始构建类《月圆之夜》《杀戮尖塔》的卡牌游戏(附带项目源码)
  • CSS 结构伪类选择器
  • C语言开发入门教程:从环境搭建到第一个程序
  • 【lucene】SpanNotQuery 存在的意义
  • 国产化Excel开发组件Spire.XLS教程:Python 读取 CSV 文件,从基础到进阶指南
  • 一文看懂@Bean注解的原理
  • 【C++】用哈希表封装实现unordered_set和unordered_map
  • Ubuntu 操作系统