Xcode Simulator integration in zsh
When running an app in Xcode’s iPhone simulator, you’ll often want to locate the place on disk where the app stores its documents and other local files, i.e., the app’s data container. There are some nice tools that help out with this, such as SimPholders and simsim. Being a command line user, however, I would really like to have something integrated in my shell.
Let’s take a look at how to do this and develop a plugin for zsh
. (If you just want the plugin, here you go.)
Finding the data container
Many developer tools are accessible from the command line via the xcrun
command. It is structured with subcommands, and one of these is simctl
with which you can do all kinds of stuff with simulators. Here’s how you can get the directory.
xcrun simctl get_app_container booted <BUNDLE_ID> data
Instead of <BUNDLE_ID>
, obviously, use your app’s bundle ID. booted
here stands for the currently running simulator – it will only work if you are only running one.
To change to that directory, you can type:
cd `xcrun simctl get_app_container booted <BUNDLE_ID> data`
That’s a lot to type and remember, so let’s make it into a zsh
function.
function simdir () {
xcrun simctl get_app_container booted $1 data
}
function simcd () {
cd `simdir $1`
}
Put that somewhere where zsh
can read it; if you’re using oh-my-zsh
, you can put it in a file with the path ~/.oh-my-zsh/custom/simulator.zsh
.
Now open a new shell. You should now be able to type simcd com.myapps.bundle.id
.
Listing available apps
The above is nice, but you have to remember the bundle ID of the app you’re working on and type it, or find it and copy and paste. Wouldn’t it be nice if you could just select from running apps? We’d really like this thing to be auto-completing. First we need to find out what apps are installed on the simulator. xcrun simctl
to the rescue again:
xcrun simctl listapps booted
This subcommand of xcrun simctl
is undocumented – it doesn’t show up in the list that shows when you enter just xcrun simctl
(reported to Apple).
It outputs a list of installed apps in the old-style ASCII property list format1. macOS comes with two tools for dealing with plists: PlistBuddy and plutil. PlistBuddy is great for extracting single values from a plist, but can’t do the job here. Using plutil, we can convert the plist to JSON, giving us a wider choice of tools to work with. The system-installed Ruby will do the job nicely:
xcrun simctl listapps booted | \
plutil -convert json - -o - | \
ruby -r json -e 'puts JSON.parse(STDIN.read).keys'
Creating zsh completions
Shell programming always kind of gives me a headache. My usual strategy is to google for similar examples, try to read the manuals if you have to and at some point you get something that seems to work. The experience of creating a zsh completion was no different. This guide seemed to be the best available, much more accessible than the zsh
manual, but still shell programming. So instead of trying to explain what’s happening, here’s what I ended up with.
_simdir() {
local state
_arguments \
'1: :->bundle_identifier'\
'*: :->rest'
case $state in
(bundle_identifier) _arguments '1:Bundle identifier:($(_bundle_identifiers))' ;;
(*) compadd "$@"
esac
}
compdef _simdir simdir simcd
And yay, it works! Now you can use both simdir
and simcd
with auto-completion of the bundle identifier. It is now easy to add other commands that take a bundle identifier and make them auto-completing as well.
I added these two.
function simpushd () {
pushd `simdir $1`
}
function simopen() {
open `simdir $1`
}
Adding simpushd
and simopen
to the line that starts with compdef
makes them auto-completable.
Future developments
I really should not just allow the currently booted device, but any simulator on the system. That should also be completable. Actually, it would be nice if all of xcrun simctl
was completable. This functionality should live somewhere, maybe best in the plugins that come bundled with oh-my-zsh – there are already some Xcode utilities there.
But enough yak shaving for now. Here is all of the above as a Gist.
Notes
Thanks to Dave De Long and Russ Bishop for advice on how to use xcrun simctl
. Thanks to Olivier Halligon for the suggestion to use plutil
and the way to process JSON as a Ruby one-liner.
1: In a previous version of this post, I used a shell pipeline like this:
xcrun simctl listapps booted | \
grep CFBundleIdentifier | \
cut -d '=' -f 2 | \
grep -oE '[a-zA-Z0-9.-]+'`