【问题标题】:loop with more than one item at a time一次循环多个项目
【发布时间】:2014-11-06 23:38:03
【问题描述】:

我们可以遍历一组项目,一次考虑一个,如下所示:

#!/bin/bash
for i in $( ls ); do
    echo item: $i
done

我们如何在类似的循环中一次处理多个项目?比如:

#!/bin/bash
for i,j,k in $( ls ); do
    echo item i: $i
    echo item j: $j
    echo item k: $k
done

第二个 shell 脚本不正确,但应该准确说明我想要实现的目标。

【问题讨论】:

  • 不要像这样使用ls:改用for i in *; do

标签: bash shell unix xargs


【解决方案1】:

假设您没有 太多 项(尽管 shell 应该能够 处理相当多的位置参数。

# Save the original positional arguments, if you need them
original_pp=( "$@" )
set -- *
while (( $# > 0 )); do
    i=$1 j=$2 k=$3     # Optional; you can use $1, $2, $3 directly
    ...
    shift 3 || shift $#   # In case there are fewer than 3 arguments left
done

# Restore positional arguments, if necessary/desired
set -- "${original_pp[@]}"

为了 POSIX 兼容性,请使用 [ "$#" -gt 0 ] 而不是 ((...)) 表达式。没有简单的方法可以以兼容 POSIX 的方式保存和恢复所有位置参数。 (除非有一个字符可以用来明确地将它们连接成一个字符串。)

这是 jm666 提到的子shell:

(
    set -- *
    while [ "$#" -gt 0 ]; do
        i=$1 j=$2 k=$3
        ...
        shift 3 || shift $#
    done
)

一旦子shell退出,您在子shell中设置的参数的任何更改都将丢失,但上述代码与POSIX兼容。

【讨论】:

  • 您可以使用子shell,只要您不需要设置以后需要的任何参数。
【解决方案2】:

如果文件名不包含空格:

find . -maxdepth 1 | xargs -L 3 | while read i j k; do
  echo item i: $i
  echo item j: $j
  echo item k: $k
done

编辑:

我删除了-print0-0

【讨论】:

  • 实际上,为了绝对安全,您确实需要-print0,以及对read3正确构造的调用。
  • 我认为-print0-0 选项可以帮助处理包含空格的文件名。
  • @SzG 但仅当读取将它们作为空值终止时。否则,这是没有意义的。当使用xargs 时,您无法读取(使用read)null 终止...所以,-print0 和正确的read-print|xargs... 不是两者。
【解决方案3】:

回答有点晚,我会用非壮观的方式来做这件事:),比如:

while read -r -d $'\0' f1
do
        read -r -d $'\0' f2
        read -r -d $'\0' f3

        echo "==$f1==$f2==$f3=="

done < <(find test/ ... findargs... -print0)

【讨论】:

    【解决方案4】:

    要获取to get n items a time from the list,我认为您想从数组中获取n 项。

    像这样使用它:

    n=3
    arr=(a b c d e)
    
    echo "${arr[@]:0:$n}"
    a b c
    

    【讨论】:

      【解决方案5】:

      这是另一种典型编程方式的解决方案->

      #!/bin/bash
      shopt -s nullglob
      arr=(*) #read the files/dirs in an array
      total=${#arr[@]} #get the array size
      count=0;
      #loop it thru in multiples of three
      while [ $count -lt $((total-2)) ]
      do
              echo "i is ${arr[$count]}"
              echo "j is ${arr[$((count+1))]}"
              echo "k is ${arr[$((count+2))]}"
              count=$((count+3))
      done
      #print the remaining element(s)
      rem=$((total%3));    
      if [ $rem -eq 1 ];
      then
              echo "i is ${arr[$total-1]}"
      elif [ $rem -eq 2 ];
      then
              echo "i is ${arr[$total-2]}"
              echo "j is ${arr[$total-1]}"
      fi
      echo "Done"
      

      【讨论】:

        【解决方案6】:

        如果你有 GNU Parallel,你可以运行:

        ls | parallel -N3 "echo item i: {1}; echo item j: {2}; echo item k: {3}"
        

        所有新计算机都有多个内核,但大多数程序本质上是串行的,因此不会使用多个内核。然而,许多任务是极其可并行化的:

        • 对多个文件运行相同的程序
        • 为文件中的每一行运行相同的程序
        • 为文件中的每个块运行相同的程序

        GNU Parallel 是一种通用的并行化器,可以轻松地在同一台机器上或在您可以通过 ssh 访问的多台机器上并行运行作业。

        如果您想在 4 个 CPU 上运行 32 个不同的作业,那么并行化的直接方法是在每个 CPU 上运行 8 个作业:

        GNU Parallel 会在完成后生成一个新进程 - 保持 CPU 处于活动状态,从而节省时间:

        安装

        个人安装不需要 root 访问权限。这样做可以在 10 秒内完成:

        (wget -O - pi.dk/3 || curl pi.dk/3/ || fetch -o - http://pi.dk/3) | bash
        

        有关其他安装选项,请参阅http://git.savannah.gnu.org/cgit/parallel.git/tree/README

        了解详情

        查看更多示例:http://www.gnu.org/software/parallel/man.html

        观看介绍视频:https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

        浏览教程:http://www.gnu.org/software/parallel/parallel_tutorial.html

        注册电子邮件列表以获得支持:https://lists.gnu.org/mailman/listinfo/parallel

        【讨论】:

        • 我不认为从互联网上运行随机代码(如果它通过普通的 http,而不是 https 更是如此)是一个好建议。 (是的,我说的是这个“安装程序”,而不是安装的软件。)
        • 一般来说这是个好建议:最好通过通过数字签名保护的路径(例如 apt)进行安装。但是有些人没有那么奢侈:也许他们没有 root 访问权限,也许他们使用的发行版没有打包 GNU Parallel。对于这些人来说,无论是直接从 Internet 运行安装程序还是运行已安装的程序,在安全方面都没有任何区别:其中任何一个都可能包含后门。
        • 至少为安装程序提供一个 https URL,因此如果他们(正确地)信任您的站点(及其证书),他们将不会获得被 MitM 攻击者替换的版本。跨度>
        【解决方案7】:

        您可以使用xargsawksedpaste 来重组您的输入。

        job_select()
        {
          ls
        }
        
        job_process()
        {
          while read i j k; do
            printf 'item i: %s\nitem j: %s\nitem k: %s\n' "$i" "$j" "$k"
          done
        }
        
        job_restructure_xargs()
        {
          xargs -L 3
        }
        
        job_rstructure_awk()
        {
          awk '(NR % 3 == 1) { i = $0 } (NR % 3 == 2) { j = $0 } (NR % 3 == 0){ k = $0; print(i,j,k)}'
        }
        
        job_restructure_sed()
        {
          sed -e 'N;N;s/\n/ /g'
        }
        
        job_restructure_paste()
        {
          paste - - -
        }
        

        然后是任意组合

        job_select | job_restructure_xargs | job_process
        job_select | job_restructure_awk | job_process
        job_select | job_restructure_sed | job_process
        job_select | job_restructure_paste | job_process
        

        做你想做的。

        【讨论】:

        • 不要解析ls的输出,尤其是在这样一个Rube-Goldberg-esque框架中。这不适用于包含空格的文件名。
        • 我只是假设解析ls 输出与 OP 假设的一样安全。在 shell 编程中,数据处理是通过过滤器来执行的,所以这里没有像 Rube-Goldberg 这样的东西,只是常规的 shell 编程。也就是说,你的答案要好得多!
        • 我收回了 Rube Goldberg 的评论;我看的不够仔细,也没有注意到你的三个 job_restructure_* 函数只是同一任务的 3 个替代实现。但是,最好抑制在ls 出现的任何地方解析它的冲动。
        猜你喜欢
        • 1970-01-01
        • 2020-07-10
        • 2019-06-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多