【问题标题】:Bash while read loop: output never changes; being cached?Bash while read 循环:输出永远不会改变;被缓存?
【发布时间】:2016-02-24 22:32:27
【问题描述】:

***最终答案:

dbus-monitor --profile "interface='net.sacredchao.QuodLibet',member='SongStarted'" |
while read -r line; do
  sleep 1
  echo -e "$(grep '~filename=' $HOME/.quodlibet/current | sed 's/~filename=//')\n$(python2 -c "import sys, urllib as ul; print ul.unquote_plus(sys.argv[1])" "$(quodlibet --print-queue | sed 's|file://||g')")" > $HOME/Dropbox/Playlists/queue
done

存在竞争条件和多次写入破坏文件的问题,我还用 python 命令替换了 echo -e,因为 quodlibet 是用 python 制作的,它可能被解析为我不知道的解析首先要使用python。现在一切正常。我已经完成了大多数音乐播放器已经拥有的 quodlibet 功能:一个队列实际上可以在程序崩溃后幸存下来,并且不需要不断地修补和照看每首歌曲只播放一次。 叹息

------------原问题:

我正在使用 Arch Linux,并且我制作了一个脚本,该脚本应该在每次歌曲完成/开始播放时保存 quodlibet 的队列。除了一件事之外,一切都有效(因此您不必检查所有这些 sed 命令是否有错误或任何东西)。如果我在终端中手动运行所有这些东西,没有任何 while 循环或 DBus——每次我注意到歌曲结尾时手动写入文件——它工作正常。一旦我把它放在while循环中,它仍然会在每次歌曲开始时写入文件,但它每次都会写入完全相同的文件,直到我重新启动脚本。我认为它正在缓存这些命令的输出,而不是更新该缓存。

所以,让我们说明一下:quodlibet 有它当前正在播放的歌曲和接下来要播放的歌曲的队列。这是脚本:

while read -r line; do
  grep '~filename=' $HOME/.quodlibet/current \
  | sed 's/~filename=//' > $HOME/Dropbox/Playlists/queue
  echo -e "$(quodlibet --print-queue | sed 's|%|\\\x|g' | sed 's|file://||g')" \
  >> $HOME/Dropbox/Playlists/queue
done <<< "$(dbus-monitor --profile "interface='net.sacredchao.QuodLibet',member='SongStarted'")"

每当一首歌曲发生变化时,应该发生的是这两个命令将正在播放的歌曲和队列写入一个文件。相反,每次一首歌曲发生变化时,这两个命令都会写入当前正在播放的歌曲,并将 while 循环写入文件的第一次时间排队。

假设现在正在播放的是第 1 首曲目,第 2 到第 10 首曲目在队列中。脚本将其写入文件。

然后,轨道 1 结束。现在,正在播放的是轨道 2,队列是轨道 3 到 10。然而,即使循环注意到歌曲更改并写入文件,它写入的内容是轨道 1 作为正在播放,轨道 2 到 10 为队列。再次。不停地,不知不觉中,它正在播放第 10 首曲目,队列为空,但文件中仍然包含所有 10 首曲目。

我尝试在循环内手动运行完全相同的命令,我自己在循环外的终端中,在歌曲更改后立即运行循环脚本。该文件将反映它应该是什么。但是当歌曲再次改变时,循环会捕捉到它并再次重写所有 10 首曲目。换句话说,这些确切的命令只有在它们在循环内时才不会做我想要的。有些东西肯定被缓存了,永远不会更新,在这里。

编辑:看来我需要澄清一些事情。

1) 尽管不应该这样,但我的脚本的行为方式完全相同,无论是:

while read -r line; do
  grep '~filename=' $HOME/.quodlibet/current \
  | sed 's/~filename=//' > $HOME/Dropbox/Playlists/queue
  echo -e "$(quodlibet --print-queue | sed 's|%|\\\x|g' | sed 's|file://||g')" \
  >> $HOME/Dropbox/Playlists/queue
done <<< "$(dbus-monitor --profile "interface='net.sacredchao.QuodLibet',member='SongStarted'")"

或者:

dbus-monitor --profile "interface='net.sacredchao.QuodLibet',member='SongStarted'" |
while read -r line; do
  grep '~filename=' $HOME/.quodlibet/current \
  | sed 's/~filename=//' > $HOME/Dropbox/Playlists/queue
  echo -e "$(quodlibet --print-queue | sed 's|%|\\\x|g' | sed 's|file://||g')" \
  >> $HOME/Dropbox/Playlists/queue
done

2) 我在引入更复杂的东西之前测试了一个更简单的版本。这是我制作的一个脚本:

while true; do
  echo "$(cat /tmp/foo | sed 's/b/n/g')"
done

我尝试在循环运行的中间更改文件 /tmp/foo,就像 quodlibet 命令的输出会自行更改一样,并且更新得很好。输出发生了应有的变化。

这不是我开始的,但这是我在开始实际脚本之前制作的最后一个。你会看到它包含了我正在做的所有事情,除了 4 件事:quodlibet、dbus 命令、>保存或>>附加到文件,以及 while-read 组合。其中之一是无论环境发生什么变化,输出始终保持不变,我认为我们可以排除 quodlibet,因为正如我之前所说,手动运行这些命令可以正常工作。

编辑 2:Welp,我没有向下滚动文件,但我刚刚做了。这个问题变得更加复杂,但可能更容易解决。它根本不是每次都写完全相同的输出。它以某种方式跳过了覆盖文件的行 - 以 grep 开头的行 - 并且只是附加了第二行的输出,即 echo -e。

