リモートホストとdiffる

bashの「Process Substitution」という機能で、コマンドの実行結果を名前つきパイプから読めます。(thx かつみくん)

$ cat -n <(date; echo foo)
     1  Wed Jan 18 21:38:30 JST 2006
     2  foo

これを使って、リモートホストのファイルとdiffを取るシェル関数を作ってみました。

こんな感じで使えます。

$ rdiff -u ~/s.txt REMOTE_HOST:~/d.txt

また-Rオプションで、rsyncの-R, --relativeオプションの様にローカルホストのパスをリモートホスト相対パスとして指定できます。たとえば、ローカルホストの/very/very/long/path/t.txtとリモートホストの同じパスのファイルをdiffるときには次のようにしてリモートホスト側のパス指定を略記することができます。

$ rdiff -uR /very/very/long/path/t.txt REMOTE_HOST:/

ローカルマシンとサーバ上のファイルの比較や、同じ構成のサーバがたくさんあるときのファイル比較に使うとすごぉく便利です。

rdiff() {
  diff_orig=$(which diff 2>/dev/null)
  if [ -z "$diff_orig" ]; then
    echo "cannot find diff command." 1>&2
    return 1
  fi

  declare -a opts
  declare -a files

  optv=("$@")
  optc=${#optv[@]}

  files=(${optv[$optc-2]} ${optv[$optc-1]})
  unset optv[$optc-1]
  unset optv[$optc-2]

  file_relative=
  i=0
  for o in "${optv[@]}"; do
    case "$o" in
      *R*)
        optv[$i]=$(echo $o | sed -e 's/\-\?R//g')
        files[0]=$(readlink -f ${files[0]})
        file_relative=${files[0]}
        ;;
    esac
    i=$(($i+1))
  done
  optv=(${optv[@]})

  case "${files[@]}" in
    *:*)
      for i in 0 1; do
        case ${files[$i]} in
          *:*)
            eval $(echo ${files[$i]} | awk -F':' '{printf "t_host=%s t_file1=%s", $1, $2}')
            if [ -n "$file_relative" ]; then
              t_file1="$file_relative"
            fi

            files[$i]=<(ssh $t_host cat $t_file1)
            ;;
          *)
            ;;
        esac
      done
      ;;
    *)
      ;;
  esac

  $diff_orig "${optv[@]}" "${files[@]}"
}