1

我有两个运行 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中执行。有谁知道使这项工作的方法?

4

1 回答 1

2

警告购买者:我在 Ubuntu 10.04 上使用 gnome-terminal 执行此操作,但它应该可以在任何运行zsh.

我也稍微改变了一下。我没有混合使用“pwd”和“cwd”,而是到处使用“pwd”。

记录当前工作目录

如果您想每次都运行一个函数cd,首选的方法是使用该chpwd函数或更可扩展的chpwd_functions数组。我更喜欢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_pwdand函数名称中添加 a+clean_up_pwd_record是一种将其从正常使用中隐藏起来的黑客方式(类似地,VCS_info 挂钩通过在所有内容前加上前缀来做到这一点+vi)。

使用上述方法,您只需start_recording_pwd在每次更改目录时调用即可开始记录当前工作目录。同样,您可以调用stop_recording_pwd以禁用该行为。stop_recording_pwd还会删除~/.pwd文件(只是为了保持清洁)。

通过这种方式,可以很容易地选择加入同步(因为您可能不希望您运行的每个 zsh 会话都这样做)。

第一次尝试:使用preexec钩子

与@Celada 的建议类似,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中没有变化。因此,子外壳(或后台作业)可以轻松地向其父外壳发送信号。将此与 zsh 允许您捕获信号这一事实相结合,并且您可以~/.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,这将+check_recorded_pwd_loop作为不承认的后台进程启动。这样,当您关闭 shell 时,您就不会收到烦人的“暂停作业”警告。循环的 PID 被记录(通过$!),因此可以稍后停止。

循环只是USR1每秒向父 shell 发送一个信号。此信号被 捕获,如有必要TRAPUSR1(),它将重新打印提示。cd我不明白必须同时调用zle -Rand 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
}

最后,您可能想要这样做:

trap 'end_synchronize' EXIT

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

于 2013-01-28T18:38:14.160 回答