6

About once a day, I'll run into an issue when I search for a history item in bash. Manipulating that item sometimes deletes the history entry, and sometimes doesn't.

Here is one case of this happening:

$ls foo
ls: No such file or directory

# (ctrl-r)ls foo(tab)
$ ls foo

# (ctrl-a)(ctrl-k)
$

# (ctrl-r)ls foo
# (no matches)

What's the explanation for what's happening here?

2
  • And where is the example where it doesn't delete the history?
    – ott--
    Commented Apr 11, 2015 at 22:52
  • 1
    @ott-- If instead of hitting ctrl-r again after clearing the line, I hit enter, then I can search for "ls foo" again
    – Eric Hu
    Commented Apr 12, 2015 at 0:21

1 Answer 1

10

I think the reason for this behavior is how Bash handles modification of previous history lines. What commands such as previous-history (C-p) or reverse-search-history (C-r) do is fetching previous history entries:

previous-history (C-p)
       Fetch the previous command from the history list, moving back in the list.

When the previous history entry is fetched it's printed just as if it was typed. Now, you can just modify it but not execute (as in your example) or modify and execute it. When you execute it you invoke accept-line:

accept-line (Newline or Return)

    Accept the line regardless of where the cursor is. If this line is
    non-empty, add it to the history list according to the setting of
    the HISTCONTROL and HISTIGNORE variables. If this line is a
    modified history line, then restore the history line to its
    original state.

After pressing Return the modified line would be saved to the history and the original line would be left untouched. But let's think about what happens if you just modify the fetched line without pressing Return - it's modified but not executed so accept-line is not invoked. What happens is that original history line becomes modified.

To see this in practice add this line to your ~/.inputrc and start the new subshell:

set mark-modified-lines On

Now let's do the same as in your example:

$ echo April # 0) press Return - `accept-line` is called

# 1. press C-r and type `April' and Tab - you will see this again because
#    `echo April' is in history:

$ echo April

# 2. now kill this line using C-k or C-u. C-r `April' doesn't work anymore
#    because there is no `echo April' in the history
# 3. DON'T PRESS RETURN HERE! Press Up arrow first a couple of times and
#    press Return to invoke a different command, it can be anything you had
#    in your history but just no Return here
# 4. now, see `history', somewhere there you will see the empty string. It
#    may look a bit different depending on your HISTTIMEFORMAT but note
#    there is an asterisk next to the history line number and a command is
#    missing. It's missing because it has been modified and saved in 2).

$ history
(...)
  483* 2015-04-12 02:36:19

As you noticed in the 2nd comment to your question, if you pressed Return in 2) you wouldn't modify the original command entered at point 0) and would be able to invoke it with C-r.

OK, I realize that this might be quite confusing and hard to understand at the first sight. Come back if you have any question, I will try to be more clear.

1
  • 2
    Superb answer, this answers everything I could have hoped for. I can't imagine how you began figuring this out.
    – Eric Hu
    Commented Apr 15, 2015 at 3:46

You must log in to answer this question.

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