WSL的9P挂载中Bash星号通配符展开异常问题
仅在WSL的9P挂载目录(如/mnt/c/这类Windows磁盘挂载点)中,Shell脚本出现异常行为:当代码中存在写入临时文件的管道操作时,for循环中的*通配符展开会包含临时文件,导致文件计数异常,本该无限运行的测试脚本会随机执行若干次后停止。
测试脚本代码如下:
#!/bin/bash # save true file number true_f_num=$(for fn in *; do echo; done | wc -l) while :; do # count of iterations to outer loop pipe echo # save current file list in file echo -n '' >.list for fn in *; do echo "$fn" >>.list # line below causes unexpected behaviour # on 9P mounts (like /mnt/c/...) # (WSL2, ubuntu 22.04) done | cat > .tmp; rm .tmp # check [ $(cat .list | wc -l ) -ne $true_f_num ] && break done | wc -l
核心原因:管道并行执行 + 9P文件系统特性
Bash管道的并行执行机制
Bash中管道的左右两侧命令是并行启动执行的,而非串行等待。在for ... done | cat > .tmp这行代码中,执行for循环的子进程与执行cat > .tmp的子进程会同时启动。cat > .tmp会立即创建.tmp文件(输出重定向会自动创建不存在的文件),这个操作可能比左侧for循环的*通配符展开更快完成。9P文件系统的元数据同步特性
WSL的9P挂载通过网络协议实现Windows与Linux子系统的文件共享,其目录元数据(如文件列表)的同步机制与原生Linux文件系统(如ext4)不同。当.tmp文件被快速创建后,9P文件系统会立即更新目录的文件列表,导致左侧for循环的*通配符展开时,.tmp文件被包含进了遍历列表中。计数异常触发循环终止
初始的true_f_num是在没有.tmp文件时计算的文件总数,而当for循环把.tmp也写入.list后,.list的行数就会比true_f_num多1,触发[ ... -ne $true_f_num ] && break条件,导致循环提前终止。
关于你提到的「隐形FIFO」:不存在隐形FIFO,问题就是.tmp这个普通临时文件因并行执行的竞态条件,被意外包含进了通配符展开的结果中。
解决方案
方案1:改为串行执行临时文件操作
先完成目录遍历和文件列表写入,再执行临时文件操作,避免并行竞态:
#!/bin/bash true_f_num=$(for fn in *; do echo; done | wc -l) while :; do echo echo -n '' >.list # 先完成遍历和文件列表写入 for fn in *; do echo "$fn" >>.list done # 串行执行临时文件操作,此时通配符展开已完成,不会干扰 cat .list > .tmp; rm .tmp [ $(cat .list | wc -l ) -ne $true_f_num ] && break done | wc -l
方案2:将临时文件放在非9P挂载目录
把临时文件创建在/tmp这类原生Linux文件系统目录下,避免干扰当前9P目录的遍历:
done | cat > /tmp/.tmp; rm /tmp/.tmp
内容的提问来源于stack exchange,提问作者daubmannus

