$ ~/Multi command fzf history 22 December 2025

TL;DR I improved the default fzf history widget to allow multiple commands and stitch them together using &&

I am a very heavy terminal user who likes to optimise his workflow in the shell using various command line tools. With the restriction that I always try to stay as close to the original feeling of the shell as possible. I prefer the Unix way, combining multiple (single-focused) tools instead of using a single tool to do it all.

As much as I like the "bare" built-ins and tools my shell and OS provide, it has its flaws too and can be improved. Specifically when searching through long lists. For this, my go-to has been fzf for a very long time, mostly to fuzzy search through my shell history.

This allows me to efficiently search through my history and execute that specific command. It is a workflow I've been very accustomed to and use countless times every day.

However, sometimes I want to run multiple commands at once. But in the (standard) history fuzzy search that is not possible. I approached something related a couple of years ago with my own cli redo. It worked well enough for my use case but over time I tend to forget the shell aliases and what exactly they do. Also, it is yet another tool.

So this "annoyance" slowly crept back into my brain. And because I really don't want to add "yet another tool" to my workflow I started to investigate if I could alter the fzf-history-widget to provide multi-select. Lucky for me fzf already provides a --multi flag. So that gave me the confidence that it was possible.

Looking at the way fzf implements the history widget for zsh, I took the original code and added the --multi flag to it.

4c4
<       FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0 --multi")
---
>       FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \
8c8
<       FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --multi")
---
>       FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m") \

The only thing to do now was to stitch the multi-line output together using &&. The original code was fetching the history item using vi-fetch-history by number and executing that history item directly. I changed the code to combine the commands into one single command using &&.

< local ret=$?
< if [ -n "$selected" ]; then
<     if [[ $(awk '{print $1; exit}' <<< "$selected") =~ ^[1-9][0-9]* ]]; then
<       zle vi-fetch-history -n $MATCH
<     else # selected is a custom query, not from history
<       LBUFFER="$selected"
<     fi
< fi
< zle reset-prompt
< return $ret
---
> # Remove the history number (for example: 800 echo 'test') will become echo 'test'
> cmds=("${(@f)$(printf '%s\n' "${selected[@]}" | awk -F ' ' '{$1=""; sub(/^ +/,""); print}')}")
> # Put them into the editor buffer combined with &&
> BUFFER="${(j: && :)cmds}"
# This ensure the buffer will be set on the input prompt
> CURSOR=${#BUFFER}
> zle reset-promp

And now with the default fzf key binding to select multiple entries <tab> I could finally execute multiple commands from my shell history.

I didn't bother to implement it for bash, so my solution is zsh only. Porting it wouldn't be too difficult, but that was out of scope for this.

The full code can be found in this commit 77510e8. Thank you for reading!