What escape code do I need in PS* to make sure terminal is back in normal settings on shell prompt?

Sometimes I accidentally cat some binary data; sometimes some ncurses program crash - for plenty of reasons terminal can end up in bad state, requiring manual reset. This is happening too often.

Such bad state can be no-echo, or converting everything to Chinese garbage, or many other things.

Is there any easy way to make sure terminal (without hard reset, clean-screen etc.) settings are restored when shell regains control?

It's bash and Terminal.app but I'd guess this problem is pretty much universal.


The problems that you mention arise in different layers and only some of them can be solved with “escape codes”.

Alternate Character Sets in the Terminal Emulation

There is a common terminal problem that might be described as “(some) lowercase letters are displayed as symbols or line drawing characters” (see this other SO question). This may not be related to your “Chinese garbage” problem, but it is the closest thing I have seen. You may also run into “Chinese garbage” when interpreting almost any 8-bit data stream as UTF-16 encoded text. Usually this is not a “sticky” problem that needs to be reset, so it is probably not the issue you are seeing.

The “stuck with line drawing characters” problem usually comes from sending the terminal emulator an unintentional control sequence (or stopping a program before it has reset the terminal after switching to the alternate character set). This can happen when some binary data is displayed and the byte stream contains a terminal control sequence that selects an alternate character set.

This is easy to trigger on most VT-100-style terminals since all it takes is a single byte (0x0e; see my answer to the previously linked SO question). The control sequence to reset this condition is also a single byte (0x0f; often produced via echo ^V^O (typed as echo Control+V Control+O, or directly typed as printf '\017').

You could clear up this type of problem** by getting your prompt to include a 0x0f byte.
** If your “Chinese garbage” is due to some other problem then it might have a different solution.

PS1="\[\017\]… "

The \[ and \] are there to tell bash that the bounded character is non-printing. This lets bash keep an accurate idea of “physical” cursor position (this is important for proper redisplay when using the command line editing functionality).


As Ignacio Vazquez-Abrams points out in his answer, another way to get the desired control sequence is via the tput command:

tput rmacs

Using this method you could avoid modifying PS1 and just put the above command in PROMPT_COMMAND:

PROMPT_COMMAND='tput rmacs'

TTY (termios) Options

The “no echo” problem*** comes from unexpected settings of the options for the OS-based tty device that connects your terminal emulator to all the programs that run inside terminal window. This is often cause by interactive text UI programs that have bugs, crash, or are killed so that they are not able to restore the tty to its original state.

You can control these settings with the stty command. This type of problem can not be solved with “escape codes” since the tty options are configured via software APIs (see tcsetattr(3) and termios(4)). Generally stty sane is a good reset mechanism.
*** Also “no ^C/^Z/^/”, “stair stepped output” (no automatic CR when an LF is received) and several other problems.

reset

The reset command can usually help with both types of problems. It will send terminal initialization control sequences that will usually fix the alternate character set problem, and it resets the tty options to reasonable values.

The problem with reset is that it also prints extra messages on some systems (e.g. “Erase is …”, “Interrupt is …”); you probably do not want these displayed before every prompt. If your implementation of reset sends the messages and the control sequences to different places (e.g. one goes to stdout while the other goes to stderr) then you might be able to filter out the messages (e.g. PROMPT_COMMAND='reset 2>/dev/null' (see below) and skip putting ^O into the prompt).

^O and stty sane

In bash, you can set the parameter PROMPT_COMMAND to command and bash will run if before displaying the primary prompt. You could put all call to stty sane in there and put ^O in your prompt:

PROMPT_COMMAND='stty sane'
PS1="\[\017\]… "

Again, you could avoid modifying PS1 (and handle non-VT-100-style terminals) by using tput (as suggested by Ignacio Vazquez-Abrams):

PROMPT_COMMAND='stty sane; tput rmacs'

Put

echo -n "$(tput rmacs)"

in $PROMPT_COMMAND.