BASH's bind command permits three activities -- definition of new
macros, definition of new key bindings for existing commands, and
dumping the installed keybindings.
Macro is a string of characters binded to a key combination. A
typical textbook example would be:
lotzmana@safe$ bind '"\M-k"':"\"ls -f\""
It pastes "ls -f" when you press Alt+K. Later in life one usually
finds that engaging the pre-existing keybinginds provides for some
more inventive use of bind. More elaborate example follows.
Have you ever found yourself at the end of very long command just
to remember that something else had to be executed before that, usually
this means ctrl+c and more typing and retyping. The designers of the
beloved 4NT
thoughtfully provided ctrl+k to save the current line into history
instead of executing it. Can't we forge something similar in bash?
lotzmana@safe$ bind '"\M-k"':"\"\C-ahistory -s '\C-e'\C-m\""
It binds a macro to Alt+K. The macro is not a plain text to be
pasted but invokes some editing commands already binded to other keys.
Imagine that you have already typed a long line, to save it you press
ctrl+a (this moves the cursor to the beginning of the line), type
"history -s ", press ctrl+e to go to the end and Enter (or ctrl+m) to
execute. This is precisely the sequence of actions encoded in the macro
binded to Alt+K.
PgUp in 4NT invokes a popup menu with the most recent commands for
easy selection. While GUI programming is not an option, bind can
still bring us closer to civilized life:
lotzmana@safe$ bind '"\e[5~"':"\"history | tail -25\C-m\""
Explore the manpages of readline and bash for more details and
options.
The binding to PgUp didn't use some key name but employed the
extended ESC sequence which PgUp generates. On some terminals using the
meta encoding, like M-k, might also not work. It just happens that
there is
no accepted standard for how functional keys are encoded. This might be
an issue for the deployment of one single .bashrc in heterogenous
OS/terminal environment.
For example on my xterm I have to define the macro like this:
lotzmana@safe$ bind '"\xeb"':"\"\C-ahistory -s '\C-e'\C-m\""
How did I know that 0xeb is the character that Alt+K generates?
Well, first approximation was to use the debug terminal of the WW
multi-platform console editor. The keyboard handler has a debug
mode
where it prints all the character sequences it couldn't match to known
key combinations. Because this editor is not yet very popular I
exported the knowledge into a handy standalone perl script. Start the
script, press Alt+K, press 'x':
lotzmana@safe$ perl keys.pl
press 'x' to exit
\xeb
restore console mode
Then use the hex sequence on the left-hand side of bind.
Moving back in time to the niceties of 4NT I remember that
ctrl+left-arrow/ctrl+right-arrow moved cursor to the beginning of the
previous or the next word. This is also available in bash --
Alt+F/Alt+B, but why not have the real thing?! Here are the key
sequences I need under xterm:
lotzmana@safe$ perl keys.pl
press 'x' to exit
\x1b\x5b\x31\x3b\x35\x44
\x1b\x5b\x31\x3b\x35\x43
restore console mode
lotzmana@safe$ bind '"\e\x5b\x31\x3b\x35\x44"':backward-word
lotzmana@safe$ bind '"\e\x5b\x31\x3b\x35\x43"':forward-word
In the course of the last few weeks I assembled a list of a few key
definition bindings which I find useful. They either assign functions
to new keys or link up function
previously inaccessible because of differences in key definitions on
xterm. Best
is to add this to your .bashrc:
# Now map xterm's alternative keybindings to existing functionality
# Some are simple translations to correspontend M- combinations
# ctrl+left/right arrows:
bind '"\e\x5b\x31\x3b\x35\x44"':backward-word
bind '"\e\x5b\x31\x3b\x35\x43"':forward-word
# alt+b/f:
bind '"\xe2"':'"\M-b"'
bind '"\xe6"':'"\M-f"'
# atl+backspace:
bind '"\xff"':backward-kill-word
# alt+'.':
bind '"\xae"':yank-last-arg
# alt+k:
bind '"\xeb"':"\"\M-k\""
# alt+w:
bind '"\xf7"':'"\M-w"'
Notice the way Alt+k is mapped to M-k, it is handy when you don't
immediately know the name of the function you are assigning to, but
only know the original keybinding. If you log in under other terminals
you must use the keys.pl script to figure the keysequences of the keys
you wish to bind there and add them to your .bashrc.
lotzmana@safe$ nl -w2 -s' ' keys.pl
1 #!/usr/bin/perl -w
2 # keys.pl 1.0.0, 13-jul-2005
3 # petar marinov, http:/geocities.com/h2428, this is public domain
4 use strict;
5 $| = 1;
6 my $got = '';
7 while (1) {
8 # wait for a sequence to begin
9 $got = getone() while (ord($got) == 0);
10 # process a sequence ($got already holds the first character)
11 while (ord($got) != 0) {
12 exit(0) if ($got eq 'x');
13 printf("\\x%02x", ord($got));
14 $got = getone();
15 }
16 print "\n";
17 }
18 exit(0);
19 BEGIN {
20 use POSIX qw(:termios_h);
21 use Fcntl;
22 my ($term, $oterm, $echo, $noecho);
23 $term = POSIX::Termios->new();
24 $term->getattr(fileno(STDIN));
25 $oterm = $term->getlflag();
26 # puts console in a raw mode -- permits non-blocking reading
27 sub raw {
28 my $flags = 0;
29 $term->setlflag($oterm & ~(ECHO | ECHOK | ICANON));
30 $term->setcc(VTIME, 1);
31 $term->setattr(fileno(STDIN), TCSANOW);
32 fcntl(STDIN, F_GETFL, $flags)
33 or die "Couldn't get flags for HANDLE : $!\n";
34 $flags |= O_NONBLOCK;
35 fcntl(STDIN, F_SETFL, $flags)
36 or die "Couldn't set flags for HANDLE: $!\n";
37 }
38 # restores console to original mode
39 sub cooked {
40 print "restore console mode\n";
41 $term->setlflag($oterm);
42 $term->setcc(VTIME, 0);
43 $term->setattr(fileno(STDIN), TCSANOW);
44 }
45 # reads character or times out
46 sub getone {
47 my $key = ' ';
48 my ($rv, $rin, $win, $ein);49 my ($nfound, $timeleft, $rout, $wout, $eout);
50 $rin = $win = $ein = '';
51 vec($rin, fileno(STDIN), 1) = 1;
52 $ein = $rin | $win;
53 ($nfound,$timeleft) =
54 select($rout=$rin, $wout=$win, $eout=$ein, 0.10);
55 return chr(0) if $nfound == 0;
56 $rv = sysread(STDIN, $key, 1);
57 return chr(0) if (!defined($rv));
58 return $key;
59 }
60 raw();
61 print "press 'x' to exit\n";
62 }
63 END {
64 cooked()
65 }
7-17: The main loop consiste of two sub loops. The first first loop
(line 9) waits for a new sequence to begin. The second extracts
all keys of a sequence and prints them on the screen.
26-37: For a program to read characters in a non-blocking mode it
has to first switch the terminal to raw mode -- echo off,
non-canonical, time-out set to minimum, and non-blocking mode read for
STDIN.
45-59: The non-blocking read function uses select() to wait with
timeout for a character from STDIN. It returns 0 on time out or ascii
when character is present.
64-65: It politely returns the terminal back to mode which is good
for command line editing.