【Bash * Yad テクニック】 リポジトリ同期ユーティリティ
開発::util
序
BashとYadの比較的深いテクニックを駆使したものである。
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の自動マウントと組み合わせるつもりで、マウントしたらリポジトリの更新状況をチェックし、コミットされてないものがあればそれを提示する。
これによって、複数の(同時に起動しているとは限らない)マシンで同期的な作業がしやすくなる。
ここで欲しいのは
- 更新があるリポジトリを提示する
- リポジトリを選択して内容を確認する
- コミットする
の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
Gist アップデート (2021-04-21)
$XDG_CONFIG_HOME
を使うように変更- ディレクトリの先頭が
$HOME/
である場合ホームディレクトリに、$DOC
または$DOCUMENTS
である場合ドキュメントディレクトリに(xdg-user-dirs
を使って)、/
で始まらない場合はホームディレクトリに置き換えられるように変更 - diffボタンの追加