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
While-loop subshell dilemma in Bash
提问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 $var
returns 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 var
is discarded, and the original var
of 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 ls
with find
because it is not good practice to parse ls
.
请注意,我也替换了ls
,find
因为解析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