Improving Shell Workflows with Fzf
seb.jambor.dev
seb.jambor.dev
1. I use i3wm at home (haven’t yet made the jump into Sway/Wayland), and don’t want to change my screen resolution by opening an app, clicking with my mouse, and then waiting. So I use fzf for quick resolution changes using xrandr:
cr() {
xrandr | sed -En “s/^[ ]+([0-9]{3,4}x[0-9]{3,4}0.\*/\1/p” | fzf | xargs -I{} xrandr —output HDMI-0 —mode {}
}
2. At work I switch between multiple Kubernetes contexts during the day. To make the switch fast without typing a sentence, I will use this helper function: kc () {
kubectl config get-contexts | tail -n +2 | fzf | cut -c 2- | awk ‘{print $1}’ | xargs kubectl config use-context
}
I found this to be better than tab-completion because with tab completion I only get the name of the context, and not the cluster/user/namespace fields that make it easier to select the right one.Use the above at your own risk, as they are fast hack-ups and could be far more clear (my sed skills are prone to error and overly verbose).
—Edited for HN formatting
Link to Rofi: https://github.com/davatorium/rofi
Examples of how I use rofi:https://adityam.github.io/linux-blog/post/rofi-selectors/
My usage of it probably counts as quite basic. That said, I use it constantly. I just love the feeling of real time interaction it gives - it is well and truly part of my muscle memory now.
I use the vim plugin too, which is also brilliant and makes navigating larger projects very quick and easy. I use these two extra bindings all the time:
nnoremap <silent> <C-Space> yiw:Rg <C-r>"<CR>
vnoremap <silent> <C-Space> y:Rg <C-r>"<CR>
They're very simple, but when combined with the ability to add search items to the quickfix list it makes for a pretty powerful workflow.The first one in particular gives a great approximation of symbol search that 'just works' anywhere.
I don't have the plugin but it sounds super useful and I'm going to give them a try.
[0] Rg being ripgrep, see https://github.com/BurntSushi/ripgrep
The first one is a normal mode binding (the n in nnoremap). yiw copies the word you are currently on (without surrounding spaces), and then <C-r>" pastes that into the :Rg command. The :Rg command is part of the fzf vim plugin and it is a wrapper around ripgrep (see the link from climb_stealth), which gives you fuzzy search over the results of a ripgrep search.
There is more detail on the <C-r>" command here:
https://vim.fandom.com/wiki/Paste_registers_in_search_or_col....
It's basically just how you paste the contents of registers into commands.
The second one is almost exactly the same except that it is a visual mode mapping (the v in vnoremap). If you highlight something with visual mode, then use that command, it will search for the highlighted text using ripgrep.
<C-Space> is just the key chord that the command is bound to. It is a pretty arbitrary personal choice, I find it a very quick and easy chord combination but you can put whatever you want in there. For example, <C-S> would activate the command when you pressed the chord 'control + s'.
There is quite a good list of keys that are unused by vim here:
https://vim.fandom.com/wiki/Unused_keys
You can pick whatever you want but I prefer not to override any of the default bindings.
Hope that helps!
Fzf is better integrated in vim than in emacs. So I use dmenu on X/wofi on wayland instead, which are basically GUI fzf replacement.
Replacing Ctrl-R with fzf is the most basic and most useful usage.
Basically every git command involving history, branches etc have fzf wrappers in my setup.
My latest, least common usage is having built a wrapper around KeepassX and keepassx-cli to access my secrets from the CLI with fzf to fuzzy search entries. Saves me a lot of time every day.
Thanks junegunn for theamazimg tool!
export FZF_DEFAULT_OPTS='--reverse --border --exact --height=50%'
export FZF_ALT_C_COMMAND='fd --type directory'
export FZF_CTRL_T_COMMAND="mdfind -onlyin . -name ."
alt-c/esc-c (esc on Mac, alt everywhere else) lets me cd to any directory, ctrl-t finds files via Apple's spotlight.The esc-c command is so freaking useful.
(I use this and it works great.)
# Open any file under current dir
vf() {
files="$(fzf --query="$1" --multi --select-1 --exit-0)"
if test -n "$files"; then
echo "${EDITOR:-vim} \"$files\""
print -s "${EDITOR:-vim} \"$files\""
${EDITOR:-vim} "$files"
fi
}
# Open from current modified files in git repository
vfc() {
files="$(git ls-files -m | fzf --multi --select-1 --exit-0)"
if test -n "$files"; then
echo "${EDITOR:-vim} \"$files\""
print -s "${EDITOR:-vim} \"$files\""
${EDITOR:-vim} "$files"
fi
} [1]: https://engineering.hybridtheory.com/aws/devops/2018/08/15/fzf-autocompletions/It's actively developed and seems to have a non-overlapping set of features with fzf other than the basics (they both have features the other doesn't, and skim copies most of the core ui from fzf)
And there were talks of how skim could not utilize concurrency as efficiently as fzf and that has something to do with how Rust handles concurrency and how Go does it...
These were just the result of me skimming (ha!) over internet posts so I'd be curious if anyone could elaborate on them...
One of my most-frequently-used pry customizations is a keybinding that opens the current backtrace in fzf, with the preview window showing the selected location, allowing me to quickly walk up or down the stack, with tons of context. `enter` takes me to that frame, and `ctrl-v` opens that file location in vim.
https://gist.github.com/bcgraham/f88e7e500e1b933bed1c7077f86...
I don't have the time now to clean it up, unfortunately. Before you judge too harshly, let him without sin cast the first stone.
I have really long commands in my shell history and fzf just truncates them, which makes it pretty useless in many cases, since those commands are similar or identical up to 80-100-120 characters.
So in many situations I kind of have to guess which command has the parameter that I want at the end.
It's not that my shell history has lines with line wrap in them, it's that fzf can't show long commands on multiple lines so that I can see the entire command...
Basically, something like:
history | strip-prefix | fzf | add-prefix | bash
I'm not sure if it would be useful, but I think it's worth a try (and would be a fun project).My Ctrl-R config is at https://gist.github.com/brbsix/5e4f18833133b3accac78e3331292... but the line below is the relevant bit:
# View full command in preview window (?)
export FZF_CTRL_R_OPTS="${FZF_CTRL_R_OPTS:+$FZF_CTRL_R_OPTS }--preview 'echo {}' --preview-window down:5:hidden:wrap --bind '?:toggle-preview'"I recently set up fasd, which I recommend, but I tied it together with fzf, which makes it really nice.
fasd: https://github.com/clvv/fasd
tie it with fzf: https://github.com/CGamesPlay/dotfiles/blob/master/files/.co...
mpv ytdl://$(youtube-dl -j 'ytsearch30:spider pig' | jq '{id,title,uploader}' --compact-output | fzf | jq -r .id)This is my setup.
PROMPT_COMMAND variable is set to a function that "history 1", pulling the last command entered. That command is piped into a command line tool that pushes a single line into a Redis list. The list is trimmed to no more than 10000 entries.
Ctrl-R is bound to a function that uses a tool to connect to Redis, fetches the list, finds uniques, and pushes them as the input to fzf. The selected command becomes a shell variable BUFFER. Finally, READLINE_LINE=${BUFFER} and READLINE_POINT=${#READLINE_LINE}, thus inserting the ${BUFFER} into the command line and moving the cursor to the end of it.
This allows me to edit the pulled command.
All systems/vms point to the same redis running on a server and using the same list key, thus allowing for a shared command line history. If the server is unreachable, the hooked functions use shell's history instead of the redis server to fetch last set commands( fewer lines, obviously ). In additional to that failure to connect to redis server switches makes the shell prompt two lines where the first line says '[Command history server unreachable/shell server cli tools not found]' and the second line becomes a standard prompt.
I have been running this for 2 years with zero issues. Initially I thought doing connect->push->disconnect will be too slow, but I'm not noticing it much, even over the Wifi. I've been thinking of converting this service into a container to add an archive of when, where and what was typed in addition to the current setup.
__set_env_vars_from_redis () {
eval $(redis-cli --raw hgetall env |\
awk '{i = (NR - 1) % 2; args[i] = $1; if(i == 1) { printf("export %s=%s\n", args[0], args[1]) }}')
}
If you call that function in your shell prompt command, then you can do stuff like redis-cli hset env BAT_THEME 'Dracula' echo '' | fzf --print-query --preview-window wrap --preview 'cat test.json | jql {q}'
Here shown with jql[0] to write json queries and see the live output.For starters, I want the completion to show the arguments order for a command, and one-line description for each option. Perhaps my completion config is just incomplete, but somehow I haven't seen any howtos of this scale yet.
Bash has a very good way of doing that. Actually Debian based distributions have very good completion scripts.
git, apt and other very used programs have good completion.
Is up to the developer.
Here's a link to my 'cp' alias that lets me choose a branch, then a commit to cherry pick into my current branch:
https://github.com/kbd/setup/blob/e23b3e8e2363284c3c766c0be2...
Though I just remembered fzf maps ctrl+t by default as well (I never use it...)
https://github.com/junegunn/fzf#key-bindings-for-command-lin...
gh api 'repos/:owner/:repo/pulls' |\
jq -c --raw-output -j '[ .[] | select( .requested_reviewers | select(.[].login | contains("your-github-handle-here"))) ] | .[] | "#\(.number)\u0009\(.title)\u0009\(.body)\u0000"' |\
fzf --read0 --delimiter='\t' --with-nth=1,2 --preview='echo {3} | mdv -' --preview-window=up:80% |\
sed -n '1s/#\([0-9]\+\).*/\1/p' |\
xargs gh pr checkout
I did my damnedest to use the unit separator character 0x1F instead of tab in between fields but I could not figure out how to get FZF to like it. It makes me so sad that those characters with absolutely primo spots in the ASCII table and that ACTUALLY MEAN what we use tabs (or commas, or pipes) and linebreaks for don't get any love. Ah well.I'll throw in a fifth, which is I've slowly been cobbling together a simple fuzzy CLI music player (though I use fzy rather than fzf but same idea).
Code is here: https://github.com/Julian/dotfiles/tree/main/.config/zsh/fun...
Look at say `artist` or `play` or `shuffle` which are the music ones.
I saw this tool to find pytest cases after I implemented my own simple fzf shell function that uses bat for preview. https://github.com/dbaty/testfinder
I also implemented simple bash functions to:
+ Select a CMake target to build
Extract all add_executables defined in the CMakeLists of my project directory and pipe the fzf selection to cmake to build
+ Find and sort (by mtime) all compiled executables placed in the cmake build directory
It can be hard to remember the path of the executable produced by cmake, so i find all executable files in the cmake build dir, ls -alh, sort them by mtime (you want to execute the most recently compiled binary) and pipe to fzf
Any time I pipe something to grep, I think if this could become an fzf-based command. Highly recommend people do the same with their workflows.
My most powerful bash alias has become this one [1], where `subl` is the editor of my choice. I no longer search files, I find them. Really, fzf is almost indistinguishable from magic and has become crucial to my workflow.
[1] alias fzubl='fzf -m | xargs subl'
Amazing trick, I use it frequently.
Question, I find your blog very clean, I imagine the blog is written using a static site generator? If yes which one?