【问题标题】:While-loop subshell dilemma in BashBash 中的 While 循环子外壳困境
【发布时间】:2012-11-23 11:41:44
【问题描述】:

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

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

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

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

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

现在的问题是使用管道创建了子壳。因此,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

【问题讨论】:

  • for 循环在 shell 中进行评估,因此它本身不会产生“参数列表太长”。也许您可以返回该代码并修复那里的其他任何错误。 (*ls 看起来像是错位了;也许最初的问题是 useless use of ls?)

标签: bash shell


【解决方案1】:

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

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

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

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

请注意,我还将ls 替换为find,因为parse ls 不是一个好习惯。

【讨论】:

  • 像魅力一样工作。非常感谢!
  • 这对于 bash、zsh 和 ksh 是正确的,但不符合 POSIX。例如,在 POSIX 模式下设置 bash set -o posix,然后尝试这样的命令。你得到:syntax error near unexpected token `&lt;'
  • 说“while 循环在子shell 中执行”是正确的,但在这种情况下有些误导——人们可能会认为循环通常在子shell 中,但事实并非如此。这里的问题是管道右侧的 any 命令将位于子外壳中。我们有一个复合命令,即循环,只是一个巧合。 (虽然这种巧合是 subshel​​l 问题表现出来的地方,因为只有复合命令提供了将值分配给未持久变量的机会。)
  • 很好的答案,救了我的命。虽然我的代码有点复杂,但我自己在这里添加了一个答案:stackoverflow.com/questions/55239979/…
  • 我认为“;”在“while read i”结束时不需要
【解决方案2】:

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

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

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

【讨论】:

  • trap 'rm -rf $TMPFIFODIR' EXIT; TMPFIFODIR=$(mktemp -d); mkfifo $TMPFIFODIR/mypipe 在脚本的开头,读/写该 fifo 将处理“不要忘记删除它”问题。
  • 或者,如果您不介意强制所有内容按顺序运行,并在文件中捕获所有输出,则可以使用实际文件。
【解决方案3】:

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

#!/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

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

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

    done <<-EOT
${passwd}
EOT

【讨论】:

    【解决方案4】:

    这也可以通过 for 循环来完成:

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

    【讨论】:

    • 不,这会产生他们一开始就试图避免的“参数列表太长”错误,并且还会破坏带有空格的文件名(可能具有严重的安全隐患)。
    • 用 find 你不会得到一个“参数列表太长”的错误,只有用 ls。
    • 我的立场是正确的,这似乎避免了“参数列表太长”的错误,至少在最近的 Bash 版本中是这样。空格错误仍然是个问题。
    • for file in *.bin; do 也不会在 bash 中产生“参数列表太长”错误。所以 find 命令不会给聚会带来任何额外的东西。参数列表太长错误来自内核。由于for 是内置的shell,因此不会执行具有长参数列表的命令。见stackoverflow.com/questions/19354870/…
    • 澄清一下,我的“shell builtin”语句最好这样:因为for 是shell builtin,shell 不会执行命令,所以参数列表限制不适用。跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-06
    • 2014-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-07
    • 2018-07-26
    相关资源
    最近更新 更多