Basics of "bind"
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:
petarm@safe$ bind '"\M-k"':"\"ls -f\""
It pastes "ls -f" when you press Alt+K.
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?
petarm@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:
petarm@safe$ bind '"\e[5~"':"\"history | tail -25\C-m\""
Explore the manpages of readline and bash for more details and options.
Binding to keys that have no name definitions — extended keys
The binding to PgUp above 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:
petarm@safe$ bind '"\xeb"':"\"\C-ahistory -s '\C-e'\C-m\""
How did I know that 0xeb is the character that Alt+K generates? A handy standalone perl script, keys.pl, can help. Start it script, press Alt+K, then press x:
petarm@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:
petarm@safe$ perl keys.pl press 'x' to exit \x1b\x5b\x31\x3b\x35\x44 \x1b\x5b\x31\x3b\x35\x43 restore console mode petarm@safe$ bind '"\e\x5b\x31\x3b\x35\x44"':backward-word petarm@safe$ bind '"\e\x5b\x31\x3b\x35\x43"':forward-word List of useful keybindings of bash under xterm
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 correspondent 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.
Inside keys.pl
You can simply download keys.pl and begin using it. If you are curious how it works, look at the source below and the explanation notes after that:
petarm@safe$ nl -w2 -s' ' keys.pl 1 #!/usr/bin/perl -w 2 # keys.pl 1.0.0, 13-jul-2005 3 # petar marinov, http://bashrc.sourceforge.net, 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.
Back to index