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.
Related
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.
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.
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.
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
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.
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.
When running a bash script like source script or . script from the command line, then all the lines in the script are added to bash's "source buffer" and then the current command shell just continues. Stopping execution is impossible (apart from aborting the shell), using ctrl-C only interrupts the current command, but then the next command is executed.
Where is this buffer, and would it be possible to clear it??
Example script:
echo A
sleep 10
echo B
sleep 10
echo C
sleep 10
echo D
After having done "source script", is there any way to stop it executing any further after it has been 'submitted'?
There is - to the best of my knowledge - no such thing as a source buffer in bash, so there is nothing to erase. The source command just executes the commands found in its arguments in the current environment (i.e. not in a child process).
There is nothing in the handling of source which is in particular related to the handling of signals. Maybe your shell script is setup to ignore Control-C? I suggest that you run your script with -x in order to find the culprit.
I have a bash script server.sh which is maintained by an external source and ideally should not be modified. This script writes to stdout and stderr.
In fact, this server.sh itself is doing an exec tclsh immediately:
#!/bin/sh
# \
exec tclsh "$0" ${1+"$#"}
so in fact, it is just a wrapper around a Tcl script. I just mention this in case you think that this matters.
I need a Tcl script setup.tcl which is supposed to do some preparatory work, then invoke server.sh (in the background), then do some cleanup work (and display the PID of the background process), and terminate.
server.sh is supposed to continue running until explicitly killed.
setup.tcl is usually invoked manually, either from a Cygwin bash shell or from a Windows cmd shell. In the latter case, it is ensured that Cygwin's bash.exe is in the PATH.
The environment is Windows 7 and Cygwin. The Tcl is either Cygwin's (8.5) or ActiveState 8.4.
The first version (omitting error handling) went like this:
# setup.tcl:
# .... preparatory work goes here
set childpid [exec bash.exe server.sh &]
# .... clean up work goes here
puts $childpid
exit 0
While this works when started as ActiveState Tcl from a Windows CMD shell, it does not work in a pure Cygwin setup. The reason is that as soon as setup.tcl ends, a signal is sent to the child process and this is killed too.
Using nohup would not help here, because I want to see the output of server.sh as soon as it occurs.
My next idea would be to created an intermediate bash script, mediator.sh, which uses disown -h to detach the child process and keep it from being killed:
#!/usr/bin/bash
# mediator.sh
server.sh &
child=$!
disown -h $child
and invoke mediator.sh from setup.tcl. But aside from the fact that I don't see an easy way to pass the child PID up to setup.tcl, the main problem is that it doesn't work either: While mediator.sh indeed keeps the child alive when called from the Cygwin command line directly, we have the same behaviour again (server.sh being killed when setup.tcl exits), when I call it via setup.tcl.
Anybody knowing a solution for this?
You'll want to set a trap handler in your server script so you can handle/ignore certain signals.
For example, to ignore HUP signals, you can do something like the following:
#!/bin/bash
handle_signal() {
echo "Ignoring HUP signal"
}
trap handle_signal SIGHUP
# Rest of code goes here
In the example case, if the script receives a HUP signal it will print a message and continue as normal. It will still die to Ctrl-C as that's the INT signal which is unhandled.
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.