【问题标题】:How to loop over files in natural order in Bash?如何在 Bash 中以自然顺序循环文件?
【发布时间】:2023-04-04 14:21:01
【问题描述】:

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

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

但是,我按这个顺序得到它们

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

而不是

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

什么是自然秩序。

我尝试过排序命令,但无济于事。

【问题讨论】:

    标签: bash natural-sort


    【解决方案1】:
    readarray -d '' entries < <(printf '%s\0' *.fas | sort -zV)
    for entry in "${entries[@]}"; do
      # do something with $entry
    done
    

    printf '%s\0' *.fas 产生一个 NUL 分隔的目录条目列表,扩展名为 .fassort -zV 按自然顺序对它们进行排序。

    请注意,您需要安装 GNU sort 才能使其工作。

    【讨论】:

      【解决方案2】:

      使用选项sort -g它根据一般数值比较

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

      0.log 1.日志 2.日志 ... 10.日志 11.日志

      这只有在文件名是数字的情况下才有效。如果它们是字符串,您将按字母顺序获取它们。例如:

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

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

      【讨论】:

        【解决方案3】:

        您将按 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)。

        【讨论】:

          【解决方案4】:

          您的意思是在您的列表中编号为 10 的文件排在编号为 3 的文件之前?那是因为ls 对其结果的排序非常简单,所以something-10.whateversomething-3.whatever

          一种解决方案是重命名所有文件,使它们具有相同的位数(其中单个数字的文件以0 开头)。

          【讨论】:

          • 是的。我明白了,谢谢。但是我没有命名文件,只是下载了它们:)
          • 试试sort -n。这会按数字顺序对连续的数字块进行排序。虽然“10a”仍然在“1a”之前,但至少“1-a”在“10-a”之前。
          【解决方案5】:
          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 的输出并且不会成为管道的受害者- to-while 问题。

          【讨论】:

            【解决方案6】:

            像@Kusalananda 的解决方案(也许更容易记住?)但适合所有文件(?):

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

            本质上是添加排序键,排序,移除排序键。

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

            【讨论】:

            • 我宁愿远离非标准标志。在 Mac OSX 上,ls -v 是“强制未经编辑打印非图形字符;这是不输出到终端时的默认设置。”。在 OpenBSD 上,它不存在。
            • 我没说你做过。我只是在评论你提到ls -v
            【解决方案7】:

            使用 sort -rh 和 while 循环

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2018-08-20
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-03-27
              相关资源
              最近更新 更多