4

I try to send a signal from one terminal A to another terminal B. Both run an interactive shell.

In terminal B, I trap signal SIGUSR1 like so :

$ trap 'source ~/mycommand' SIGUSR1

Now in terminal A I send a signal like so :

$ kill -SIGUSR1 pidOfB

Unfortunately, nothing happens in B. If I want to have my command executed, I need to switch to B and either input a new command or press enter.

How can I avoid this drawback and immediately execute my command instead ?

EDIT :

It's important to note that I want to interact directly with the interactive shell in terminal B from terminal A. For this reason, every solution where the trap command would be executed in a subshell would not work for me...

Also, terminal B must stay interactive.

ouflak
  • 2,458
  • 10
  • 44
  • 49
ogr
  • 610
  • 7
  • 23
  • By `pidOfB`, do you mean the process id of the `bash` instance running in the terminal, or the process id of the terminal emulator itself? – chepner Oct 02 '17 at 12:32
  • pidOfB is what I get when doing `echo $$`, so it's the bash instance I think – ogr Oct 02 '17 at 13:11
  • When I test this, I don't get any output until after I hit enter at the prompt. I think the the shell simply doesn't execute the handler until after the blocking read completes. – chepner Oct 02 '17 at 13:45
  • yep, it's probably that, do you think I could somehow kill this read to force the trap to be executed ? – ogr Oct 02 '17 at 16:27
  • Not in an interactive shell, no. – chepner Oct 02 '17 at 16:34
  • Can you explain *WHY* this setup is needed? Because it feels more and more like shady hackery. – Paul Hodges Oct 02 '17 at 19:37
  • Shady, not really, but hacky, yes very much. So the reason is as follow. I'm running I3, a tiled window manager and in my work setup, I usually go with multiple opened terminals working in the same working directory. Sometime, I need to change directory to work in another place. And I need to `cd` multiple time in the multiple terminals open. That's annoying. By passing a `cd` command via traps, I can have some sort of syncing between my terminals. Then I realise that I could do much more than just `cd`, and that would be interesting to experiment with that... – ogr Oct 02 '17 at 22:02

4 Answers4

2

Based on the answers and my numerous attempt to solve this, I don't think it's possible to catch a trap signal immediately in an interactive bash terminal.

For it to trigger, there must be an interaction from the user.

This is due to the readline program blocks until a newline is entered. And there is no way to stop this read.

My solution is to use dtach, a small program that emulate the detach feature of screen.

This program can run a fully interactive shell and features in its last version a way to communicate via a custom socket to this shell (or whatever program you launch)

To start a new dtach session running an interactive bash, in terminal B :

$ dtach -a /tmp/MySocket bash -i

Now from terminal A, we can send a message to the bash session in terminal B like so :

$ echo 'echo hello' | dtach -p /tmp/MySocket

In terminal B, we now see :

$ echo hello
hello

To expand on that if I now do in terminal A :

$ trap 'echo "cd $(pwd)" | dtach -p /tmp/MySocket' DEBUG

I'll have the directory of the two terminals synced

PS :I'd still like to know if there is a way to do this in pure bash

ogr
  • 610
  • 7
  • 23
1

The shell may simply be stuck in a blocking read, waiting for command-line input. Hitting enter causes the handler to execute before the entered command. Running a non-blocking command like wait:

$ sleep 60 & wait

then sending the signal causes wait to terminate immediately, followed by the output of the handler.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • I'm not sure to understand how that works, can you expand a little bit ? However, this doessn't solve my problem. Once executed, the command you gave does not give back an interactive shell, and it only work one time : if I send multiple time the signal, it will execute the trap only the first time. – ogr Oct 02 '17 at 16:19
  • 1
    As soon as `wait` exits, the interactive shell goes back to a blocking read, which delays the *next* execution of the handler as before. This wasn't meant to solve your problem, but to demonstrate that your problem is just the normal behavior of the shell: it won't execute a signal handler while it is waiting for a blocking command or system call to return. The solution is simple: don't make a blocking call. – chepner Oct 02 '17 at 16:33
1

I use a similar trap so that periodically I can (from a separate cron job) force all idle bash processes to do a 'history -a'. I found that if I trap SIGALRM instead of SIGUSR1, then the bash blocking read seems not to be a problem: the trap runs now, rather than next time one hits return. I tried SIGINT, but that caused an annoying "^C", followed by a new prompt line, to be displayed. I haven't yet found any drawbacks of using SIGALRM, but perhaps they will arise.

0

It may be buffering.

As a test, try installing a loop trigger. In window A:

{ trap 'ls' USR1; while sleep 1; do echo>/dev/null;done } &
[1] 7316

in window B:

 kill -usr1 7316

back in window A the ls is firing when the loop does an echo. Don't know if that will help, but it's something.

Paul Hodges
  • 13,382
  • 1
  • 17
  • 36
  • Hum, it's not working for me. `DEBUG` event trigger once you input something on the command line, and that's precisely what I need to do in terminal B for `SIGUSR1` to trigger – ogr Oct 02 '17 at 13:27
  • My error - the trap isn't working for me either. will change my suggestion. – Paul Hodges Oct 02 '17 at 13:32
  • Nice try, but it won't work for me : the trap here is in a subshell, so, in the case of the simple `ls` command, if I `cd` somewhere else and send the signal again, the trap `ls` will still yield the content of the directory where the subshell was call – ogr Oct 02 '17 at 16:23
  • Then it's working as written - just not as you'd hoped. What exactly is it that you wanted? It can't scry your location without help. I can't help you get the result you wanted unless you give clear specifications of the desired behavior. – Paul Hodges Oct 02 '17 at 16:30
  • Indeed, I edited my question a little bit. Note that my final command for the trap would look more like this : `trap 'source ~/mycommand' SIGUSR1`. With that I'll be able to feed to terminal B an arbitrary command from terminal A in the interactive shell. I'm almost there, but there is just this blocking stdin that is in the way, so I hope that some bash trickery could avoid this issue. – ogr Oct 02 '17 at 17:58