7

Bash offers many useful emacs-style keybindings for simple commandline editing. For example, Ctrl+w deletes ("kills") word left from the cursor.

Another keybinding, Alt+d is supposed to be a "mirror" of the first one. It is supposed to delete a word right from the cursor.

However, I have noticed, these two keybindings do not act completely symetricaly. Whereas Ctrl+w treats foo.bar as one word, Alt+d treats it as two words

Even more annoyingly, # echo are two words for Ctrl+w, but one word for Alt+d.

Is there some logic in this? Is there some reason why they don't treat words in the same way?

Is there any way for me to change this?

I am using bash on Debian Wheezy

eyoung100
  • 6,252
  • 23
  • 53
Martin Vegter
  • 358
  • 75
  • 236
  • 411

3 Answers3

8

Different bash commands use different notions of word. Check the description of each command in the manual.

C-w kills to the previous whitespace. M-DEL (usually Alt+BackSpace) kills to the previous word boundary where words contain only letters and digits (the same as M-b and M-f), and M-d kills forward similarly.

Bash uses the Readline library to process user input, and can be configured either via ~/.inputrc or via the bind builtin in ~/.bashrc. You can bind a key to a different readline command if you wish. You can also use bind -x to bind a key to a bash functions that modifies the READLINE_LINE variable.

For example, to make M-d kill a shell word, bind it to shell-kill-word in your .bashrc:

bind '"\M-d": shell-kill-word'

To make M-d delete a whitespace-delimited word, there is no built-in function, so you need to write either a macro or a shell function. Since there is no motion command that goes by whitespace-delimited words, you need a function at least for that part.

delete_whitespace_word () {
  local suffix="${READLINE_LINE:$READLINE_POINT}"
  if [[ $suffix =~ ^[[:space:]]*[^[:space:]]+ ]]; then
    local -i s=READLINE_POINT+${#BASH_REMATCH[0]}
    READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}${READLINE_LINE:$s}"
  fi
}
bind -x '"\ed": delete_whitespace_word'

To make M-d kill a whitespace-delimited word is more complicated because as far as I know, there is no way to access the kill ring from bash code. So this requires a function to find the end of the portion to kill, and a macro to follow this by the actual killing.

forward_whitespace_word () {
  local suffix="${READLINE_LINE:$READLINE_POINT}" 
  if [[ $suffix =~ ^[[:space:]]*[^[:space:]]+ ]]; then
    ((READLINE_POINT += ${#BASH_REMATCH[0]}))
  else
    READLINE_POINT=${#READLINE_LINE}
  fi
}
bind -x '"\C-xF": forward_whitespace_word'
bind '"\C-x\C-w": kill-region'
bind '"\ed": "\e \C-xF\C-x\C-w"'

All of this would be a lot easier in zsh.

  • do you know how to modify ~/.inputrc, so that Alt-D will use white space as a word boundary? – Martin Vegter Aug 17 '14 at 10:54
  • @MartinVegter I don't think you can do it with readline alone, but you can do it in bash, see my edit. If you want fancy command line edition, you'll be better off in zsh. – Gilles 'SO- stop being evil' Aug 17 '14 at 12:58
  • after having used your hack for some time now, I find it absolutely great. There is only one small problem: When I press M-d to delete a whitespace-delimited word, it deletes the word including the whitespace after it. I have to manually add the space, otherwise the two commands will be joined together. Would there be any way of changing that in the code? – Martin Vegter Oct 14 '14 at 21:35
  • @MartinVegter This makes sense. My code was actually deleting the first space character after the word, which was just weird. I've changed my approach in delete_whitespace_word, it should now work like in Emacs and like you want. – Gilles 'SO- stop being evil' Oct 14 '14 at 21:57
  • sorry, my mistake. Actually it works perfectly. – Martin Vegter Oct 14 '14 at 22:48
  • I have encountered small problem: I have added the "delete_whitespace_word ()" code to my /etc/profile.d/readline.sh. All works OK if I login with login shell (i.e. SSH, su). But when I log in from my terminal emulator (Terminator) the file probably does not get loaded and therefore it does not work. Further, if I change terminator to "run command as login shell", I get an error: "/etc/profile.d/readline.sh: line 8: bind: warning: line editing not enabled" – Martin Vegter Nov 25 '14 at 23:13
  • @MartinVegter You put that code in the wrong file. It should be in your .bashrc. Make sure that your .bash_profile sources ~/.bashrc, otherwise the bindings wouldn't be available in a login shell (bash's init file management is screwy). – Gilles 'SO- stop being evil' Nov 25 '14 at 23:15
4

Readline already has vi-fword and vi-bword which use whitespace as word boundaries so there's no need for Gilles's forward_whitespace_word function.

vi-fword followed by unix-word-rubout (\C-w) deletes a whitespace-delimited word backward (including trailing spaces).

bind '"\eb":vi-bword'
bind '"\ef":vi-fword'
bind '"\ed":"\ef\C-w"'

vi-backward-word is an alias to vi-bword, vi-forward-word is an alias to vi-fword, vi-backward-bigword is an alias to vi-bWord, and vi-forward-bigword is an alias to vi-fWord.

Lri
  • 5,223
  • This sounds great, but how can I test it? Where should I put your code? the lines in my /etc/inputrc have different format, i.e. "\e[1;5D": backward-word. (there is no bind infront) – Martin Vegter Oct 22 '14 at 00:54
  • and BTW, for what key combination does "\ef" stand ? What should I press? Alt + something ? – Martin Vegter Oct 22 '14 at 00:59
1

This is a result of the way readline treats "words". Altd is a shortcut for kill-word:

Kill from point the end of the current word, or if between words, to the end of the next word. Word boundaries are the same as those used by M-f (forward-word).

Word boundaries in forward-word are defined thus:

Words are composed of letters and digits.

Whereas Ctrlw is a shortcut for unix-word-rubout:

Kill the word behind point, using white space as a word boundary.

So, to use your example of foo.bar, the first command treats the string as two "words" separated by a ., whilst the second sees no whitespace so treats it as a single "word".

jasonwryan
  • 73,126