The man page for bind appears not to link to a syntax explanation, so while attempting to convert bind -p into human readable format I can filter out the garbage from 483 down to 109, but then am only able to understand 73;

bind -p 2>/dev/null \
| grep -vP "digit-argument|do-lowercase-version|not bound|self-insert" \
| perl -pe 's/\\e/[alt]/g;s/\\C/[control]/g' \
| grep -P "[\-\]].\""

"[control]-g": abort
"[control]-x[control]-g": abort
"[alt][control]-g": abort
"[control]-j": accept-line
"[control]-m": accept-line
"[control]-b": backward-char
"[control]-h": backward-delete-char
"[control]-?": backward-delete-char
"[control]-x[control]-?": backward-kill-line
"[alt][control]-h": backward-kill-word
"[alt][control]-?": backward-kill-word
"[alt]b": backward-word
"[alt]<": beginning-of-history
"[control]-a": beginning-of-line
"[alt]c": capitalize-word
"[control]-]": character-search
"[alt][control]-]": character-search-backward
"[control]-l": clear-screen
"[control]-i": complete
"[alt]!": complete-command
"[alt]/": complete-filename
"[alt]@": complete-hostname
"[alt]{": complete-into-braces
"[alt]~": complete-username
"[alt]$": complete-variable
"[control]-d": delete-char
"[control]-x[control]-v": display-shell-version
"[alt]l": downcase-word
"[alt][control]-i": dynamic-complete-history
"[control]-x[control]-e": edit-and-execute-command
"[alt]>": end-of-history
"[control]-e": end-of-line
"[control]-x[control]-x": exchange-point-and-mark
"[control]-f": forward-char
"[control]-s": forward-search-history
"[alt]f": forward-word
"[alt]g": glob-complete-word
"[alt]^": history-expand-line
"[alt]#": insert-comment
"[alt]*": insert-completions
"[alt].": insert-last-argument
"[alt]_": insert-last-argument
"[control]-k": kill-line
"[alt]d": kill-word
"[control]-n": next-history
"[alt]n": non-incremental-forward-search-history
"[alt]p": non-incremental-reverse-search-history
"[control]-o": operate-and-get-next
"[alt]=": possible-completions
"[alt]?": possible-completions
"[control]-p": previous-history
"[control]-q": quoted-insert
"[control]-v": quoted-insert
"[control]-x[control]-r": re-read-init-file
"[control]-r": reverse-search-history
"[alt][control]-r": revert-line
"[alt]r": revert-line
"[control]-@": set-mark
"[alt] ": set-mark
"[alt][control]-e": shell-expand-line
"[alt]&": tilde-expand
"[control]-t": transpose-chars
"[alt]t": transpose-words
"[control]-x[control]-u": undo
"[control]-_": undo
"[control]-u": unix-line-discard
"[control]-w": unix-word-rubout
"[alt]u": upcase-word
"[control]-y": yank
"[alt].": yank-last-arg
"[alt]_": yank-last-arg
"[alt][control]-y": yank-nth-arg
"[alt]y": yank-pop

the remanding 36 I have not deciphered yet;

bind -p 2>/dev/null \
| grep -vP "digit-argument|do-lowercase-version|not bound|self-insert" \
| perl -pe 's/\\e/[alt]/g;s/\\C/[control]/g' \
| grep -vP "[\-\]].\""

"[alt]OD": backward-char
"[alt][D": backward-char
"[alt][alt][D": backward-word
"[alt][1;5D": backward-word
"[alt][5D": backward-word
"[alt]OH": beginning-of-line
"[alt][1~": beginning-of-line
"[alt][H": beginning-of-line
"[alt][200~": bracketed-paste-begin
"[control]-xe": call-last-kbd-macro
"[alt][alt]": complete
"[alt][3~": delete-char
"[alt]\\": delete-horizontal-space
"[control]-x)": end-kbd-macro
"[alt]OF": end-of-line
"[alt][4~": end-of-line
"[alt][F": end-of-line
"[alt]OC": forward-char
"[alt][C": forward-char
"[alt][alt][C": forward-word
"[alt][1;5C": forward-word
"[alt][5C": forward-word
"[control]-x*": glob-expand-word
"[control]-xg": glob-list-expansions
"[alt]OB": next-history
"[alt][B": next-history
"[control]-x!": possible-command-completions
"[control]-x/": possible-filename-completions
"[control]-x@": possible-hostname-completions
"[control]-x~": possible-username-completions
"[control]-x$": possible-variable-completions
"[alt]OA": previous-history
"[alt][A": previous-history
"[alt][2~": quoted-insert
"[control]-x(": start-kbd-macro

For example I know one of these 3 must be alt;

