如何在Bash中按自然顺序循环遍历文件?

62

我正在使用以下命令遍历目录中的所有文件:

for i in *.fas; do some_code; done;

然而,我按照这个顺序获取到它们

vvchr1.fas  
vvchr10.fas  
vvchr11.fas
vvchr2.fas
...

替代

vvchr1.fas
vvchr2.fas
vvchr3.fas
...

什么是自然顺序。

我尝试过 sort 命令,但没有成功。

7个回答

119
readarray -d '' entries < <(printf '%s\0' *.fas | sort -zV)
for entry in "${entries[@]}"; do
  # do something with $entry
done

执行 printf '%s\0' *.fas 命令会返回一个以 NUL 分隔的目录列表,该列表包含后缀为 .fas 的文件名。然后使用 sort -zV 命令按自然排序对它们进行排序。

请注意,需要安装 GNU sort 才能使此方法生效。


11

通过选项sort -g进行排序时,它会按照一般数值进行比较。

 for FILE in `ls ./raw/ | sort -g`; do echo "$FILE"; done

0.log 1.log 2.log ... 10.log 11.log

只有当文件名为数字时才能正常工作。如果它们是字符串,则会按字母顺序排序。例如:

 for FILE in `ls ./raw/* | sort -g`; do echo "$FILE"; done

原始/0.log 原始/10.log 原始/11.log ... 原始/2.log


4

您将按ASCII顺序获取文件。这意味着vvchr10*会在vvchr2*之前出现。我了解您不能重命名文件(我的生物信息学大脑告诉我它们包含染色体数据,我们根本不称染色体1为“chr01”),所以这里有另一种解决方案(不使用sort -V,因为我在使用的任何操作系统中都找不到):

ls *.fas | sed 's/^\([^0-9]*\)\([0-9]*\)/\1 \2/' | sort -k2,2n | tr -d ' ' |
while read filename; do
  # do work with $filename
done

这有点复杂,并且不能处理包含空格的文件名。

另一种解决方案:假设我们希望按大小顺序迭代文件,这可能对某些生物信息学任务更合适:

du *.fas | sort -k2,2n |
while read filesize filename; do
  # do work with $filename
done

为了反向排序,只需在 -k2,2n 后添加 r (得到 -k2,2nr)。

2
while IFS= read -r file ; do
    ls -l "$file" # or whatever
done < <(find . -name '*.fas' 2>/dev/null | sed -r -e 's/([0-9]+)/ \1/' | sort -k 2 -n | sed -e 's/ //;')

解决问题,假设文件命名保持一致,不依赖于非常新的GNU sort版本,不依赖于读取ls的输出,并且不会遇到管道到while循环的问题。

谢谢你的回答。在<(find . -name '*.fas' 2>/dev/null | sed -r -e 's/([0-9]+)/ \1/' | sort -k 2 -n | sed -e 's/ //;')的结尾,你忘记了一个括号。 - Andrew
@Andrew:在十多年的时间里,你是第一个注意到这个问题的人。这个问题已经被解决了。 - sorpigal

2
你的意思是在你的列表中,编号为10的文件会排在编号为3的文件之前?这是因为ls按照非常简单的方式对结果进行排序,因此something-10.whateversomething-3.whatever
一种解决方法是重命名所有文件,使它们具有相同数量的数字(带有单个数字的文件数字前面加上0)。

是的,我明白了,谢谢。不过我没有给文件命名,只是下载了它们 :) - Perlnika
1
尝试使用 sort -n 命令。它可以按照数字顺序排序连续的数字块。虽然 "10a" 仍然在 "1a" 之前,但至少 "1-a" 在 "10-a" 之前。 - Ben

0

像@Kusalananda的解决方案(可能更容易记住?)但适用于所有文件(?):

array=("$(ls |sed 's/[^0-9]*\([0-9]*\)\..*/\1 &/'| sort -n | sed 's/^[^ ]* //')")
for x in "${array[@]}";do echo "$x";done

本质上添加排序键,排序,删除排序键。

编辑:将评论移动到适当的解决方案


1
我宁愿远离非标准标志。在Mac OSX上,ls -v是“强制未编辑的打印非图形字符;当输出不到终端时,这是默认设置。” 在OpenBSD上,它不存在。 - Kusalananda
我并没有说你这样做了。我只是在评论你提到 ls -v - Kusalananda

0

使用 sort -rh 和 while 循环

du -sh * | sort -rh | grep -P "avi$" |awk '{print $2}' | while read f; do fp=`pwd`/$f; echo $fp; done;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接