bash 设置在 while 循环内部的 Shell 变量在其外部不可见
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4667509/
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
Shell variables set inside while loop not visible outside of it
提问by Mark
I am trying to find the pathname with the most characters in it. There might be better ways to do this. But I would like to know why this problem occurs.
我试图找到包含最多字符的路径名。可能有更好的方法来做到这一点。但我想知道为什么会出现这个问题。
LONGEST_CNT=0
find samples/ | while read line
do
line_length=$(echo $line | wc -m)
if [[ $line_length -gt $LONGEST_CNT ]]
then
LONGEST_CNT=$line_length
LONGEST_STR=$line
fi
done
echo $LONGEST_CNT : $LONGEST_STR
It somehow always returns:
它以某种方式总是返回:
0 :
If I print the results for debugging inside the while loop the values are correct. So why bash does not make these variables global?
如果我在 while 循环内打印调试结果,则值是正确的。那么为什么 bash 不使这些变量成为全局变量呢?
回答by Paused until further notice.
When you pipe into a while
loop in Bash, it creates a subshell. When the subshell exits, all variables return to their previous values (which may be null or unset). This can be prevented by using process substitution.
当您通过管道进入while
Bash 中的循环时,它会创建一个子外壳。当子shell退出时,所有变量都返回到它们以前的值(可能为空或未设置)。这可以通过使用进程替换来防止。
LONGEST_CNT=0
while read -r line
do
line_length=${#line}
if (( line_length > LONGEST_CNT ))
then
LONGEST_CNT=$line_length
LONGEST_STR=$line
fi
done < <(find samples/ ) # process substitution
echo $LONGEST_CNT : $LONGEST_STR
回答by mivk
The "correct" reply is given by Dennis. However, I find the process substitution trick extremely unreadable if the loop contains more than a few lines. When reading a script, I want to see what goes into the pipe before I see how it is processed.
Dennis给出了“正确”的答复。但是,如果循环包含多于几行,我发现进程替换技巧非常难以理解。在阅读脚本时,我想先查看管道中的内容,然后再查看它是如何处理的。
So I usually prefer this trick of encapsulating the while loop in "{}".
所以我通常更喜欢这种将 while 循环封装在“{}”中的技巧。
LONGEST_CNT=0
find /usr/share/zoneinfo | \
{ while read -r line
do
line_length=${#line}
if (( line_length > LONGEST_CNT ))
then
LONGEST_CNT=$line_length
LONGEST_STR=$line
fi
done
echo $LONGEST_CNT : $LONGEST_STR
}
回答by grebneke
About finding the longest pathname. Here's an alternative:
关于找到最长的路径名。这是一个替代方案:
find /usr/share/zoneinfo | while read line; do
echo ${#line} $line
done | sort -nr | head -n 1
# Result:
58 /usr/share/zoneinfo/right/America/Argentina/ComodRivadavia
Forgive me if this is considered off topic, I hope it helps someone.
如果这被认为是题外话,请原谅我,我希望它可以帮助某人。
回答by Alois Mahdal
Do what you always (should) do:
做你一直(应该)做的:
- separate concerns,
- avoid globals,
- document your code,
- be readable,
- maybe be POSIXy.
- 单独的关注点,
- 避免全局变量,
- 记录您的代码,
- 可读,
- 也许是POSIXy。
(Yes, I added a bit more "good practice" ingredients to the soup than absolutely necessary ;))
(是的,我在汤中添加了比绝对必要的更多的“良好做法”成分;))
So my favorite "knee-jerk reaction" to problems with invisible subshell is to use function:
所以我最喜欢对隐形子外壳问题的“下意识反应”是使用函数:
#!/bin/sh
longest() {
#
# Print length and body of the longest line in STDIN
#
local cur_ln # current line
local cur_sz # current size (line length)
local max_sz # greatest size so far
local winner # longest string so far
max_sz=0
while read -r cur_ln
do
cur_sz=${#cur_ln}
if test "$cur_sz" -gt "$max_sz";
then
max_sz=$cur_sz
winner=$cur_ln
fi
done
echo "$max_sz" : "$winner"
}
find /usr/share/zoneinfo | longest
# ok, if you really wish to use globals, here you go ;)
LONGEST_CNT=0
LONGEST_CNT=$(
find /usr/share/zoneinfo \
| longest \
| cut -d: -f1 \
| xargs echo\
)
echo "LONGEST_CNT='$LONGEST_CNT'"
Aside from avoiding the subshell annoyance, it gives you perfect place to document the code, and sort-of adds namespacing: notice that inside function you can use much shorter and simpler variable names without losing any of readability.
除了避免子shell的烦恼之外,它还为您提供了记录代码的理想场所,并且在某种程度上添加了命名空间:请注意,在函数内部您可以使用更短更简单的变量名,而不会失去任何可读性。