-1

Foreword

My initial intention was to write a script that keeps checking every 1 second what hardware (maybe sink is a better term?) is playing Spotify music (headphones, stereo,...) such that this information is reliable even after Spotify is terminated (as in, this is the id of the hardware Spotify was using when it got closed).

The script I initially wrote to do this, was essentially a script that calls and parses pactl list in while true loop to retrieve the #id of the hardware on which Spotify is playing audio.

This worked as long as Spotify was up, but when Spotify was closed, the final outcome of that "listener" was an empty string, which would seem ok to me if pactl info was called after closing Spotify, but I thought I was calling it this before.

At this point, I think I might have misunderstood how signal handling and traps work in bash.

The M(non)WE

In order to give to myself and to you a MWE, I came up with the following.

  • A driver.sh script which launches a listener.sh script in background and then starts Spotify not in background (my understanding is that doing so, sending a signal to Spotify, e.g. closing it, results in that signla being sent to driver.sh); it also sets trap which terminates the background process.
#!/bin/bash
trap 'kill -TERM $bg_pid' SIGINT SIGTERM EXIT
./listen.sh &
bg_pid=$!
echo "bg_pid: $bg_pid"
/usr/bin/spotify
  • A listener.sh script which parses pactl list's output for the line media.name infinitely in a while true loop; the trap ensures that the parsing happens one last time upon termination.
#!/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

I thought that, given this setting, exectuting driver.sh from terminal would result in the following.

  • Spotify opening up, and the terminal being filled, second by second with lines of the form
func: media.name = "Spotify"
func: media.name = "Spotify"
func: media.name = "Spotify"
...
  • Upon closing Spotify (by killing driver.sh or closing Spotify window) two more line:
trap:
func: media.name = "Spotify"

Instead, what I get if I close Spotify is

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

If instead I CTRL+C driver.sh I get this (comments are mine):

...
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?

This is puzzling me. I thought that upon receiving the termination signal, driver.sh should capture it with the trap and run the kill -TERM $bg_pid command to handle it. In turn, listener.sh, upon receiving the signal, should execute echo, then func, then exit. Only at this point should Spotify terminate. If calling pactl list after this would have no Spotify in it, I would understand.

Apparently I have possibly misunderstood the whole thing.

Enlico
  • 23,259
  • 6
  • 48
  • 102
  • Spotify receives SIGINT the moment you hit Ctrl-C, then driver.sh traps it and kills listener.sh. What made you think that *Only at this point should Spotify terminate*? – oguz ismail Mar 29 '20 at 18:06
  • My lack of knowledge of this topic, I guess. However this would explain why the behavior is how it is when I close Spotify (thus sending the signal to **it**). But if I hit ctrl-c in the terminal where `driver.sh` is running... how can **it** terminate before handling the exception with the code? – Enlico Mar 29 '20 at 18:10
  • 1
    Understanding how bash runs commands might help. When `/usr/bin/spotify` line is run, 1) bash forks, 2) child process execs spotify, 3) bash blocks signals and starts waiting for child to die. When you hit Ctrl-C, 1) it kills child, 2) bash sees this and unblocks signals, 3) bash receives INT, takes action. – oguz ismail Mar 29 '20 at 18:17
  • Is this because I am **not** executing `/usr/bin/spotify` in background? I mean, if I change the line to `/usr/bin/spotify &`, should this make `driver.sh` available for receiving a signal and dealing with it instead (and possibly send a term signal to `/usr/bin/spotify` as well)? – Enlico Mar 29 '20 at 18:20
  • `spotify` is a child process of `driver.sh` process. When you close or send SIGINT to `spotify` process, it dies which is indepedent of the signal handling in its parent process `driver.sh`. What you're trying to do is insert a signal handler into `spotify` (so that you could do something before it dies) from its parent process - this isn't possible. – P.P Mar 29 '20 at 18:23
  • @oguzismail As I understand, OP wants to run `pactl list` to get the media name *before* `spotify` exits. When `spotify` does, you'd get a SIGCHLD regardless but that's not useful for this purpose. – P.P Mar 29 '20 at 18:28
  • @oguzismail, there was an answer from you, yesterday, which I had no time to check. Have you removed it? – Enlico Mar 30 '20 at 06:50
  • @Enrico undeleted now, check it out and let me know – oguz ismail Mar 30 '20 at 06:54
  • @oguzismail , at the moment I can't, but I'll do it this evening and let you know. – Enlico Mar 30 '20 at 06:57
  • 1
    Okay. Btw, preventing spotify from segfaulting is impossible here, it's caused by an error in its design – oguz ismail Mar 30 '20 at 07:00
  • 1
    After understanding this topic a bit more thanks to this comment and the answer, I realize that my initial intention was indeed the one @usr identified: inserting a signal handler into spotify. Now I understand why this is impossible. Thank both of you, I'm going to accept the answer since it truly illustrates the best I can do: inserting a signal handler into a wrapper around spotify. In order for this to work, I have to close spotify through the wrapper, as if I close spotify directly, the wrapper would take action when it's too late. – Enlico Mar 30 '20 at 17:47

1 Answers1

1

/usr/bin/spotify receives SIGINT and dies before driver.sh terminates listen.sh, thus the empty output from pactl. You should run /usr/bin/spotify at the background and terminate it after terminating listen.sh.

So, your scripts should look like:

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
oguz ismail
  • 1
  • 16
  • 47
  • 69
  • Thank you for both answers and comments, which both helped me understand. Only pity, you deleted a comment with a nice link which someone alse could have benefited from. I myself, will have to dig in my browser history to pick that link up. – Enlico Mar 30 '20 at 17:48
  • @Enrico [here is the link](https://cwe.mitre.org/data/definitions/364.html), irrelevant to the question though – oguz ismail Mar 30 '20 at 17:51
  • Maybe irrelevant, but possibly useful to the interest reader. I mean, my question tries to pinpoint a specific usecase of signal handling (am maybe it doesn't, based on the -1 and on the vote for closing it), but a reader could end up here with a wider interest in signal handling. – Enlico Mar 30 '20 at 18:02
  • 1
    @Enrico okay then I'll leave it there. Wrt the downvote&closure, this question could have been way shorter&clearer without spotify and pactl; and would have received far better answers. Maybe that's the reason – oguz ismail Mar 30 '20 at 18:09
  • 1
    I agree in hindsight. – Enlico Mar 30 '20 at 18:11