如何安全地按修改时间顺序遍历glob匹配的文件?
安全实现按修改时间逆序遍历.tar文件的Bash方案
问题背景
我在Bash脚本中使用以下代码遍历.tar文件:
for i in $(ls -tr *.tar); do # 处理逻辑 done
运行shellcheck时收到如下警告:
for i in $(ls -tr *.tar); do
------------- SC2045 (warning): Iterating over ls output is fragile. Use globs.
直接使用glob无法实现ls -tr的排序效果(按修改时间逆序,最新文件排在最后),同时不想采用那些看起来更脆弱的find/sort/awk组合方案,求Linux标准coreutils环境下的安全实现方法。
解决方案
方法一:stat+while循环(适合大多数场景)
利用stat获取文件时间戳并排序,配合安全的read命令遍历,能处理含特殊字符的文件名:
stat -c "%Y %n" *.tar | sort -n | awk '{print $2}' | while IFS= read -r file; do # 这里写入你的文件处理逻辑,例如: echo "正在处理:$file" done
各部分作用:
stat -c "%Y %n" *.tar:输出每个.tar文件的秒级修改时间戳和文件名,格式为「时间戳 文件名」sort -n:按时间戳从小到大排序,对应最早修改的文件在前,和ls -tr的顺序完全一致awk '{print $2}':提取排序后的文件名部分while IFS= read -r file:安全读取文件名,即使文件名包含空格、换行符等特殊字符也能正确识别
方法二:stat+数组(适合需要修改外部变量的场景)
如果需要在循环内修改脚本的外部变量(避免while子shell的变量隔离问题),可以先将排序后的文件名存入数组再遍历:
# 将排序后的文件名存入数组 mapfile -t sorted_files < <(stat -c "%Y %n" *.tar | sort -n | awk '{print $2}') # 遍历数组处理文件 for file in "${sorted_files[@]}"; do # 你的处理逻辑 echo "正在处理:$file" done
各部分作用:
mapfile -t sorted_files:将命令输出的每一行存入数组sorted_files,-t参数自动去除每行末尾的换行符< <(...):进程替换,让mapfile直接读取命令输出,避免子shell导致的变量无法向外传递问题
备选方案:ls+eval(不推荐但可用)
如果依赖GNU coreutils的ls,可以用其引号格式化功能安全输出文件名,再通过eval存入数组:
eval "files=($(ls -tr --quoting-style=shell-always *.tar))" for file in "${files[@]}"; do # 处理逻辑 done
该方案依赖GNU ls的特定参数,且使用了eval存在潜在风险,仅作为特殊场景下的备选。
内容的提问来源于stack exchange,提问作者Karel Bílek
相关产品推荐
相关产品推荐

