在两个Zsh会话之间同步当前目录

时间:2013-01-18 18:33:40

标签: shell unix zsh

我有两个运行zsh的iTerm窗口:一个用于vim中的文档;另一个我用来执行shell命令。我想同步两个会话的当前工作目录。我想我可以通过每次更改目录

时输出到新文件~/.cwd来完成此操作
alias cd="cd; pwd > ~/.cwd"

并创建一个shell脚本~/.dirsync,每秒监视~/.cwd的内容,如果另一个shell更新了目录,则更改目录。

#!/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

然后我会将以下代码行附加到~/.zshrc

的末尾
~/.dirsync &

然而,它没有用。然后我发现shell脚本总是在它自己的子shell中执行。有没有人知道如何使这项工作?

1 个答案:

答案 0 :(得分:2)

警告:我在Ubuntu 10.04上使用gnome-terminal执行此操作,但它应该在运行zsh的任何* NIX平台上运行。

我也稍微改变了一点。而不是混合“pwd”和“cwd”,我到处都是“pwd”。

记录当前工作目录

如果您希望每次cd时都运行一项功能,首选方法是使用chpwd function or the more extensible chpwd_functions array。我更喜欢chpwd_functions,因为您可以动态添加和删除函数。

# Records $PWD to file 
function +record_pwd {
    echo "$(pwd)" > ~/.pwd
}

# Removes the PWD record file
function +clean_up_pwd_record {
    rm -f ~/.pwd
}

# Adds +record_pwd to the list of functions executed when "cd" is called
# and records the present directory
function start_recording_pwd {
    if [[ -z $chpwd_functions[(r)+record_pwd] ]]; then
        chpwd_functions=(${chpwd_functions[@]} "+record_pwd")
    fi
    +record_pwd
}

# Removes +record_pwd from the list of functions executed when "cd" is called
# and cleans up the record file
function stop_recording_pwd {
    if [[ -n $chpwd_functions[(r)+record_pwd] ]]; then
        chpwd_functions=("${(@)chpwd_functions:#+record_pwd}")
        +clean_up_pwd_record
    fi
}

+添加到+record_pwd+clean_up_pwd_record函数名称是一种隐藏正常使用的黑客方法(类似地,VCS_info挂钩通过使用{前缀所有内容来实现此目的) {1}})。

通过上述内容,您只需在每次更改目录时调用+vi即可开始记录当前工作目录。同样,您可以调用start_recording_pwd来禁用该行为。 stop_recording_pwd还会移除stop_recording_pwd文件(只是为了保持清洁)。

通过这种方式,可以轻松地选择同步(因为您可能不希望在运行的每个zsh会话中都这样做。)

第一次尝试:使用~/.pwd挂钩

与@Celada的建议类似,preexec挂钩在执行命令之前运行。这似乎是获得所需功能的简单方法:

preexec

这有点......由于autoload -Uz add-zsh-hook function my_preexec_hook { if [[-r ~/.pwd ]] && [[ $(pwd) != $(cat ~/.pwd) ]]; then cd "$(cat ~/.pwd)" fi } add-zsh-hook preexec my_preexec_hook 挂钩在每个命令之前运行,因此它将在运行下一个命令之前自动更改目录。但是,在此之前,提示符保留在最后一个工作目录中,因此它的选项卡完成了最后一个目录等。(顺便说一下,空行不算作命令。)所以,它有点工作,但这并不直观。

第二次尝试:使用信号和陷阱

为了让终端自动preexec并重新打印提示,事情变得更加复杂。

经过一些搜索,我发现cd(shell的进程ID)在子shell中没有变化。因此,子shell(或后台作业)可以轻松地向其父级发送信号。将此与zsh允许您trap signals的事实相结合,您可以定期轮询$$

~/.pwd

如果您致电# Used to make sure USR1 signals are not taken as synchronization signals # unless the terminal has been told to do so local _FOLLOWING_PWD # Traps all USR1 signals TRAPUSR1() { # If following the .pwd file and we need to change if (($+_FOLLOWING_PWD)) && [[ -r ~/.pwd ]] && [[ "$(pwd)" != "$(cat ~/.pwd)" ]]; then # Change directories and redisplay the prompt # (Still don't fully understand this magic combination of commands) [[ -o zle ]] && zle -R && cd "$(cat ~/.pwd)" && precmd && zle reset-prompt 2>/dev/null fi } # Sends the shell a USR1 signal every second function +check_recorded_pwd_loop { while true; do kill -s USR1 "$$" 2>/dev/null sleep 1 done } # PID of the disowned +check_recorded_pwd_loop job local _POLLING_LOOP_PID function start_following_recorded_pwd { _FOLLOWING_PWD=1 [[ -n "$_POLLING_LOOP_PID" ]] && return # Launch signalling loop as a disowned process +check_recorded_pwd_loop &! # Record the signalling loop's PID _POLLING_LOOP_PID="$!" } function stop_following_recorded_pwd { unset _FOLLOWING_PWD [[ -z "$_POLLING_LOOP_PID" ]] && return # Kill the background loop kill "$_POLLING_LOOP_PID" 2>/dev/null unset _POLLING_LOOP_PID } ,则会启动start_following_recorded_pwd作为不同的后台流程。这样,当您关闭shell时,您将不会收到恼人的“暂停作业”警告。记录循环的PID(通过+check_recorded_pwd_loop),以便稍后停止。

循环每秒向父shell发送一个$!信号。此信号被USR1捕获,TRAPUSR1()将在必要时重新打印提示。我不明白必须同时调用cdzle -R,但这是对我有用的神奇组合。

还有zle reset-prompt标志。由于每个终端都将定义_FOLLOWING_PWD函数,因此这将阻止它们处理该信号(和更改目录),除非您实际指定了该行为。

与录制当前工作目录一样,您可以致电TRAPUSR1停止整个自动stop_following_posted_pwd

将两半放在一起:

cd

最后,您可能希望这样做:

function begin_synchronize {
    start_recording_pwd
    start_following_recorded_pwd
}

function end_synchronize {
    stop_recording_pwd
    stop_following_recorded_pwd
}

这将在您的终端退出之前自动清理所有内容,从而防止您意外地离开孤立的信号环路。