bind -p | grep forward-word | grep \\[
"\e\e[C": forward-word
"\e[1;5C": forward-word
"\e[5C": forward-word

but I know not which one, or what the others are. Is there a list of what these 36 typically map to?

[EDIT1] Thanks to @undercat for the ^V tip I was able to get my unknowns down to 7.

bind -p 2>/dev/null \
| grep -vP "digit-argument|do-lowercase-version|not bound|self-insert" \
| perl -pe 's/\\e\[A/[up]/g' \
| perl -pe 's/\\e\[B/[down]/g' \
| perl -pe 's/\\e\[C/[right]/g' \
| perl -pe 's/\\e\[D/[left]/g' \
| perl -pe 's/\\e\[1;5A/[control]-[up]/g' \
| perl -pe 's/\\e\[1;5B/[control]-[down]/g' \
| perl -pe 's/\\e\[1;5C/[control]-[right]/g' \
| perl -pe 's/\\e\[1;5D/[control]-[left]/g' \
| perl -pe 's/\\e\[2~/[control]-[insert]/g' \
| perl -pe 's/\\e\[3~/[control]-[delete]/g' \
| perl -pe 's/\\e/[alt]/g;s/\\C/[control]/g' \
| grep -P "\[[^\]]*\"" \
| perl -pe 's/\[alt\]/\\e/g;s/\[control\]/\\C/g'

"\e[5D": backward-word
"\e[1~": beginning-of-line
"\e[H": beginning-of-line
"\e[200~": bracketed-paste-begin
"\e[4~": end-of-line
"\e[F": end-of-line
"\e[5C": forward-word

[EDIT2] Thanks to @JdeBP for the nosh and printf tips, but the output of decode-ecma48 (from nosh-terminal-management_1.39_amd64) is equally indecipherable to me;

... \
| perl -pe 's/:.*//g;s/"//g;s/^/\\/g' \
| xargs -I {} printf '{}' \
| ./console-decode-ecma48

DEC FNK 200;1

Can JdeBP or someone translate those vintage abbreviations to full length modern (example 1, example 2, vs an ancient example) keys (if they even map to a modern keyboard)?


2 Answers 2


A lot of the information on key sequences can be gleaned from the console_codes man page. For instance, for \e[1;5C we have:

ECMA-48 CSI sequences

CSI (or ESC [) is followed by a sequence of parameters, at most NPAR (16), that are decimal numbers separated by semicolons. (...) The action of a CSI sequence is determined by its final character.

This tells us the first number 1 is the parameter and C is the action. Looking further down we can successfully locate the action:

C CUF Move cursor right the indicated # of columns.

Which means the sequence describes the key that moves the cursor right by one symbol... which is the key!

The undocumented(?) here prefix 5 indicates the control key. It is mentioned in the following document, though I think it would've been easier (but certainly not easy!) to guess it.

I'm unaware of a simple way of converting an escape code like that into a human-readable form, however the conversion in the opposite direction is simple, you just have to press C-v in Bash followed by a key combination, and its control sequence will show up on the screen in its symbolic form, so for instance Ctrl+v, Ctrl will produce ^[[1;5C.


console-decode-ecma48 did not appear to work;

From the output given in the question, it quite clearly did work. What didn't work was your echo command, which you invoked wrongly, not generating control sequences in the first place. The decoded output shown is what your echo command actually emits.

% echo -n '\e[1;5D'

console-decode-ecma48 decodes the actual ECMA-48 control sequences, as used in terminal input and output, not encoded representations of them. So you need to give to it the actual ECMA-48 control sequences.

There are ways to get echo to do this but what happens with echo varies from shell to shell and the superior approach is to use printf.

% printf '\e[1;5D' | console-decode-ecma48 --input
Control+CUB 1

However, this is not ideal, because the encoding used by GNU Readline is idiosyncratic and not wholly shared with anything else, such as the escape sequences used in printf and (sometimes) in echo, or in the vis/unvis encoding.

printf will handle many of your remaining unknowns, but cannot translate \C- sequences into control codes. You will need to translate \C-c forms beforehand into the octal escape sequences that printf understands. Otherwise it simply will not print the right ECMA-48 character sequence to be decoded in the first place.

% printf '\C-g' | console-decode-ecma48 --input
% printf '\007' | console-decode-ecma48 --input

The --input option is necessary because this is terminal input that you are decoding. To handle ⎇ Alt key chords you will also need the --no-7bit option to prevent the decoding of (almost all) ECMA-48 7-bit code extensions and other escape sequences, which will otherwise be decoded according to ECMA-48 into their proper meanings.

% printf '\eD' | console-decode-ecma48 --input
% printf '\eb' | console-decode-ecma48 --input
% printf '\eD' | console-decode-ecma48 --input --no-7bit
Meta 'D' 
% printf '\eb' | console-decode-ecma48 --input --no-7bit
Meta 'b' 

Looping over the output of bind -p and feeding the first field sequence by sequence into printf (translating \C-c into an octal escape beforehand) I leave as an exercise to the reader, as clearly from the question you know how to do that.

For the meanings of the standard abbreviations used in the output, see the explanations and the standards referenced in the manual.

Further reading

  • Thanks for the printf tip (I up voted you for that) Please address edit 2 of the question before I mark it as solved. Mar 3, 2019 at 13:57
  • You didn't use --input as I said to use.
    – JdeBP
    Mar 4, 2019 at 12:44
  • 1
    Thanks for the comment, using --input produces the same result other than "CUP 0" changes to "CUP 1". Mar 4, 2019 at 15:00

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .