Bash内置命令read行为与手册描述不符的问题排查
先明确read -t 0的真实行为
根据GNU Bash手册,read -t 0的核心逻辑是立即检查指定文件描述符(默认是stdin)是否能立即完成读取操作:
- 若缓冲区已有可读取的数据,返回0(成功)
- 若缓冲区为空,但写入端未关闭,返回非0(失败,因为读取会阻塞)
- 若写入端已关闭,无论缓冲区是否有数据,都返回0(因为读取会立即得到EOF,属于"可完成读取"的情况)
逐个解释你的异常现象
1. 启动脚本后第一个up read fail
脚本启动时,arecord和lame刚初始化完成,还没向管道写入足够数据到脚本的stdin缓冲区。read -t 0检测的是当前脚本进程的stdin缓冲区状态,而非管道是否存在。此时缓冲区为空,写入端又未关闭,所以返回失败——这完全符合手册描述,你误解了"有输入"的定义:手册指的是"当前有可读取的数据",而非"管道连接存在"。
2. 杀死cat后出现的bottom read fail和up read fail
当你杀死cat进程时,cat已经把脚本stdin缓冲区里的数据全部读取并写入文件了。此时lame可能还在向管道写数据,但数据还在管道的中间缓冲区(未到达脚本的stdin缓冲区)。脚本回到循环顶部执行read -t 0时,缓冲区仍然为空,写入端也没关闭,所以返回失败。后续重新启动的cat会阻塞等待数据,但read -t 0是在cat执行前做的检查,自然还是输出up read fail。
3. 杀死arecord后出现的bottom read succ
杀死arecord后,lame会因为输入源关闭而立即退出,此时脚本stdin的写入端被彻底关闭。根据read -t 0的规则,写入端关闭时,读取操作会立即返回EOF,属于"可立即完成读取"的场景,所以read -t 0返回0(成功),触发break退出循环。这也符合手册描述,只是你没考虑到"写入端关闭"这个分支的行为。
你的脚本逻辑缺陷
当前脚本用cat >>$filename来写入文件,会导致脚本全程被cat阻塞,无法响应HUP信号(因为cat在前台运行,脚本没有机会处理信号)。这也是你需要杀死cat来测试的原因,正常场景下HUP信号根本无法被脚本捕获。
修正方向(实现HUP触发文件切换)
要实现HUP信号触发重新打开新文件,需要避免用cat阻塞脚本,改用信号陷阱+分块读取的方式:
#!/bin/bash # 初始文件名 output_file="test.mp3" # 标记是否需要切换文件 rotate_flag=0 # 捕获HUP信号,设置切换标记 trap 'rotate_flag=1' HUP while :; do # 检查是否需要切换文件 if [ "$rotate_flag" -eq 1 ]; then # 生成带时间戳的新文件名 output_file="test_$(date +%Y%m%d%H%M%S).mp3" rotate_flag=0 echo "Switched to new output file: $output_file" fi # 分块读取输入,避免阻塞整个脚本 if read -r -N 4096 chunk; then printf "%s" "$chunk" >> "$output_file" else # 读取到EOF,退出循环 break fi done
内容的提问来源于stack exchange,提问作者Deserter

