Bash中的循环子shell困境

use*_*748 24 bash shell

我想计算给定目录中的所有*bin文件.最初我正在使用for-loop:

var=0
for i in *ls *bin
do
   perform computations on $i ....
   var+=1
done
echo $var
Run Code Online (Sandbox Code Playgroud)

但是,在某些目录中有太多文件导致错误: Argument list too long

因此,我正在尝试使用管道while-loop:

var=0
ls *.bin | while read i;
do
  perform computations on $i
  var+=1
done
echo $var
Run Code Online (Sandbox Code Playgroud)

现在的问题是使用管道子壳创建.因此,echo $var回报0.
我该如何处理这个问题?
原始代码:

#!/bin/bash

function entropyImpl {
    if [[ -n "$1" ]]
    then
        if [[ -e "$1" ]]
        then
            echo "scale = 4; $(gzip -c ${1} | wc -c) / $(cat ${1} | wc -c)" | bc
        else
            echo "file ($1) not found"
        fi
    else
        datafile="$(mktemp entropy.XXXXX)"
        cat - > "$datafile"
        entropy "$datafile"
        rm "$datafile"
    fi

    return 1
}
declare acc_entropy=0
declare count=0

ls *.bin | while read i ;
do  
    echo "Computing $i"  | tee -a entropy.txt
    curr_entropy=`entropyImpl $i`
    curr_entropy=`echo $curr_entropy | bc`  
    echo -e "\tEntropy: $curr_entropy"  | tee -a entropy.txt
    acc_entropy=`echo $acc_entropy + $curr_entropy | bc`
    let count+=1
done

echo "Out of function: $count | $acc_entropy"
acc_entropy=`echo "scale=4; $acc_entropy / $count" | bc`

echo -e "===================================================\n" | tee -a entropy.txt
echo -e "Accumulated Entropy:\t$acc_entropy ($count files processed)\n" | tee -a entropy.txt
Run Code Online (Sandbox Code Playgroud)

dog*_*ane 52

问题是while循环是在子shell中执行的.while循环终止后,将var丢弃子shell的副本,并回var显父级的原始值(其值未更改).

解决此问题的一种方法是使用Process Substitution,如下所示:

var=0
while read i;
do
  # perform computations on $i
  ((var++))
done < <(find . -type f -name "*.bin" -maxdepth 1)
Run Code Online (Sandbox Code Playgroud)

请查看BashFAQ/024以获取其他解决方法.

请注意,我也替换lsfind因为解析ls不是好习惯.

  • 说"while循环在子shell中执行"是正确的,但在这种情况下有些误导 - 人们可能会认为循环通常在子shell中而不是这种情况.这里的问题是管道右侧的*any*命令将位于子shell中.我们有一个复合命令,即一个循环,只是巧合.(虽然这种巧合是子shell问题表现出来的地方,因为只有复合命令才能为不保持的变量赋值.) (5认同)
  • 说"丢弃"是一个措辞有点不正确. (2认同)
  • 这对于 bash、zsh 和 ksh 是正确的,但不符合 POSIX 标准。例如,将 bash 设置为 POSIX 模式 `set -o posix`,然后尝试这样的命令。你会得到:```意外标记附近出现语法错误`&lt;'``` (2认同)

Dun*_*tos 10

符合POSIX的解决方案是使用管道(p文件).这个解决方案是非常好的,可移植的和POSIX,但在硬盘上写了一些东西.

mkfifo mypipe
find . -type f -name "*.bin" -maxdepth 1 > mypipe &
while read line
do
    # action
done < mypipe
rm mypipe
Run Code Online (Sandbox Code Playgroud)

您的管道是硬盘上的文件.如果您想避免使用无用的文件,请不要忘记将其删除.

  • `trap'rm -rf $ TMPFIFODIR'EXIT; TMPFIFODIR = $(mktemp -d); 在脚本开头的mkfifo $ TMPFIFODIR/mypipe`,以及读取/写入fifo会处理"别忘了删除它"的问题. (5认同)