了解 bash 信号处理

Understanding bash signal handling

前言

我的初衷是编写一个脚本,每隔 1 秒检查一次什么硬件(也许 sink 是一个更好的术语?)正在播放 Spotify 音乐(耳机、立体声、. ..) 这样即使在 Spotify 终止后此信息 也是可靠的(例如,这是 Spotify 关闭时使用的硬件 ID).

我最初为此编写的脚本本质上是一个脚本,它在 while true 循环中调用和解析 pactl list 以检索 Spotify 正在播放音频的硬件的 #id。

只要 Spotify 启动,它就可以工作,但是当 Spotify 关闭时,"listener" 的最终结果是一个空字符串,如果调用 pactl info 对我来说似乎没问题 after 关闭 Spotify,但我想我是这样称呼它的 before.

在这一点上,我想我可能误解了信号处理和 traps 在 bash 中的工作方式。

M(非)WE

为了给自己和你一个 MWE,我想到了以下内容。

#!/bin/bash
trap 'kill -TERM $bg_pid' SIGINT SIGTERM EXIT
./listen.sh &
bg_pid=$!
echo "bg_pid: $bg_pid"
/usr/bin/spotify
#!/bin/bash
trap 'echo trap:; func; exit' SIGINT SIGTERM EXIT
func() {
  echo func: $(pactl list | sed -E '/media\.name/p;d')
}
while true; do
  func
  sleep 1
done

我认为,鉴于此设置,从终端执行 driver.sh 会产生以下结果。

func: media.name = "Spotify"
func: media.name = "Spotify"
func: media.name = "Spotify"
...
trap:
func: media.name = "Spotify"

相反,如果我关闭 Spotify,我得到的是

...
func: media.name = "Spotify"
func: media.name = "Spotify"
+enrico:~$ trap:
func: # empty!
trap:
func: # why again?

如果我 CTRL+C driver.sh 我明白了(评论是我的):

...
func: media.name = "Spotify"
func: media.name = "Spotify"
^Cfunc: media.name = "Spotify"   # here I hit ctrl+c
func: media.name = "Spotify"     # maybe this delay is because of sleep in listener.sh?
./spot.sh: line 6:  8704 Segmentation fault      (core dumped) /usr/bin/spotify # why this SegV?
+enrico:~$ trap:
func: # empty!
trap:
func: # why again?

这让我很困惑。我认为在接收到终止信号时,driver.sh 应该使用 trap 和 运行 捕获它并使用 kill -TERM $bg_pid 命令来处理它。反过来,listener.sh收到信号后,应该执行echo,然后是func,然后是exit。只有此时 Spotify 才应该终止。如果在此之后调用 pactl list 将没有 Spotify,我会理解。

显然我可能误解了整件事。

/usr/bin/spotify 收到 SIGINT 并在 driver.sh 终止 listen.sh 之前死亡,因此 pactl 的输出为空。你应该在后台运行 /usr/bin/spotify 终止后listen.sh.

因此,您的脚本应该如下所示:

driver.sh

#!/bin/bash
trap 'kill -TERM $bg_pid $!' SIGINT SIGTERM
./listen.sh &
bg_pid=$!
echo "bg_pid: $bg_pid"
/usr/bin/spotify &
wait

listen.sh

#!/bin/bash
trap 'echo trap:; func; exit' SIGINT SIGTERM
func() {
  echo func: $(pactl list | sed -E '/media\.name/p;d')
}
while true; do
  func
  sleep 1
done