11

我是 shell 脚本的新手。如果我的怀疑太愚蠢,请多多包涵。

我在 2 个不同的目录中有 png 图像和一个可执行文件,它从每个目录获取图像并处理它们以生成新图像。

我正在寻找一个可以同时采用两个变量的 for 循环构造。这在 C、C++ 等中是可能的,但我该如何完成以下操作。代码显然是错误的。

#!/bin/sh

im1_dir=~/prev1/*.png  
im2_dir=~/prev3/*.png
index=0

for i,j in $im1_dir $im2_dir  # i iterates in im1_dir and j iterates in im2_dir 
do
  run_black.sh $i $j  
done

谢谢!

4

8 回答 8

18

如果您依赖于两个目录以根据区域设置排序顺序进行匹配(如您的尝试),那么数组应该可以工作。

im1_files=(~/prev1/*.png)
im2_files=(~/prev3/*.png)

for ((i=0;i<=${#im1_files[@]};i++)); do
   run_black.sh "${im1_files[i]}" "${im2_files[i]}"
done
于 2012-06-26T20:06:30.507 回答
7

这里有一些额外的方法来做你正在寻找的关于利弊的注释。

以下仅适用于不包含换行符的文件名。它以同步方式配对文件。它使用额外的文件描述符从第一个列表中读取。如果im1_dir包含更多文件,循环将在im2_dir用完时停止。如果im2_dir包含更多文件,file1则对于所有不匹配的file2. 当然,如果它们包含相同数量的文件,则没有问题。

#!/bin/bash
im1_dir=(~/prev1/*.png) 
im2_dir=(~/prev3/*.png)

exec 3< <(printf '%s\n' "${im1_dir[@]}")

while IFS=$'\n' read -r -u 3 file1; read -r file2
do
    run_black "$file1" "$file2"
done < <(printf '%s\n' "${im1_dir[@]}")

exec 3<&-

您可以使行为保持一致,以便循环仅以非空匹配文件停止,无论哪个列表更长,方法是用双&符号替换分号,如下所示:

while IFS=$'\n' read -r -u 3 file1 && read -r file2

此版本使用for循环而不是while循环。当两个列表中较短的一个用完时,这个停止。

#!/bin/bash
im1_dir=(~/prev1/*.png) 
im2_dir=(~/prev3/*.png)

for ((i = 0; i < ${#im1_dir[@]} && i < ${#im2_dir[@]}; i++))
do
    run_black "${im1_dir[i]}" "${im2_dir[i]}"
done

此版本与上面的版本类似,但如果其中一个列表用完,它会环绕以重复使用这些项目,直到另一个列表用完。它非常难看,你可以用另一种更简单的方式做同样的事情。

#!/bin/bash
im1_dir=(~/prev1/*.png) 
im2_dir=(~/prev3/*.png)

for ((i = 0, j = 0,
          n1 = ${#im1_dir[@]}, 
          n2 = ${#im2_dir[@]}, 
          s = n1 >= n2 ? n1 : n2, 
          is = 0, js = 0; 

      is < s && js < s; 

      i++, is = i, i %= n1, 
          j++, js = j, j %= n2))
do
    run_black "${im1_dir[i]}" "${im2_dir[i]}"
done

此版本仅将数组用于内部循环(第二个目录)。它只会执行与第一个目录中的文件一样多的次数。

#!/bin/bash
im1_dir=~/prev1/*.png
im2_dir=(~/prev3/*.png)

for file1 in $im1_dir
do
    run_black "$file1" "${im2_dir[i++]}"
done
于 2012-06-27T01:36:37.927 回答
4

如果您不介意走老路(bash),工具命令语言(TCL)有这样一个循环结构:

#!/usr/bin/env tclsh

set list1 [glob dir1/*]
set list2 [glob dir2/*]

foreach item1 $list1 item2 $list2 {
    exec command_name $item1 $item2
}

基本上,循环读取:对于从 list1 中取出的每个 item1,从 list2 中取出 item2command_name然后,您可以用自己的命令替换。

于 2012-06-26T21:03:09.620 回答
0

这可能是在同一个循环中使用两个变量的另一种方式。但是您需要知道目录中的文件总数(或者,您想要运行循环的次数)才能将其用作迭代的值i

获取目录中的文件数:

ls /path/*.png | wc -l

现在运行循环:

im1_dir=(~/prev1/*.png) 
im2_dir=(~/prev3/*.png)

for ((i = 0; i < 4; i++)); do run_black.sh ${im1_dir[i]} ${im2_dir[i]}; done

如需更多帮助,请参阅此讨论

于 2015-10-17T23:13:18.557 回答
0

很简单,你可以在这个问题中使用两个 for 循环函数。

#bin bash
index=0
for i in ~/prev1/*.png  
do
    for j ~/prev3/*.png
    do 
        run_black.sh $i $j
    done
done
于 2016-01-09T05:30:34.093 回答
0

对于我想要同时达到顶部和底部范围的类似情况,我遇到了这个问题。这是我的解决方案;它不是特别有效,但它既简单又干净,而且对于 icky BASH 数组和所有那些废话来说一点也不复杂。

SEQBOT=$(seq 0  5  $((PEAKTIME-5)))
SEQTOP=$(seq 5  5  $((PEAKTIME-0)))

IDXBOT=0
IDXTOP=0

for bot in $SEQBOT; do
    IDXTOP=0
    for top in $SEQTOP; do
        if [ "$IDXBOT" -eq "$IDXTOP" ]; then
            echo $bot $top
        fi
        IDXTOP=$((IDXTOP + 1))
    done
    IDXBOT=$((IDXBOT + 1))
done
于 2017-06-13T19:38:03.093 回答
0

可以使用${!array[@]}迭代array's 索引的语法进一步简化接受的答案:

a=(x y z); b=(q w e); for i in ${!a[@]}; do echo ${a[i]}-${b[i]}; done
于 2021-07-30T19:29:38.280 回答
-1

另一种解决方案。两个带有文件名的列表被粘贴到一个列表中。

paste <(ls --quote-name ~/prev1/*.png) <(ls --quote-name ~/prev3/*.png) | \
while read args ; do
  run_black $args
done  
于 2012-06-27T10:27:53.447 回答