1

zshを実行している2つのiTermウィンドウがあります。1つはvimのドキュメントに使用します。もう1つは、シェルコマンドを実行するために使用します。2つのセッションの現在の作業ディレクトリを同期したいと思います。~/.cwdディレクトリを変更するたびに新しいディレクトリをファイルに出力することでこれができると思いました

alias cd="cd; pwd > ~/.cwd"

毎秒~/.dirsyncの内容を監視し、他のシェルがディレクトリを更新した場合はディレクトリを変更するシェルスクリプトを作成します。~/.cwd

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

しかし、それは機能しませんでした。次に、シェルスクリプトは常に独自のサブシェルで実行されることがわかりました。誰かがこれを機能させる方法を知っていますか?

4

1 に答える 1

2

警告エンプター:これは、Ubuntu 10.04でgnome-terminalを使用して実行していますが、を実行している*NIXプラットフォームで動作するはずですzsh

私も少し変更しました。「pwd」と「cwd」を混ぜる代わりに、私はどこでも「pwd」に固執しました。

現在の作業ディレクトリの記録

毎回関数を実行する場合は、関数またはより拡張可能な配列cdを使用することをお勧めします。関数を動的に追加および削除できるので、私は好みます。chpwdchpwd_functionschpwd_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
}

関数名にa+を追加することは、通常の使用からそれを隠すためのハックっぽい方法です(同様に、VCS_infoフックはすべてに接頭辞を付けることによってこれを行います)。+record_pwd+clean_up_pwd_record+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、次のコマンドを実行する前にディレクトリが自動的に変更されます。ただし、それまでは、プロンプトは最後の作業ディレクトリにとどまるため、最後のディレクトリなどのタブが完成します(ちなみに、空白行はコマンドとしてカウントされません)。しかし、それは直感的ではありません。

2番目の試み:信号とトラップの使用

端末に自動的cdにプロンプ​​トを再印刷させるために、事態はさらに複雑になりました。

いくつか検索したところ、$$(シェルのプロセスID)がサブシェルで変更されていないことがわかりました。したがって、サブシェル(またはバックグラウンドジョブ)は、その親にシグナルを簡単に送信できます。これを、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このようにして、シェルを閉じようとしたときに、迷惑な「ジョブの一時停止」の警告が表示されることはありません。ループのPIDは(を介して$!)記録されるため、後で停止できます。

ループは、親シェルにUSR11秒ごとにシグナルを送信するだけです。この信号はによってトラップされTRAPUSR1()cd必要に応じてプロンプトが再出力されます。zle -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
}

最後に、おそらくこれを実行する必要があります。

trap 'end_synchronize' EXIT

これにより、端末が終了する直前にすべてが自動的にクリーンアップされるため、孤立したシグナリングループが誤って残されるのを防ぐことができます。

于 2013-01-28T18:38:14.160 に答える