Bash:变量分配似乎没有“坚持”

Jus*_*ahm 4 bash shell-script variable

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

该脚本涉及几个循环,但本质上它旨在每天早上在由 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
Run Code Online (Sandbox Code Playgroud)

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

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/
Run Code Online (Sandbox Code Playgroud)

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

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

Mic*_*mer 12

你做:

cat "$wd/wakeUp_blacklist.txt" | while read black; do
Run Code Online (Sandbox Code Playgroud)

在 Bash 中

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

一个子shell的另一个实例bash,有自己的状态。这意味着,任何变量的变化,你就在右手边的一个|概念“属于”不同的bash实例-在开始时被复制现有的值和声明,但变量有不同的存储位置,并修改它们的可见仅在该子外壳内(fork如果您愿意,您可以将其视为像 一样工作)。

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


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

    while read black; do
        ...
        pass="false"
        ...
    done < "$wd/wakeUp_blacklist.txt"
Run Code Online (Sandbox Code Playgroud)

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

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