当测试在 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
的第一个输出显示它再次为真!pass
read | 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
)一开始就没有这种行为,您可以自由修改管道右侧的变量。