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

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

Shell variables set inside while loop not visible outside of it

bash

提问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 whileloop 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.

当您通过管道进入whileBash 中的循环时,它会创建一个子外壳。当子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的烦恼之外,它还为您提供了记录代码的理想场所,并且在某种程度上添加了命名空间:请注意,在函数内部您可以使用更短更简单的变量名,而不会失去任何可读性。