How can I get a confirmation before I exit screen (when typing exit on the command-line). Is this possible?
3 Answers
I approached this by masking the exit
command with a function; the function checks to see if you're within screen, and if you're the only child process left of that screen process.
exit() {
if [[ "$(ps -o pid= --ppid "$(ps -o ppid= -p "$$")" | wc -l)" -eq 1 ]]
then
read -p "Warning: you are in the last screen window; do you really want to exit? (y/n) "
case $REPLY in
(y*) command exit "$@" ;;
esac
else
# not within screen at all, or not within the last screen window
command exit "$@"
fi
}
You would have to include that function as part of your (bash) profile, e.g. ~/.bash_profile
. When starting screen, it will (unless told otherwise), start an instance of your $SHELL. That shell will be a child of a screen process. When exiting from a child shell, the above code checks to see how many processes are children of the current shell's parent process. From the inside out:
$(ps -o ppid= -p "$$")
-- asks for the parent PID (adding the=
suppresses the header line) of the current process ($$
)$(ps -o pid= --ppid ... | wc -l)
-- asks for the list of PIDs (again without the header) whose parent PID is our parent, then counts the number of lines of output
If it looks like we're the last child process of a screen session, it prompts for confirmation; if the response begins with the letter y
, the function calls the "real" exit
command to exit the shell; otherwise, the function ends without exiting the shell.
If we're not the last child process, the function goes ahead and exits normally.
A couple notes as I developed this:
I initially had more tests in the
if
line to see if we are within a screen session, including seeing ifSTY
is populated and forSHLVL
being greater than 1. screen sets STY, and bash will increment SHLVL, but neither of those variables are read-only, so the test is not strong enough to be useful.screen also sets a
WINDOW
variable, but checking it for0
is not reliable; you could open two windows and then close window0
, leaving window1
as the last window.Entering EOF (usually Control+D) will, by default, cause the shell to immediately exit, bypassing this wrapper function. The best workaround I know would be to set the variable
IGNOREEOF
to some non-zero value; that will only delay the shell's inevitable exit, though.
Because I used so many bash- (and GNU procutils-) specific features above, I wanted to also provide a POSIX-conformant solution. The ps
line changes to a ps ... | grep -c
scheme in order to capture the number of processes with a specific parent PID. The other change is to re-work the read -p
into a separate prompt and read
.
exit() {
parent="$(ps -o ppid= -p $$)"
if [ "$( ps -eo ppid= | grep -c "^ *${parent}\$" )" -eq 1 ]
then
printf "Warning: you are in the last screen window; do you really want to exit? (y/n) "
read REPLY
case $REPLY in
(y*) command exit "$@" ;;
esac
else
# not within screen at all, or not within the last screen window
command exit "$@"
fi
}
I assume you are using bash.
are_u_sure(){
read -n1 -p "Are you sure to exit? [y/N] "
[ "$REPLY" != y ] && { echo; history -a; bash; }
}
trap are_u_sure EXIT
Add this to your ~/.bashrc, and then whenever you try to exit bash (type exit or ^D), you will be prompted. And you'll never quit unless you press 'y'.
This way, we don't have to stick to the screen magic since we can do it bash-wide.
As this approach is extremely simple, it does have a flaw, you will lose the variable assignments you typed in before. But may be we can work on that. You can modifiy this code to fit your needs anyway.
-
1This technically quits
bash
but spawns a new interactivebash
shell before it quits, leaving the original shell hanging in itsEXIT
signal handler until the new shell exits. Unconventional, but I suppose it works.– Kusalananda ♦Apr 16, 2018 at 6:30 -
I wasn't really satisfied with any other solution I saw out there so I ended up settling on this (added to .bashrc):
exit_with_prompt() {
printf "Do you really want to exit screen? (y/n) "
read REPLY
case $REPLY in
(y*) command exit "$@" ;;
esac
}
export IGNOREEOF=0
[[ $(type -t exit) == "alias" ]] && unalias exit
[[ $(type -t logout) == "alias" ]] && unalias logout
if grep -q "^screen\b" /proc/$PPID/comm; then
export IGNOREEOF=2
alias exit=exit_with_prompt
alias logout=exit_with_prompt
fi
And then this to .screenrc:
shell -$SHELL
This works sort of backwards from the way I'd normally do things. It unsets aliases and re-enables ctrl-d -> exit normally, but it checks if bash is running immediately inside a screen instance (specifically it checks if the start of the command of the parent process is "screen" then a word-boundary), then it disables ctrl-d/EOF to exit and sets up the exit with prompt aliases for both exit and logout.
I haven't been using it for very long but so far I'm quite satisfied with how it works.