编辑 3:现在我又被难住了。当我将确切的两行从 while 循环中复制并粘贴到 bash 终端中时,它们会执行我想要的操作。但是在 while 循环中,第一个 grep 命令从未真正写入该文件。我想也许是莫名其妙地吃掉了第一个命令,所以我尝试预先添加一个空的回显:

dbus-monitor --profile "interface='net.sacredchao.QuodLibet',member='SongStarted'" |
while read -r line; do
  echo
  grep '~filename=' $HOME/.quodlibet/current \
  | sed 's/~filename=//' > $HOME/Dropbox/Playlists/queue
  echo -e "$(quodlibet --print-queue | sed 's|%|\\\x|g' | sed 's|file://||g')" \
  >> $HOME/Dropbox/Playlists/queue
done

但它仍在吃第一个 grep。为什么不保存文件?

编辑 4:我已确认 grep 命令肯定输出正确的输出,但不会将其写入文件。

【问题讨论】:

  • 如果您不关心文件而只是在循环中运行quodlibet 命令,您会看到正确的队列输出还是原始输出?因为这是真正的问题。该命令是否在那里工作。同样,current 文件是否按照您期望的方式实时更改。
  • 您确定它实际上在运行循环吗? &lt;&lt;&lt; "$(dbus-monitor ... )" 部分应该捕获dbus-monitor 命令的整个输出,并一次性将其全部输入循环,而不是在歌曲结束时一次一行地输入。尝试在循环顶部添加 `echo "line = '$line'",看看会发生什么。
  • @EtanReisner 是的,如果我不关心文件,这些命令会改变。我在OP中说过几次。 (哇,我希望回车键不一定会发布评论;这让我感到惊讶)
  • @GordonDavisson 我所拥有的是我从寻找如何做 DBus 的事情,然后是如何运行一个 while 循环而不是每次迭代都将它管道到一个单独的 fork 中拼凑起来的东西(这是什么我起初尝试过,结果与我现在的结果完全相同)。我所知道的是,我所做的实际上是在每次歌曲更改时再次运行循环。我已经测试过了。
  • 将 grep 输出转储到标准输出。将所有内容转储到标准输出。通过一块一块地撕开它来调试它。如果您的 grep 输出不起作用,则说明您的查询有问题,或者文件不存在(在那个时间点)处于您期望的状态。

标签: linux bash while-loop audio-player


【解决方案1】:

为了将您尝试做的具体事情与您感知到的问题解耦,我建议您在尝试使用任何花哨的东西之前先尝试一些独立的命令。

while read -r line ; do
    echo $line
done <<< $(yes)

请注意,这永远不会输出任何内容。 $(yes) 提供的输入永远不会完成,因此外部循环永远不会打印任何内容。至少有一位 cmets 在您的主帖中指出了这一点。

尝试将输出直接传送到循环,而不是使用捕获变量。我不能说这对你有用,但它至少解决了流输出到 while 循环的明显问题之一。

yes | while read -r line ; do
    echo $line
done

根据此处的其他调试工作,问题似乎是关于歌曲转换的 dbus 信号与将新的“当前”歌曲写入磁盘之间的竞争条件。为了提供更完整的解决方案(当然未经测试),我可以从以下内容开始:

function urldecode() {
    python -c "import sys, urllib as ul; print ul.unquote_plus(sys.argv[1])" "$@"
}

dbus-monitor --profile "interface='net.sacredchao.QuodLibet',member='SongStarted'" | \
while read -r line; do
    sleep 1 # Adjust as necessary to preventing race conditions
    $CurrentSongFile=$(grep '~filename=' $HOME/.quodlibet/current | sed 's/~filename=//')
    $SongFileQueue=$(urldecode "$(quodlibet --print-queue)") | sed 's_file://__g')
    printf "$CurrentSongFile\n$SongFileQueue" > $HOME/Dropbox/Playlists/queue
done

【讨论】:

  • 一开始我就是这样做的,但它同时产生了与我现在得到的相同的输出。我切换到我现在正在做的事情,因为当我试图寻求帮助时,很多人在 while 循环中遇到了变量问题,直到他们从你的建议切换到我现在正在做的事情。但话虽如此,这并不重要,因为无论哪种方式,我都会得到完全相同的不良行为。
  • 无论如何,独立的命令。我确实是先这样做的,直到我将 while-read 和 quodlibet 考虑在内之前它一直有效。我将使用该信息编辑我的帖子。
  • 你能简化你的问题吗?我敢打赌,我们大多数人都没有准备好调试您的设置。作为调试步骤,请尝试dbus-monitor ... | while read -r line; do printf "$line" ; done。使用 printf 并将变量括在引号中以确保获得原始输出。在你确认你已经正确地制定了你的循环之后,然后添加其他东西。
  • 完成。我将我的测试脚本修改为dbus-monitor --profile "interface='net.sacredchao.QuodLibet',member='SongStarted'" | while read -r line; do echo "$(cat /tmp/foo | sed 's/b/n/g')"; done。输出仍然按预期更改。因此,这意味着错误不在于 while-read 或 dbus 命令。
  • 我会建议我们打开一个聊天室,我会发回我们调试工作的结果。也许这个链接有效? chat.stackoverflow.com/rooms/95874/question-33860537
猜你喜欢
  • 2011-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-30
  • 2015-04-21
相关资源
最近更新 更多