2

I have an unlimited .zsh_history which is currently at 10k lines. I often want to see all commands from history that have a certain string, for example curl. If I do history | grep curl it takes a long time to finish. If I do grep curl ~/.zsh_history it is much faster, but the formatting is broken (timestamps aren't parsed into dates, delimiters are left as symbols).

Is there a way around the slowness of piping history to grep? I see people recommending Ctrl+R but that is clearly not the same thing - grep produces a list of commands while Ctrl+R shows them one at a time. Also piping to grep allows chaining multiple grep filters, while the same thing is not possible with Ctrl+R.

2
  • I can grep 16000 lines (1MB) in 0.030 secs. Grep is not your problem. Where you say "timestamps aren't parsed into dates, delimiters are left as symbols" strongly hints that the history command is doing a lot of processing. So some technique that does that conversion incrementally one-time, or in background, is going to be helpful. Commented Jul 2, 2020 at 7:25
  • @Paul_Pedant Correct, in fact the problem is still there if I just do history without grep (although grep proves that the problem is not only due to the large number of lines printed to stdout) Commented Jul 8, 2020 at 23:05

4 Answers 4

1

First, if you aren't doing anything more complex than grep, limiting matches with history -m PATTERN may be faster than grep. Even if you're applying complex filters afterwards, try adding a simple filter with -m that gets rid of most undesired lines.

If the limiting factor is the time spent printing out the full history with history, there's a hackish way to print the part of the history selected by grep (or other filter). Use fc -p to start a new history, and make it read the selected part of the history. Untested:

function format_history {
  fc -p -a =(cat)
  fc -l 1
}
history 1 | grep curl | format_history

This isn't very convenient to use (you need something on both sides of the pipeline, so you can't just have a magic_history_filter prefix). And what speed it gains in not formatting the whole history, it might lose in having fancier plumbing and requiring the use of a temporary file (zsh can only read the history from a seekable file, not from a pipe).

2
  • So is history -m curl supposed to return all lines containing curl? I get omz_history:fc:13: no matching events found. Commented Jul 8, 2020 at 23:08
  • @Donentolon It has to be history -m 'curl*' 1. Not very convenient to type, for sure. This is with the history builtin, I don't know about omz_history. Commented Jul 9, 2020 at 10:35
0

Here's a simple function that will let you quickly search your entire history for any string:

histgrep() {
  fc -lm "*${1}*" 1 -1
}

or even faster:

histgrep() {
  zmodload -i zsh/parameter
  print -raC2 ${(kv)history[(R)*$1*]}
}

Either of these outputs a history line number in front of each line, so you can execute the line you want with !<number>. For example to insert history line number 2596 into your command line, do

!2596

then press Tab or Enter.

I have around 3400 lines of history. Doing histgrep cd produces over 220 lines for me but takes only about 0.004 seconds to complete with the first version of the function and half that with the second version.

6
  • There's also the print -rC1 -- ${(M)history:#*pattern*} if you don't care about timestamps Commented Jul 3, 2020 at 11:56
  • fc -l does not output timestamps; it outputs history line numbers, so you can execute the line you want with !<number>. Commented Jul 3, 2020 at 12:00
  • OK, so maybe rather print -raC2 ${(kv)history[(R)*pattern*]} if you want the history numbers Commented Jul 3, 2020 at 12:04
  • Thanks. I tried it and it's twice as fast as my solution —but it's also twice as hard to read. 😉 Plus, you might also need to do zmodload -i zsh/parameter first for that to work, depending on your Zsh installation. Commented Jul 3, 2020 at 12:10
  • These days, zsh/parameter should automatically be loaded upon the first reference to $history (or any special variable provided by the module). Commented Jul 3, 2020 at 12:13
-2

From time to time you can run history >~/.zsh_history.cache. Then you can grep over that file plus the output of history for those commands missing in that file.

I am not familiar with zsh and thus cannot provide code for combining the file content with the command output.

-2

You could try ripgrep ( https://github.com/BurntSushi/ripgrep ) Is actually faster and has many useful filtering options: the github page has a long list of features.

It's in the repositories of many distributions.

2
  • 3
    It may help a little, but if history | grep is noticeable slower than grep ~/.zsh_history, the performance bottleneck is not grep. Commented Jul 1, 2020 at 23:06
  • appreciate the suggestion, but grep is not the bottleneck here, history is. Commented Jul 8, 2020 at 23:07

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.