I have two iTerm windows running zsh: one I use to documents in vim; the other I use to execute shell commands. I would like to synchronize the current working directories of the two sessions. I thought I could do this by outputting to a file ~/.cwd the new directory every time I change directories
alias cd="cd; pwd > ~/.cwd"
and creating a shell script ~/.dirsync that monitors the contents of ~/.cwd every second and changes directory if the other shell has updated it.
#!/bin/sh
echo $(pwd) > ~/.cwd
alias cd="cd; echo $(pwd) > ~/.cwd"
while true
do
if [[ $(pwd) != $(cat ~/.cwd) ]]
then
cd $(cat ~/.cwd)
fi
sleep 1
done
I would then append the following line of code to the end of my ~/.zshrc.
~/.dirsync &
However, it did not work. I then found out that shell scripts always execute in its own subshell. Does anyone know of a way to make this work?
Caveat emptor: I’m doing this on Ubuntu 10.04 with gnome-terminal, but it should work on any *NIX platform running
zsh.I’ve also changed things slightly. Instead of mixing “pwd” and “cwd”, I’ve stuck with “pwd” everywhere.
Recording the Present Working Directory
If you want to run a function every time you
cd, the preferred way is to use thechpwdfunction or the more extensiblechpwd_functionsarray. I preferchpwd_functionssince you can dynamically append and remove functions from it.Adding a
+to the+record_pwdand+clean_up_pwd_recordfunction names is a hack-ish way to hide it from normal use (similarly, the VCS_info hooks do this by prefixing everything with+vi).With the above, you would simply call
start_recording_pwdto start recording the present working directory every time you change directories. Likewise, you can callstop_recording_pwdto disable that behavior.stop_recording_pwdalso removes the~/.pwdfile (just to keep things clean).By doing things this way, synchronization be easily be made opt-in (since you may not want this for every single zsh session you run).
First Attempt: Using the
preexecHookSimilar to the suggestion of @Celada, the
preexechook gets run before executing a command. This seemed like an easy way to get the functionality you want:This works… sort of. Since the
preexechook runs before each command, it will automatically change directories before running your next command. However, up until then, the prompt stays in the last working directory, so it tab completes for the last directory, etc. (By the way, a blank line doesn’t count as a command.) So, it sort of works, but it’s not intuitive.Second Attempt: Using signals and traps
In order to get a terminal to automatically
cdand re-print the prompt, things got a lot more complicated.After some searching, I found out that
$$(the shell’s process ID) does not change in subshells. Thus, a subshell (or background job) can easily send signals to its parent. Combine this with the fact that zsh allows you to trap signals, and you have a means of polling~/.pwdperiodically:If you call
start_following_recorded_pwd, this launches+check_recorded_pwd_loopas a disowned background process. This way, you won’t get an annoying “suspended jobs” warning when you go to close your shell. The PID of the loop is recorded (via$!) so it can be stopped later.The loop just sends the parent shell a
USR1signal every second. This signal gets trapped byTRAPUSR1(), which willcdand reprint the prompt if necessary. I don’t understand having to call bothzle -Randzle reset-prompt, but that was the magic combination that worked for me.There is also the
_FOLLOWING_PWDflag. Since every terminal will have theTRAPUSR1function defined, this prevents them from handling that signal (and changing directories) unless you actually specified that behavior.As with recording the present working directory, you can call
stop_following_posted_pwdto stop the whole auto-cdthing.Putting both halves together:
Finally, you will probably want to do this:
This will automatically clean up everything just before your terminal exits, thus preventing you from accidentally leaving orphaned signalling loops around.