【问题标题】:Using a loop to select items in a directory使用循环选择目录中的项目
【发布时间】:2019-11-28 15:40:04
【问题描述】:

我有一个包含几个大文件的目录。每个文件都成对出现,我想每次选择两个文件时使用 bash 循环,在它们上运行命令行工具,然后继续处理下一对文件。

我的目录应该是:file1、file2、file3、file4、file5、file6

然后我会拿 file1 和 file2,做一些事情,拿 file3 和 file4,做一些事情等等。

我只为单个文件做到了这一点:

for file_name in dir_name; do something; done

【问题讨论】:

  • 如何准确确定一对?这是什么逻辑?
  • 文件的顺序已经正确。因此,如果我是正确的,就可以使用索引 0 和 1、2 和 3 等

标签: bash loops file directory


【解决方案1】:

回到基本的 for 循环,没有数组等。适用于任何 shell(不依赖 bash 功能)。

捕获一对中第一个文件的名称,并在第二个文件上执行命令。

first=
for file_name in dir_name/* ; do
    if [ "$first" ] ; then
        # 2nd entry - pair
        do-something "$first" "$file_name"
        first=
    else
        # First entry - just remember.
        first=$file_name
    fi
done

【讨论】:

    【解决方案2】:

    一种方法是将它们放在一个数组中并构建对,如下所示:

    files=(*)  ## assuming you're in current dir
    for i in $(seq 0 $((${#files[@]}-1)))
    do 
         if [[ $(( $i % 2)) == 0 ]]  
         then 
            pair="${files[$i]} ${files[$i+1]}"  
            echo "$pair" 
            # do what you want with the pair here
         fi  
    done
    

    【讨论】:

      【解决方案3】:

      如果您知道文件的名称和数量,您可以这样做:

      #!/bin/bash
      
      limit=20
      for ((i=0; i < limit; i+=2 )) {
          echo "file${i} file$(( i + 1))"
      }
      

      输出

      file0 file1
      file2 file3
      file4 file5
      file6 file7
      file8 file9
      file10 file11
      file12 file13
      file14 file15
      file16 file17
      file18 file19 
      

      假设你不知道文件名,你可以使用这个 Ruby 脚本:

      #!/usr/bin/ruby
      
      require 'find'
      
      search_in='.'
      files = []
      Find.find(search_in) do |path|
        files << path if path =~ /.*\.txt$/
      end
      
      files.sort_by!{|f| f.scan(/[0-9]+/)[0].to_i }
      
      files.each_slice(2) do |a, b|
          system("echo #{a} #{b}")
      end
      

      只需将 echo 更改为您想要的任何内容。

      希望对你有帮助!

      【讨论】:

        【解决方案4】:

        假设您的文件名不包含空格或引号:

        ls dir_name \
            | xargs -L 2 \
            | while read FILE1 FILE2; do \
                printf "file1 %s file2 %s\n" "$FILE1" "$FILE2"
            done
        

        例子:

        $ ls
        a  b  c  d  e  f
        $ ls . \
            | xargs -L 2 \
            | while read FILE1 FILE2; do \
                printf "file1 %s file2 %s\n" "$FILE1" "$FILE2"
            done
        file1 a file2 b
        file1 c file2 d
        file1 e file2 f
        

        注 1:

        由于管道,printf 的每次执行都在另一个 shell 进程中。设置变量不适用于循环迭代。如果你想这样做,你可以将所有行读入一个数组中

        readarray -t FILES < <(ls dir_name | xargs -L 2)
        

        然后迭代数组

        COUNT=0
        for LINE in "${FILES[@]}"; do
            FILE1="${LINE%% *}"
            FILE2="${LINE##* }"
            printf "file1 %s file2 %s\n" "$FILE1" "$FILE2"
            ((COUNT++))
        done
        

        这允许您设置变量,例如COUNT 跨迭代,但是,当您想要三胞胎文件时,它会占用更多内存并停止工作。

        您可以将数组用于任意文件元组:

        COUNT=0
        for LINE in "${FILES[@]}"; do
            TUPLE=( $LINE ) # note: no quotes
            printf "file1 %s file2 %s\n" "${TUPLE[0]}" "${TUPLE[1]}"
            ((COUNT++))
        done
        

        注2:

        也可以使用readarray的回调机制:

        COUNT=0
        callback() 
        { 
            TUPLE=( $2 ) # note: no quotes
            printf "file1 %s file2 %s\n" "${TUPLE[0]}" "${TUPLE[1]}"
            ((COUNT++))
        }
        ...
        readarray -t -C callback -c 1 FILES < <(ls dir_name | xargs -L 2)
        

        【讨论】:

          【解决方案5】:

          如果您想严格处理文件名中的特殊字符, 怎么样:

          while IFS= read -r -d "" f; do
              ary+=("$f")
          done < <(find "dir_name" -type f -print0 | sort -z)
          
          for ((i=0; i<${#ary[@]}; i+=2 )); do
              echo "${ary[i]}" "${ary[i+1]}"
              # or some_command "${ary[i]}" "${ary[i+1]}"
          done
          

          它允许文件名包含空格、制表符、换行符、引号或 任何其他特殊字符。
          (虽然有些人不喜欢这种严肃的态度:-/)

          希望这会有所帮助。

          【讨论】:

            【解决方案6】:

            我属于“使用数组”阵营,但我不会使用findseq 等外部工具填充或解析数组。你需要的一切都已经在 bash 中了。

            files=( * )
            
            for ((i=0; i<${#files[@]}; i+=2)); do
              printf '%s / %s\n' "${files[i]}" "${files[$((i+1))]}"
            done
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2021-12-14
              • 1970-01-01
              • 2016-03-20
              • 1970-01-01
              • 1970-01-01
              • 2013-11-26
              • 1970-01-01
              相关资源
              最近更新 更多