Determining shell success asynchronously? - shell

If a POSIX-compliant shell fails to execute a command it immediately exits with status 127.
Is it possible to immediately determine that such a shell has succeeded in executing a command without waiting for the command (and thus the shell) to exit? (NOT the exit status of the command!) In particular I'm thinking of commands like xterm that don't exit until the user chooses to terminate them.
If this can't be done portably can it be done non-portably? Under Linux, or just with Bash or some other subset of shells?

Related

Is it possible to execute a bashscript after kill a terminal?

I know that have a file called .bash_profile that executes code (bashscript) when you open a terminal.
And there is another file that is called .bash_logout that executes code when you exit the terminal.
How I would execute some script when terminal is killed?
(.bash_logout do not cover this when terminal is killed).
How I would execute some script when terminal is killed?
I interpret this as "execute a script when the terminal window is closed". To do so, add the following inside your .bashrc or .bash_profile:
trap '[ -t 0 ] || command to execute' EXIT
Of course you can replace command to execute with source ~/.bash_exit and put all the commands inside the file .bash_exit in your home directory.
The special EXIT trap is executed whenever the shell exits (e.g. by closing the terminal, but also by pressing CtrlD on the prompt, or executing exit, or ...).
[ -t 0 ] checks whether stdin is connected to a terminal. Due to || the next command is executed only if that test fails, which it does when closing the terminal, but doesn't for other common ways to exit bash (e.g. pressing CtrlD on the prompt or executing exit).
Failed attempts (read only if you try to find and alternative)
In the terminals I have heard of, bash always receives a SIGHUP signal when the window is closed. Sometimes there are even two SIGHUPs; one from the terminal, and one from the kernel when the pty (pseudoterminal) is closed. However, sometimes both SIGHUPs are lost in interactive sessions, because bash's readline temporarily uses its own traps. Strangely enough, the SIGHUPs always seem to get caught when there is an EXIT trap; even if that EXIT trap does nothing.
However, I strongly advise against setting any trap on SIGHUP. Bash processes non-EXIT traps only after the current command finished. If you ran sh -c 'while true; do true; done' and closed the terminal, bash would continue to run in the background as if you had used disown or nohup.

Ctrl-C doesn't always terminate a shell script

I have two scenarios:
#!/usr/bin/env bash
sleep infinity
# When I type Ctrl-C here, "sleep" command and script are stopped so I didn't see "End"
echo End
#!/usr/bin/env bash
docker exec container-id sleep infinity
# When I type Ctrl-C here, "docker exec" command is stopped but script continued so I saw "End"
echo End
Why the difference in behaviour?
That's how bash behaves when its process group receives a SIGINT but the program currently running on the foreground terminates normally.
The rationale for this behavior is given here as follows:
The basic idea is that the user intends a keyboard-generated SIGINT to go
to the foreground process; that process gets to decide how to handle it;
and bash reacts accordingly. If the process dies to due SIGINT, bash acts
as if it received the SIGINT; if it does not, bash assumes the process
handled it and effectively ignores it.
Consider a process (emacs is the usual example) that uses SIGINT for its
own purposes as a normal part of operation. If you run that program in a
script, you don't want the shell aborting the script unexpectedly as a
result.

How can a background bash script exit the running shell?

Running a bash script in the background with job control enabled and stdin closed will exit the PARENT shell. How can that happen?
To demonstrate make this background_bash_script:
#!/bin/bash
set -m
ruby -e "puts :here"
Then run it in bash - it will exit the shell you ran it in. The ruby command does not matter although it appears it must be a command and not a bash built-in (for example awk --version works but true does not). To get a better look I've been running it in yet another instance of bash. A full session looks like this.
parent: PS1='child: ' bash
child: ./background_bash_script <&- &
[1] 3893
child: here
exit
parent:
Confusing!
What seems like is happening is that after set -m is run in the script, the next command that is run is forced to be in the foreground process group, which takes the original shell out of the foreground process group. Once that process exits, the shell running the script is now in the foreground process group, but once that shell exits, the original shell doesn't put itself back into the foreground process group because it ran the script in the background. So you now have an interactive shell that is in a background process group.
You can see some weird behavior here if you put a sleep at the end of your script so that it doesn't exit immediately. When you run the script in the background you get the terminal prompt back, but now your interactive shell isn't in the foreground process group! As soon as you try to type anything the shell exits. I'm not sure exactly what mechanism causes the exit. Since the shell is in the background, any attempts to read or write characters to the terminal should result in SIGTTIN OR SIGTTOU, but these signals don't cause the shell to exit in my tests.

How to kill all children of the current shell on interrupt?

My scripts cdist-deploy-to and cdist-mass-deploy (from cdist configuration management) run interactively (i.e. are called by a user).
These scripts call a lot of scripts, which again call some scripts:
cdist-mass-deploy ...
cdist-deploy-to ...
cdist-explorer-run-global ...
cdist-dir ....
What I want is to exit / kill all scripts, as soon as cdist-mass-deploy is either stopped by control C (SIGINT) or killed with SIGTERM.
cdist-deploy-to can also be called interactively and should exhibit the same behaviour.
Using ps -ef... and co variants to find out all processes with the ppid looks like it could be quite unportable. Using $! does not work as in the deeper levels the children are no background processes.
I tried using the following code:
__cdist_kill_on_interrupt()
{
__cdist_tmp_removal
kill 0
exit 1
}
trap __cdist_kill_on_interrupt INT TERM
But this leads to ugly Terminated messages as well as to a segfault in the shells (dash, bash, zsh) and seems not to stop everything instantly anyway:
# cdist-mass-deploy -p ikq04.ethz.ch ikq05.ethz.ch
core: Waiting for cdist-deploy-to jobs to finish
^CTerminated
Terminated
Terminated
Terminated
Segmentation fault
So the question is, how to cleanly exit including all (sub-)children in a portable manner (bourne shell, no csh support needed)?
You don't need to handle ^C, that will result in a signal being sent to the whole process group, which will kill all the processes that are not in the background. So you don't need to catch INT.
The only reason you get a Terminated when you kill them is that kill sends TERM by default, but that's reasonable if you are handling a TERM in the first place. You could use kill -INT 0 if you want to avoid the messages.
(responding with extra info)
If the child processes are run in the background, you can get their process ids just after you start them, using the $! special shell variable. Gather these together in a variable and just kill them all when you need to terminate.

Run script before Bash exits

I'd like to run a script every time I close a Bash session.
I use XFCE and Terminal 0.4.5 (Xfce Terminal Emulator), I would like to run a script every time I close a tab in Terminal including the last one (when I close Terminal).
Something like .bashrc but running at the end of every session.
.bash_logout doesn't work
You use trap (see man bash):
trap /u1/myuser/on_exit_script.sh EXIT
The command can be added to your .profile/.login
This works whether you exit the shell normally (e.g. via exit command) or simply kill the terminal window/tab, since the shell gets the EXIT signal either way - I just tested by exiting my putty window.
My answer is similar to DVK's answer but you have to use a command or function, not a file.
$ man bash
[...]
trap [-lp] [[arg] sigspec ...]
The command arg is to be read and executed when the shell
receives signal(s) sigspec.
[...]
If a sigspec is EXIT (0) the command arg is executed on
exit from the shell.
So, you can add to your .bashrc something like the following code:
finish() {
# Your code here
}
trap finish EXIT
Write you script in "~/.bash_logout". It executed by bash(1) when login shell exits.
If you close your session with "exit", might be able to something like
alias endbash="./runscript;exit" and just exit by entering endbash. I'm not entirely sure this works, as I'm running windows at the moment.
edit: DVK has a better answer.

Resources