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.

  • 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. 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)
    – Donentolon
    Jul 8, 2020 at 23:05

4 Answers 4


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).

  • So is history -m curl supposed to return all lines containing curl? I get omz_history:fc:13: no matching events found.
    – Donentolon
    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. Jul 9, 2020 at 10:35

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


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.

  • There's also the print -rC1 -- ${(M)history:#*pattern*} if you don't care about timestamps 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>. Jul 3, 2020 at 12:00
  • OK, so maybe rather print -raC2 ${(kv)history[(R)*pattern*]} if you want the history numbers 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. 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). Jul 3, 2020 at 12:13

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.


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.

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.