Bash:变量赋值似乎并没有“坚持”

Bash:变量赋值似乎并没有“坚持”

当测试在 while 循环内失败时,我很难追踪我的“布尔”标志变量不会保持 false 的原因。

该脚本涉及几个循环,但本质上它的目的是每天早上在 cron 触发时播放两张随机专辑。我曾经有一个简单的单行脚本,可以选择两张专辑,但我想将我不想播放的任何专辑列入黑名单(例如圣诞音乐专辑)。

为此,我让它读取了一个wakeUp_blacklist.txt 文件,其中包含我不想听到的专辑名称,因此它可以拒绝任何匹配项。这会将列入黑名单的专辑与随机选择的一张专辑逐行进行比较,并测试是否匹配。如果找到匹配项,它会将匹配标记为失败 ( pass = false),并且应该循环返回并选择不同的专辑。由于某种原因,一旦执行退出read | while循环,标志就会切换回 true,因此永远不会选择另一个专辑!

这是我的代码和示例输出:

#!/bin/bash

wd="/media/External1/albums"

pass="false"
for ((i=0; i<=1; i++)); do
    while [ "$pass" == "false" ]; do
        album=`ls -d "$wd"/*/*/ | sort -R | tail -n 1`
        echo "album selected:"
        echo "$album"
        pass="true"
        echo "pass reset; pass=$pass"
        cat "$wd/wakeUp_blacklist.txt" | while read black; do
            echo "pass check 2:$pass"
            echo "black: $wd/$black"
            if [ "$album" == "$wd/$black" ] || [ "$album\n" == "$albums" ]; then
                pass="false"
                echo "test failed; pass=$pass"
            fi
            echo "pass check 1:$pass"
        done
        echo "Blacklist check complete; pass=$pass" #Why is it true again?!
    done
    pass="false"
    albums="$albums$album\n"
    echo "album assigned:"
    echo "$album"
done

echo
echo

for album in "$albums"; do
#    play "$album*"
     echo -e "$album" #List album names rather than actually play them
done

因此,如果我运行几次,它最终会选择一张列入黑名单的专辑并给出如下所示的输出:

album selected:
/media/External1/albums/Adele/21/
pass reset; pass=true
pass check 2:true
black: /media/External1/albums/Vince_Guaraldi_Trio/A_Charlie_Brown_Christmas/
pass check 1:true
pass check 2:true
black: /media/External1/albums/Sound_Inventions/Stille_Nacht-_A_German_Christmas/
pass check 1:true
Blacklist check complete; pass=true
album assigned:
/media/External1/albums/Adele/21/
album selected:
/media/External1/albums/Sound_Inventions/Stille_Nacht-_A_German_Christmas/
pass reset; pass=true
pass check 2:true
black: /media/External1/albums/Vince_Guaraldi_Trio/A_Charlie_Brown_Christmas/
pass check 1:true
pass check 2:true
black: /media/External1/albums/Sound_Inventions/Stille_Nacht-_A_German_Christmas/
test failed; pass=false
pass check 1:false
Blacklist check complete; pass=true
album assigned:
/media/External1/albums/Sound_Inventions/Stille_Nacht-_A_German_Christmas/


/media/External1/albums/Adele/21/
/media/External1/albums/Sound_Inventions/Stille_Nacht-_A_German_Christmas/

您可以看到,到最后,当黑名单条目与所选专辑(German_Christmas)匹配时,测试失败,变量pass是但检查循环外部值false的第一个输出显示它再次为真!passread | while

我知道我的脚本可能还不够高效,但我想在继续之前了解这里发生的情况。有什么建议么?

答案1

你做:

cat "$wd/wakeUp_blacklist.txt" | while read black; do

在重击中:

管道中的每个命令都在其自己的子 shell 中执行

A子壳是的另一个实例bash,具有自己的状态。这意味着您对右侧概念上|“属于”不同的bash实例 - 开头的现有值和声明被复制进来,但变量具有不同的存储位置,并且对它们的修改仅在该子 shell 内可见(您可以将其视为像这样工作fork, 如果你喜欢)。

当子 shell 完成时,变量的副本将不复存在。“外部”shell 只能看到它自己的、未修改的变量副本,这就是为什么看起来你在循环之外丢失了修改。


在这种情况下,您可以避免使用子 shell简单重定向:

    while read black; do
        ...
        pass="false"
        ...
    done < "$wd/wakeUp_blacklist.txt"

现在,while循环在主 shell 中运行,并且不会创建任何子 shell。黑名单文件的内容仍然作为循环的标准输入给出< file重定向。所有变量都存在于同一环境中。这种方法还避免了无用的使用cat

其他一些 shell(特别是zsh)一开始就没有这种行为,您可以自由修改管道右侧的变量。

相关内容