KISS

Keep It Simple Stupid

Using fzf to pick an iOS Simulator

| comments

I love using UNIX’s/Linux’s command line (aka shell, aka terminal) because it has excellent options for customizations and extensions. There are dozens of standard coreutils as well as hundreds (thousands?) of extra CLI utilities. Some tools can increase your productivity significantly once you get used to them; one of those is fzf — a fuzzy finder, which allows you to pick an item/a set of items from a list quickly. It has a number of useful commands and key bindings out of the box and of course you can write your own! That’s what I wanted to do to start using it more frequently and help with frequent actions.

My use case is to pick a booted iOS Simulator for a command. Most of the time I use one simulator, so a typical command for me would be xcrun simctl uninstall booted org.example.app to uninstall an application from the booted simulator. However that command fails when I have 2+ open simulators, in which case I need to provide a UDID of the simulator I want to target. Do you easily remember the UDIDs of the simulators that you use often? I don’t, so the tiny task here is to get the UDID of a simulator by its name. That’s where fzf comes in. (Granted, in this case I’m unlikely to have dozens of simulators open at the same time to justify using a fuzzy finder, but I needed to start somewhere.)

Note: I’m using zsh as the main shell, so the commands here are tested only in that shell.

Which simulators are booted?

First, we need to get a list of currently booted iOS Simulators in order to pipe them to fzf. A simple way would be:

1
2
3
$ xcrun simctl list devices | rg -F 'Booted'
    iPhone SE (97F2EC54-DEA9-46B0-B994-08411A028E1B) (Booted)
    iPad Air (3rd generation) (E5798BC8-69DF-4D34-97B7-848C12ACA42C) (Booted)

I’m using rg here, but the regular fgrep would work just fine too. The output isn’t very script-friendly; no fear, we can massage the output into what we’d like more:

1
2
3
$ xcrun simctl list devices | sed -nE 's/^[[:space:]]+(.*) \((.*)\) \(Booted\)[[:space:]]*$/\1'$'\t''\2/p'
iPhone SE       97F2EC54-DEA9-46B0-B994-08411A028E1B
iPad Air (3rd generation)       E5798BC8-69DF-4D34-97B7-848C12ACA42C

BTW I learned about this trick about a single sed command to filter and modify data from a git man page (don’t remember which one). This command is too dependent on the output format of simctl, and there is a more script-friendly way, via json:

1
2
3
$ xcrun simctl list -j devices | jq -r '.devices[][] | select(.state == "Booted") | [.name, .udid] | @tsv'
iPhone SE       97F2EC54-DEA9-46B0-B994-08411A028E1B
iPad Air (3rd generation)       E5798BC8-69DF-4D34-97B7-848C12ACA42C

Where would we be without jq?! This command iterates over all devices of all device types and prints tab-separated name and udid of those that are "Booted". The ‘tab’ character is used to here to be able to separate those two fields easily.

Using fzf

Now we can compose a pipeline that will let us select a booted simulator by name (or UDID if you so wish) and prints its UDID. It looks like this:

1
2
3
4
5
6
7
$ xcrun simctl list -j devices | jq -r '.devices[][] | select(.state == "Booted") | [.name, .udid] | @tsv' | fzf --height 20% | cut -d$'\t' -f2


  iPad Air (3rd generation)       E5798BC8-69DF-4D34-97B7-848C12ACA42C
> iPhone SE       97F2EC54-DEA9-46B0-B994-08411A028E1B
  2/2
>

At the fzf’s prompt, I can type ai and it will leave only the “iPad Air”. Then I press Enter, and the result is:

1
2
$ xcrun simctl list -j devices | jq -r '.devices[][] | select(.state == "Booted") | [.name, .udid] | @tsv' | fzf --height 20% | cut -d$'\t' -f2
E5798BC8-69DF-4D34-97B7-848C12ACA42C

Attaching a hotkey

Now I would like to get the picker when I’m typing a command, e.g. when I press Ctrl-g Ctrl-s (the mnemonic is “Get Simulator”):

1
$ xcrun simctl uninstall <C-g><C-s>

To do that, I put this into my ~/.zshrc:

1
2
3
4
5
6
7
# `C-g C-s` to insert the UDID of the selected booted iOS Simulator
fzf-ios-sim() {
  local udid="$( xcrun simctl list -j devices | jq -r '.devices[][] | select(.state == "Booted") | [.name, .udid] | @tsv' | fzf --height 20% | cut -d$'\t' -f2 )"
  LBUFFER="${LBUFFER}${(q)udid}"
  zle redisplay
}
zle -N fzf-ios-sim && bindkey '^g^s' fzf-ios-sim

Roughly, fzf-ios-sim is a function (and a zsh completion widget) that will capture the selected UDID and append it to the string typed so far. I don’t fully understand the details of ZLE here, so I tweaked it reading the references until it started to work. The references are:

It works!

1
2
3
$ xcrun simctl uninstall <C-g><C-s>
# after picking:
$ xcrun simctl uninstall E5798BC8-69DF-4D34-97B7-848C12ACA42C

Conclusion

This is a simple widget and a great place to start. There are a number of possible improvements here: for example, if I cancel fzf, the widget will print '', but should print nothing; or it would be nice to be able to switch between a list of booted simulators and a list of all available simulators.

ps. Initially I wanted to show only the simulators’ names, but return the UDID of the selected one. I couldn’t figure out how to do that with fzf, and in reality displaying the UDID is not an issue. What I wanted is something like this Haskell code, filtering by name but returning UDID:

1
safeHead . map udid . filter ((~= "ai") . name) $ simulators

Comments