Workflow terminal

Improve Your Workflow in the Terminal with These fzf Tips

In a previous blog post I’ve written, I had mentioned fzf as a utility that is very useful to have in your toolbox. I’ll be providing some tips and code snippets that can be used to help improve your workflow in the terminal and also how to create your own fzf-based workflow. You can find a collection of some of these in my shared dotfiles repository or in the fzf examples wiki page.

git checkout

There are many reasons for us to switch between branches. We might want to pull down a co-worker’s branch locally for testing/review purposes. Or we may need to address feedback from peers to improve workflow. Because of this, I often see people spending time looking for the branch name to pull down, trying to remember what it was named, or using autocomplete to tab through all of the branches locally. The below screencast demonstrates using fzf with the code snippet provided on how much easier it can be to switch branches and have context as well.

Asciicast
gcb() {
  result=$(git branch -a --color=always | grep -v '/HEAD\s' | sort |
    fzf --height 50% --border --ansi --tac --preview-window right:70% \
      --preview 'git log --oneline --graph --date=short --pretty="format:%C(auto)%cd %h%d %s" $(sed s/^..// <<< {} | cut -d" " -f1) | head -'$LINES |
    sed 's/^..//' | cut -d' ' -f1)

  if [[ $result != "" ]]; then
    if [[ $result == remotes/* ]]; then
      git checkout --track $(echo $result | sed 's#remotes/##')
    else
      git checkout "$result"
    fi
  fi
}

In the above code block, notice that we can do a fuzzy search against the branch name without having to ensure that we spelled the branches correctly. And, we don’t have to constantly do tab completion to narrow down the results. Another benefit: we can also view the git history of the branch on the side. to determine if we are looking for the correct branch.

git status

Sometimes, we want to look at the current state of the branch we are working on and perform certain actions the files modified. The following snippet allows us to take a peek at the diff of each file returned by git status and allow us to perform various actions upon them. The --multi flag allows us to also decide which files we want to perform actions on.

Asciicast
gf() {
  git -c color.status=always status --short |
  fzf --height 50% --border --ansi -multi --ansi --nth 2..,.. \
    --preview '(git diff --color=always -- {-1} | sed 1,4d; cat {-1}) | head -500' |
  cut -c4- | sed 's/.* -> //'
}

In the above, we still have the option to perform fuzzy search against the files listed. However, we also have a diff displayed on the right side associated with the selected file. With the --multi flag, we are able to perform actions on any file selected and perform a bulk action on it. Some of the various actions would be performing a git add on the selected files to changes for those files, editing them, or something else. Also, it is worth mentioning that the --preview panel on the right is scrollable with the mouse scroll wheel or with bindable actions, which is shown briefly in the screen capture above.

ps kill

There are times when I need to kill certain running processes due to various reasons. It isn’t ideal, but it does happen every now and then. Make it easier to kill multiple process by using fuzzy matching, and selecting the ones that we wish to kill. Use the following snippet:

Asciicast
nuke() {
  local pid
  pid=$(ps -ef | grep -v ^root | sed 1d | fzf -m | awk '{print $2}')

  if [ "x$pid" != "x" ]
  then
    echo $pid | xargs kill -${1:-9}
  fi
}

Writing Your Own…

The simplest way to explain how fzf works is that it performs the following:

  • takes input piped into fzf.
  • allows you to perform fuzzy searching and returns any “selected” entry.
  • the selected entries are returned as the output of fzf with each selection on its own line.

So essentially, what we want to have as the output should be what we pipe into fzf. It is important that it contains all the data you might possibly need to execute against. I explain this a bit more with code comments in the example below. We will take the following example and break it down:

sys-start() {
  sudo systemctl list-unit-files | grep disabled | awk '{print $1}' | fzf -m |
    while read unit; do
      sudo systemctl start $unit &&
        journalctl -u $unit --since "10 sec ago" --no-pager
    done
}

With our basic understanding that fzf accepts stdin input, let’s break up what is going on above….

sudo systemctl list-unit-files | grep disabled | awk '{print $1}'

It will take the initial result of:

nginx.service         disabled
postgres.service      disabled
sshd.service          disabled

to

nginx.service
postgres.service
sshd.service

With the above, fzf will do fuzzy matching on each line to determine which you want to perform an action upon. The string that is displayed on the selected/highlighted line will be what is returned from fzfupon selection. So, if we had selected abc.service, then we would get abc.service as the output. Upon getting that output, we’d perform an action on it to increase our workflow strength.

We're building an AI-powered Product Operations Cloud, leveraging AI in almost every aspect of the software delivery lifecycle. Want to test drive it with us? Join the ProdOps party at ProdOps.ai.