Chienomi

【Bash * Yad テクニック】 リポジトリ同期ユーティリティ

開発::util

  • TOP
  • Articles
  • 開発
  • 【Bash * Yad テクニック】 リポジトリ同期ユーティリティ

Gistに公開した。

BashとYadの比較的深いテクニックを駆使したものである。

Repository Checker

ZshでなくBashなのは、「関数のエクスポート」がBashの固有の機能だからだ。

#!/bin/bash

function build_repoliststr {
  ruby -ryaml -e 'print YAML.load(ARGF).map {|i| i.join(":::") }.join("%%%")' $HOME/.config/reasonset/workrepos.yaml
}

export repoliststr="$(build_repoliststr)"

function splitrepos {
  echo $repoliststr | sed 's:%%%:\n:g'
}
export -f splitrepos

repolist=($(splitrepos))

function getchanges {
(
  cd $(getpath $1)
  echo -n "2:"
  hg status | perl -pe 's/\n/\\n/'
)
}
export -f getchanges

function commit {
  (
    cd $(getpath $1)
    echo -n "2:"
    if hg commit -A -m "Working snapshot from  widget."
    then
      echo COMMITED.
    fi
  ) | perl -pe 's/\n/\\n/'
}
export -f commit

function getpath {
  declare -A repoaa
  for i in $(splitrepos)
  do
    declare -a r=(${i/:::/ })
    repoaa[${r[0]}]="${r[1]}"
  done
  echo ${repoaa[$1]}
}
export -f getpath

repos="$(for i in ${repolist[*]}
do
  declare repoentry=(${i/:::/ })
  (
    cd ${repoentry[1]}
    if [[ -n "$(hg status)" ]]
    then
      echo ${repoentry[0]}
    fi
  )
done | perl -pe 's/\n/!/' | sed 's/!$//' )"

if [[ -z $repos ]]
then
  exit
fi

yad --width=1000 --height=500 --form --field='Repositories:CB' "$repos" --field="Changes:TXT" "" --field="CHECK:FBTN" '@bash -c "getchanges %1"' --field="COMMIT:FBTN" '@bash -c "commit %1"' > /dev/null

青写真

先日のEncFSの自動マウントと組み合わせるつもりで、マウントしたらリポジトリの更新状況をチェックし、コミットされてないものがあればそれを提示する。

これによって、複数の(同時に起動しているとは限らない)マシンで同期的な作業がしやすくなる。

ここで欲しいのは

  1. 更新があるリポジトリを提示する
  2. リポジトリを選択して内容を確認する
  3. コミットする

の3段階だ。

YADの複合フォームと更新ボタン

Zenityでも--formsで複合フォームは作ることができるが、フォーム部品がだいぶ限られる。 Yadの--formはかなり部品が豊富だ。

ここではリポジトリを選択するコンボボックス、更新を表示するテキストボックス、そして操作ボタンが欲しい。

これ自体は次のように示すことができる。

yad --form --field='Repositories:CB' 'A!B!C' --field="Changes:TXT" "" --field="CHECK:FBTN" 'echo CHECK' --field="COMMIT:FBTN" 'echo COMMIT'

ボタンを押すとコマンドが実行される。これに%1のようにしてフォーム部品の内容を引数として渡すことができる。

また、コマンドが@で始まるものについては、その出力でフォームの値を変更する。例えば

yad --form --field='Repositories:CB' 'A!B!C' --field="Changes:TXT" "" --field="CHECK:FBTN" '@echo "1:X!Y!Z"' --field="COMMIT:FBTN" 'echo COMMIT'

とすると、

echo "1:X!Y!Z"

の出力は

1:X!Y!Z

であり、これによりフォームの第1部品の値をX!Y!Zに変更する。 1行1エントリ形式で複数の値を変更することもでき、値に改行を含めたい場合は\nという文字列にすれば良い。

Bashの関数エクスポート

しかし、コマンドを実行して実現するためには、複合のコマンドを一発で実現できる必要がある。

もちろん、複数のスクリプトファイルを用意することもできるが、コンパクトにするためにBashには関数をエクスポートする機能がある。 これはかなり特殊なメカニズムだが、使う分にはexport -f <func_name>という形式なので難しくはない。

具体的にはBASH_FUNC_<func_name>%%という環境変数文字列となり、evalを使うことで関数として戻せるようになっている。

この仕組みにより、Yadを起動するBashプロセスで関数をエクスポートしておくと、Yadから起動されるBashプロセスでも使えるわけだ。

Bashの配列、連想配列

配列はこう

declare -a arr=(a b c)

あるいはこう

arr=(a b c)

でpush

arr+=(d)

Zshと違い、joinやsplitの処理はなく、そもそも配列操作はほとんどない。

連想配列はこう

declare -A assoc=(
  [a]=apple
  [b]=banana
  [c]=cherry
)

この構文のため、Zshみたいに配列との相互変換は効かない。追加はこう

assoc[d]=dragonfruit

配列や連想配列を複数プロセスで共有

共有する方法としては環境変数なのだが、環境変数は文字列しかない。

文字列から配列を組み上げる方法(split)はそれほど難しくない。要は、展開されたときにIFSで分かれるようにすれば良い。エントリにIFS、つまりはホワイトスペースを含む場合は難しいが、含まない前提であれば

str="A:B:C"
arr=(${str/:/ })

のようにすれば良い。

Zshの場合は連想配列もそれに似た処理、というかそもそもZshに変換するための機能があるが、Bashの場合ループ処理が必要になる。それが

function getpath {
  declare -A repoaa
  for i in $(splitrepos)
  do
    declare -a r=(${i/:::/ })
    repoaa[${r[0]}]="${r[1]}"
  done
  echo ${repoaa[$1]}
}

で組まれる$repoaaである。

splitreposは環境変数として渡された文字列をkey:::valueな配列にして返す。 これでループを回し、:::で分離することで2要素配列にする。あとは、

repoaa[${r[0]}]="${r[1]}"

で連想配列に入れられるわけだ。

使いやすい設定ファイルを使う

ハードコーディングしないためには、この環境変数となる文字列をファイルに出せば良いのだが、特殊なセパレータで結合された文字列はだいぶ書きづらい。

そこで、YAMLファイルにしてRubyを用いて

ruby -ryaml -e 'print YAML.load(ARGF).map {|i| i.join(":::") }.join("%%%")' $HOME/.config/reasonset/workrepos.yaml

としてYAMLファイルから文字列に変換している。

小技

sed 1

sed 's:%%%:\n:g'

%%%という文字列を改行文字(LF)に変換している。 sedは「置き換える文字列」のほうでは\nが使える。

perl

perl -pe 's/\n/\\n/'

改行文字(LF)を文字列\nに変換している。

perl -pe 's/\n/!/'

こっちは改行文字(LF)を!という文字列に変換している。

Perlなのは、左辺に\nを使うために必要だから。

sed 2

sed 's/!$//'

行末の!を消している。これは、join時に!が末尾についてしまうため。

EncFSとの連携

非常にシンプル。

#!/bin/zsh

open_encfs.zsh "$@" && repos-checker.bash