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

3 Answers 3

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.

7
  • do you know how to modify ~/.inputrc, so that Alt-D will use white space as a word boundary? Commented Aug 17, 2014 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. Commented Aug 17, 2014 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? Commented Oct 14, 2014 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. Commented Oct 14, 2014 at 21:57
  • sorry, my mistake. Actually it works perfectly. Commented Oct 14, 2014 at 22:48
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.

2
  • 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) Commented Oct 22, 2014 at 0:54
  • and BTW, for what key combination does "\ef" stand ? What should I press? Alt + something ? Commented Oct 22, 2014 at 0: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".

5
  • is there any way to change it? (other than recompiling my own version of Bash). Can this be changed in .bashrc, for instance ? Commented Aug 16, 2014 at 22:22
  • @MartinVegter Yes, you can modify any of the bindings in ~/.inputrc.
    – jasonwryan
    Commented Aug 17, 2014 at 0:12
  • could you please suggest how I can modify my ~/.inputrc, so that Alt-D acts on whole words (same as Ctrl-W) ? Commented Aug 17, 2014 at 0:29
  • @MartinVegter You can use ~/.inputrc to rebind any of the readline functions; to change the functions themselves, you'd have to hack the source...
    – jasonwryan
    Commented Aug 17, 2014 at 3:10
  • OK, but what line shall I put into my ~/.inputrc, so that Alt-D acts on whole words (whitespace separated) Commented Aug 17, 2014 at 10:48

You must log in to answer this question.

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