了解 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.
在这一点上,我想我可能误解了信号处理和 trap
s 在 bash
中的工作方式。
M(非)WE
为了给自己和你一个 MWE,我想到了以下内容。
- 一个
driver.sh
脚本,它在后台启动一个 listener.sh
脚本,然后启动 Spotify 不在后台(我的理解是这样做,发送一个向 Spotify 发出信号,例如关闭它,导致该信号被发送到 driver.sh
);它还设置 trap
终止后台进程。
#!/bin/bash
trap 'kill -TERM $bg_pid' SIGINT SIGTERM EXIT
./listen.sh &
bg_pid=$!
echo "bg_pid: $bg_pid"
/usr/bin/spotify
- 一个
listener.sh
脚本,它在while true
循环中无限地解析pactl list
行media.name
的输出; trap
确保解析在终止时发生最后一次。
#!/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
会产生以下结果。
- Spotify 打开,终端被填满,一秒一秒地用表格的行
func: media.name = "Spotify"
func: media.name = "Spotify"
func: media.name = "Spotify"
...
- 关闭 Spotify 后(通过终止
driver.sh
或关闭 Spotify window)还有两行:
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
前言
我的初衷是编写一个脚本,每隔 1 秒检查一次什么硬件(也许 sink 是一个更好的术语?)正在播放 Spotify 音乐(耳机、立体声、. ..) 这样即使在 Spotify 终止后此信息 也是可靠的(例如,这是 Spotify 关闭时使用的硬件 ID).
我最初为此编写的脚本本质上是一个脚本,它在 while true
循环中调用和解析 pactl list
以检索 Spotify 正在播放音频的硬件的 #id。
只要 Spotify 启动,它就可以工作,但是当 Spotify 关闭时,"listener" 的最终结果是一个空字符串,如果调用 pactl info
对我来说似乎没问题 after 关闭 Spotify,但我想我是这样称呼它的 before.
在这一点上,我想我可能误解了信号处理和 trap
s 在 bash
中的工作方式。
M(非)WE
为了给自己和你一个 MWE,我想到了以下内容。
- 一个
driver.sh
脚本,它在后台启动一个listener.sh
脚本,然后启动 Spotify 不在后台(我的理解是这样做,发送一个向 Spotify 发出信号,例如关闭它,导致该信号被发送到driver.sh
);它还设置trap
终止后台进程。
#!/bin/bash
trap 'kill -TERM $bg_pid' SIGINT SIGTERM EXIT
./listen.sh &
bg_pid=$!
echo "bg_pid: $bg_pid"
/usr/bin/spotify
- 一个
listener.sh
脚本,它在while true
循环中无限地解析pactl list
行media.name
的输出;trap
确保解析在终止时发生最后一次。
#!/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
会产生以下结果。
- Spotify 打开,终端被填满,一秒一秒地用表格的行
func: media.name = "Spotify"
func: media.name = "Spotify"
func: media.name = "Spotify"
...
- 关闭 Spotify 后(通过终止
driver.sh
或关闭 Spotify window)还有两行:
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