Bash 中的 While 循环子外壳困境

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13726764/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 23:03:51  来源:igfitidea点击:

While-loop subshell dilemma in Bash

bashshell

提问by user1192748

i want to compute all *bin files inside a given directory. Initially I was working with a for-loop:

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

var=0
for i in *ls *bin
do
   perform computations on $i ....
   var+=1
done
echo $var

However, in some directories there are too many files resulting in an error: Argument list too long

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

Therefore, I was trying it with a piped while-loop:

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

var=0
ls *.bin | while read i;
do
  perform computations on $i
  var+=1
done
echo $var

The problem now is by using the pipe subshells are created. Thus, echo $varreturns 0.
How can I deal with this problem?
The original Code:

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

#!/bin/bash

function entropyImpl {
    if [[ -n "" ]]
    then
        if [[ -e "" ]]
        then
            echo "scale = 4; $(gzip -c  | wc -c) / $(cat  | wc -c)" | bc
        else
            echo "file () 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

回答by dogbane

The problem is that the while loop is executed in a subshell. After the while loop terminates, the subshell's copy of varis discarded, and the original varof the parent (whose value is unchanged) is echoed.

问题在于 while 循环是在子 shell 中执行的。while 循环结束后,var丢弃子shell 的副本,并回var显父级的原始副本(其值不变)。

One way to fix this is by using Process Substitutionas shown below:

解决此问题的一种方法是使用进程替换,如下所示:

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

Take a look at BashFAQ/024for other workarounds.

查看BashFAQ/024以了解其他解决方法。

Notice that I have also replaced lswith findbecause it is not good practice to parse ls.

请注意,我也替换了lsfind因为解析ls.

回答by Dunatotatos

A POSIX compliant solution would be to use a pipe (p file). This solution is very nice, portable, and POSIX, but writes something on the hard disk.

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

mkfifo mypipe
find . -type f -name "*.bin" -maxdepth 1 > mypipe &
while read line
do
    # action
done < mypipe
rm mypipe

Your pipe is a file on your hard disk. If you want to avoid having useless files, do not forget to remove it.

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

回答by oliver

So researching the generic issue, passing variables from a sub-shelled while loop to the parent. One solution I found, missing here, was to use a here-string. As that was bash-ish, and I preferred a POSIX solution, I found that a here-string is really just a shortcut for a here-document. With that knowledge at hand, I came up with the following, avoiding the subshell; thus allowing variables to be set in the loop.

因此,研究通用问题,将变量从子壳 while 循环传递给父级。我找到的一个解决方案,在这里缺少,是使用这里的字符串。因为那是 bash-ish,而且我更喜欢 POSIX 解决方案,所以我发现 here-string 实际上只是 here-document 的快捷方式。有了这些知识,我想出了以下内容,避免使用子shell;从而允许在循环中设置变量。

#!/bin/sh

set -eu

passwd="username,password,uid,gid
root,admin,0,0
john,appleseed,1,1
jane,doe,2,2"

main()
{
    while IFS="," read -r _user _pass _uid _gid; do
        if [ "${_user}" = "${1:-}" ]; then
            password="${_pass}"
        fi
    done <<-EOT
        ${passwd}
    EOT

    if [ -z "${password:-}" ]; then
        echo "No password found."
        exit 1
    fi

    echo "The password is '${password}'."
}

main "${@}"

exit 0

One important note to all copy pasters, is that the here-document is setup using the hyphen, indicating that tabs are to be ignored. This is needed to keep the layout somewhat nice. It is important to note, because stackoverflow doesn't render tabs in 'code' and replaces them with spaces. Grmbl. SO, don't mangle my code, just cause you guys favor spaces over tabs, it's irrelevant in this case!

对所有复制粘贴者的一个重要说明是,here-document 是使用连字符设置的,表示将忽略选项卡。这需要保持布局有点好。重要的是要注意,因为 stackoverflow 不会在“代码”中呈现选项卡并用空格替换它们。咕噜噜。所以,不要破坏我的代码,只是因为你们喜欢空格而不是制表符,在这种情况下无关紧要!

This probably breaks on different editor(settings) and what not. So the alternative would be to have it as:

这可能会在不同的编辑器(设置)上中断,而不会中断。因此,另一种选择是将其设为:

    done <<-EOT
${passwd}
EOT

回答by Thomas K.

This could be done with a for loop, too:

这也可以用 for 循环来完成:

var=0;
for file in `find . -type f -name "*.bin" -maxdepth 1`; do 
    # perform computations on "$i"
    ((var++))
done 
echo $var