Shell script

Alfred Workflowで複数のアイテムの実行ファイルをひとつにしてしまえ

の続き。

実行ファイルを一つにしてしまえ

前回は、Alfred WorkflowのGUIエディタから編集するのくそだるいので実行ファイルにしてしまえという話でした。

もはや複数の実行ファイルにパスとか書き込むのもめどいので、実行ファイルをひとつにしてしまいましょう。

このようなWorkflowがあるとしましょう。

スクリーンショット

Script Filter、Run Scriptの内容はそれぞれ以下の通りです。

Filter.sh
1
./hx.sh input "{query}"
Script.sh
1
./hx.sh run "{query}"

で、hx.shではcase文を利用して分岐処理を行います。

1
2
3
4
5
6
7
8
9
case $1 in
input )
case "$2" in
# 以下略
;;
run )
case "$2" in
# 以下略
;;

Alfred Workflowのアイテムごとに異なる引数1($1)が最初の分岐に利用され、その後の入力テキストである引数2以降($2,$3…)が実際の処理に利用されます。

実際どう?

実行速度については、数KBのファイルなら分割した場合と大差ない感じです。

今回は、ファイルが長大すぎてかえって編集がめどくなった感がなきにしもあらず。個々のスクリプトがどのくらい複雑かにもよりますが、ゴチャゴチャした分岐処理とかしないならできるだけまとめたほうが楽そうです。

AlfredでTab→Open Terminal Hereするのがめどいので一工夫

最近、なにかフォルダを探す場合は、ほぼAlfredのFile Navigationを試用しています。(Powerpack専用。)

で、ターミナルで開く場合、フォルダを見付けてから「Tab→Open Terminal Here」とやるのですが、これが微妙にめどい。タイプ数でいえば、「(Tab)te」で3タイプなんですけどね。

「Alfred Preferences > Advanced」に装飾キー+リターンへの割り当てメニューがあるのですが、この中に「ターミナルで開く」はありません。

スクリーンショット

というわけで、Alfred Workflowでやってみます。

スクリーンショット

File FilterのSearch Scopeは任意に。何も指定しなければ、デフォルトのAlfred検索結果が適用されます。

Run Scriptの内容は以下。File Filterからは選択したファイル・フォルダのフルパスが{query}として渡されます。

(2014-12-31修正)

1
2
3
4
5
6
7
8
9
10
11
12
q="{query}"
# 選択したのがディレクトリなら
if [ -d "${q}" ]; then
# そのまま変数に格納
dir="${q}"
# ディレクトリじゃなければ
else
# 親フォルダを変数に格納
dir="$(dirname $q)"
fi
# 変数を展開してiTerm2で開く
open -a iterm "$dir"

bashの変数展開において、末尾の/*最短一致を削除することで、親フォルダを得ることができます。

当初は↑の形にしていましたが、素直にdirnameコマンドを使うようにしました。

ただし、ここでFile Navigationを利用してフォルダを掘り始めると、このスクリプトは適用されないため、検索で直接出てくるファイル・フォルダしか開くことができません。素直に「Tab→Open Terminal Here」しましょう。

あと、このWorkflow使うときだけブックマークを検索対象から除くのができなくて悲しい。

Alfred Workflowでコンフィグファイルを利用する

ユーザ情報の管理がめんどくさい

最近Alfred Workflowを作ったり配布したりしているのですが、けっこう悩まされたのがユーザ情報の管理です。具体的には、いじるファイルを置いてあるフォルダパスとかです。

まあ、シェルスクリプトにdir=~/heroku/wbtmiu.herokuapp.comとか書いたりするわけですが、そうするとふたつ問題がありまして。

  1. パスを必要とするシェルスクリプト全てに記述する必要があり、変更が入ると書き換えがめどい
  2. 配布するとき削除しないと見られちゃう

1については、分岐処理とScript Filterを駆使してシェルスクリプトをまとめることである程度軽減できるのですが、限度がありますし2はどうしようもありません。

困る。

ファイルとして管理すればいい

そこで、ユーザ情報を書き込むファイルを作ってしまうことにします。

例えばWorkflowフォルダに「Config」というファイルを作って、2行目にフォルダパスを書き込むことにすれば、以下のようなスクリプトでパスを取り出すことができます。

1
2
3
# Configの2行目を出力し、自動でチルダ展開できないのでチルダをホームディレクトリに置換する
# 式の区切り文字にスラッシュを使うとパスと混ざるので、何でもいいけどとりあえず%を使う
dir=`sed -n 2p Config | sed "s%~%$HOME%"`

こうすれば、シェルスクリプトがいくつあっても、同様のスクリプトで情報を取り出すことができます。

Gitで管理する場合、.gitingnoreにConfigを書いておけばGithubとかで公開されることもありません。Configを生成するアクションを含めておくとなおよし。

Alfred Workflowのシェルで追加コマンドが利用できない理由とその対策

Alfred Workflowで利用できない追加コマンド

以前書きましたが、Alfred WorkflowのScript Filter、Run Script、Terminal Commandなどでは、Homebrewパッケージなどのユーザが追加したコマンドが利用できません。

そこで虫アイコンをクリックしてログを確認してみると、gsed: command not foundとなっています。

上記の記事を書いたときはパス通ってるがな!としか思わなかったのですが、よく考えたら、普通にターミナルからbash使ったときとAlfred Workflowから使ったときでは、単にPATHが違うんじゃね?

環境変数を確認してみると…

Run Scriptでecho $PATHして、シェルスクリプト内で環境変数PATHを出力してみると、以下のようになっています。

1
/usr/bin:/bin:/usr/sbin:/sbin

普通にターミナルでやると、以下の通りです。

1
/usr/local/heroku/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/git/bin

確かに違いがありました。gsedのwhichは/usr/local/bin/gsedです。Alfred Workflowのシェルスクリプトではパスが通っていません。

Alfred WorkflowからTerminal Commandを実行した場合は、ターミナルを起動した場合と同じになりました。

(追記)ログインシェルとして実行されていないので、bashがPATHに格納する内容に違いあると思われます。また.bash_profileも読み込まれないので、そちらで行うPATHの追加処理は当然反映されないことになります。

というわけで、 PATH=$PATH:/usr/local/bin としてからgsedを実行すると、問題なく実行されます!

既存の文書群から要素を抽出してインクリメンタルサーチするAlfred Workflow

さらにリベンジ。

抽出したタグが重複するのはやっぱり改行記号だった

ふとタグ一覧をファイルに書き出してVimで開いたら一発でした。

^Mを削除してタグ重複は解決。

配列変数を駆使してタグ一覧を取得する

既存のファイルからタグ一覧を取得するのはそう難しくないのですが、問題はそれをいかにしてインクリメンタルサーチするかです。

ファイルに書き出してしまうのが簡単ですし、デプロイ時にでもタグリストを作成するようにしておけば高速化にもつながりますが、どうにもいらんファイルができるのが気に食わない。

そこで、配列変数に格納して、完全な形でそれぞれのタグを取り出せるようにします。

構成はこう。

スクリーンショット

Script Filterの内容は以下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
cd ~/heroku/wbtmiu.herokuapp.com/source/_posts
q="{query}"
# tags変数をリセット。これやらないと増えていくみたい
tags=()
# コマンドの各結果を一度tag変数に格納し、tag変数を配列変数tagsに追加していく
# 以下コマンドの内容
# _postsフォルダの各ファイルにループ処理
# フロントマターの区切りを示す'---'までをsedで切り出し
# '- 'で始まる行を抽出
# '- 'を削る
# ソートして重複を削除
while read tag; do tags+=("$tag"); done < <(for file in `ls`; do sed '/^---$/q' "$file" | grep '^- ' | sed 's/^- //g'; done | sort -u)
# XML上部の共通部分を出力
cat << EOB
<?xml version="1.0"?>
<items>
EOB
# 区切り文字を改行にする
IFS=$'\n'
# 配列変数tagsの全要素を改行区切りで出力→入力文字を含むものを抽出
for i in `echo "${tags[*]}" | grep "$q"`
# 抽出結果ごとにitem要素を出力
do
cat << EOB
<item uid="$i" arg="$i" valid="YES" >
<title>${i}</title>
</item>
EOB
done
# XML下部の共通部分を出力
echo "</items>"

/bin/bashスクリプトでコピーして結果を出力。

1
2
echo -n "{query}" | pbcopy
echo -n "{query}"

入力中はこうなります。

入力中

さすがに時間はかなりかかりますが、前回のWorkflowと違ってHexoテーマに依存しないため、少しいじれば多くの静的サイトジェネレータで利用できるはずです。

参考

現在のシェル、がなかなか分からなかったのですが、この記事のおかげで実現できました。

シェルスクリプトで日付フォーマットを自動補完する

Todo.txtに追記するAlfred Workflowで日付自動補完機能が欲しかった

ので、書いてみました。

今日が2014年5月5日だとします。

input output note
+0 2014-05-05 +n はn日後
+1 2014-05-06
+365 2015-05-05
7 2014-05-07 今月7日
1 2014-06-01 5月1日は過去日なので来月にする
7-10 2014-07-10 今年7月10日
1-10 2015-01-10 今年1月10日は過去日なので来年にする
2014-01-01 2014-01-01 日付が入力された場合は訂正しない
2014-1-1 2014-01-01

該当部分のシェルスクリプト

文字列置換と日付計算を組み合わせて処理します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# dat変数に入力された日付が格納されている
# +n形式なら
if echo "${dat}" | grep -q "\+[0-9]*$";then
# dateコマンドの-vオプションでn日後の日付を出力
dat=`date -v${dat}d '+%Y-%m-%d'`
fi
# '1-2桁の数字'なら('日'なので)
if echo "${dat}" | grep -q "^[0-9]\{1,2\}$";then
# 先頭に0を付けてから後方2文字を出力。改行文字を含まないように-nをつける
# -nなしの後方3文字でも同様
dat=`echo -n 0${dat} | tail -c 2`
# さらに、出力された'日'が現在の'日'より小さい数字である場合
if [ "${dat}" -lt `date '+%d'` ];then
# '来月'を出力する
month=`date -v+1m '+%m'`
# でなければ
else
# '今月'を出力する
month=`date '+%m'`
fi
# 'mm'-'dd'フォーマットに変換
dat="$month"-"${dat}"
fi
# '1-2桁の数字'-'1-2桁の数字'なら('月'-'日'なので)
if echo "${dat}" | grep -q "^[0-9]\{1,2\}-[0-9]\{1,2\}$";then
# 'mm-dd'フォーマットに修正
# 'mm': ハイフンとそれ'以降'の数字を削除→先頭に0を追加→改行を付けずに再度出力→後方2文字を出力
# 'dd': ハイフンとそれ'以前'の数字を削除→先頭に0を追加→以下同文
dat=`echo ${dat} | sed -e 's/-[0-9]*//' -e 's/^/0/' | xargs echo -n | tail -c 2`-`echo ${dat} | sed -e 's/^[0-9]*-//' -e 's/^/0/' | xargs echo -n | tail -c 2`
# 'dateから数字のみを残したもの'が現在の'月日'より小さい数字である場合
if [ `echo "${dat}" | tr -dc [0-9]` -lt `date '+%m%d'` ];then
# '来年'を出力
year=`date -v+1y '+%Y'`
else
# '今年'を出力
year=`date '+%Y'`
fi
# 'yyyy-mm-dd'フォーマットに修正
dat="$year"-"${dat}"
fi
# '4桁の数字'-'1-2桁の数字'-'1-2桁の数字'なら('年'-'月'-'日'なので)
if echo "${dat}" | grep -q "^[0-9]\{4\}-[0-9]\{1,2\}-[0-9]\{1,2\}$";then
# 'yyyy-mm-dd'フォーマットに修正
# 'yyyy': 先頭4文字を出力
# 'mm': ハイフンとそれ'以降'の数字を削除→ハイフンとそれ'以前'の数字を削除→先頭に0を追加→改行を付けずに再度出力→後方2文字を出力
# 'dd': 2つのハイフンとそれ'以前'の数字を削除→先頭に0を追加→以下同文
dat=`echo "${dat}" | head -c 4`-`echo ${dat} | sed -e 's/^[0-9]*-//' -e's/-[0-9]*$//' -e 's/^/0/' | xargs echo -n | tail -c 2`-`echo ${dat} | sed -e 's/^[0-9]*-[0-9]*-//' -e 's/^/0/' | xargs echo -n | tail -c 2`
# 省略せずに入力してるんだから現在日付との比較はしなくていいでしょ?
fi

caseとかwhileとかでもっとスマートにやろうとしましたが、結局ifみだれうちになっちったテヘッ。

caseで正規表現が使えれば楽なんですけどねえ。[]とワイルドカードの*しか使えない模様。

ああでも一応、+-を数えてやれば、4caseに分類はできるのか。そっちのほうがいいかな?

やりなおし

test結果を変数に格納し、その組み合わせパターンをcaseで判定して分岐します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# dat変数に入力された日付が格納されている
# RP変数に格納:+n形式なら0、そうでなければ1
[ `echo "$dat" | grep "^+[0-9]\+$"` ];RP=$?
# RP変数に格納:d形式なら0以下同文
[ `echo "$dat" | grep "^[0-9]\{1,2\}$"` ];RD=$?
# RP変数に格納:m-d形式なら0以下同文
[ `echo "$dat" | grep "^[0-9]\{1,2\}-[0-9]\{1,2\}$"` ];RM=$?
# RP変数に格納:y-m-d形式なら0以下同文
[ `echo "$dat" | grep "^[0-9]\{4\}-[0-9]\{1,2\}-[0-9]\{1,2\}$"` ];RY=$?
# ${RP}${RD}${RM}${RY}の内容により分岐
case "${RP}${RD}${RM}${RY}" in
# +n形式の場合
0111)
# n日後の日付を出力
dat=`date -v${dat}d '+%Y-%m-%d'`
;;
# d形式の場合
1011)
dat=`echo -n 0$dat | tail -c 2`
if [ "$dat" -lt `date '+%d'` ];then
month=`date -v+1m '+%m'`
else
month=`date '+%m'`
fi
date="$month"-"$dat"
;;
# m-d形式の場合
1101)
dat=`expr "0${dat}" : '[0-9]*\([0-9]\{2\}\)-'`-`echo $dat | sed -e 's/^[0-9]*-//' -e 's/^/0/' | tail -c 3`
if [ `echo "$dat" | tr -dc [0-9]` -lt `date '+%m%d'` ];then
year=`date -v+1y '+%Y'`
else
year=`date '+%Y'`
fi
date="$year"-"$dat"
;;
# y-m-d形式の場合
1110)
dat=`echo "${dat}" | head -c 4`-`echo "$dat" | sed -e 's/^[0-9]*-[0-9]\{1,2\}-//' -e 's/^/0/' | tail -c 3`-`echo ${dat} | sed -e 's/^[0-9]*-[0-9]*-//' -e 's/^/0/' | tail -c 3`
;;
esac

こっちのほうが少しスマートかな?

静的サイトジェネレータの記事ファイルから公開URLを取得するAlfred Workflow

Webからのインクリメンタルサーチの仕方がわからんともいう

そんなわけで、ブログ内リンクとかを張りたいとき、「Googleサイト内検索→該当記事開く→AppleScriptでMarkdownリンク取得」とかしなきゃならんわけです。

いやだ! しんどい! Alfred助けて!

URL規則に基づいて公開URLを取得する

現在、このブログのはHexoで構築されていますが、Hexoの_config.ymlでは、パーマリンクは以下のように設定されています。

1
permalink: :year/:month/:day/:pid/

したがって、この規則に基づいて元ファイルからURLを取得することが可能です。

なお、フロントマターは以下のようになっています。

1
2
3
4
5
6
7
8
title: 静的サイトジェネレータの記事ファイルから公開URLを取得するAlfred Workflow
date: 2014-10-25 19:12
pid: bb761da3b47ab091d59975868e9391fc
status: p
tags:
- Alfred Workflow
- Shell script
- bash

pidは、titleからmd5ハッシュで生成されています。(その前はランダム生成だった。)

やってみた:File Filterを利用

Alfred Workflow自体にFile Filterというファイル検索機能がありますので、これを利用します。

File Filterでファイル名を取得し、これをシェルスクリプトでリンクに加工の上、「Copy to Clipboard」でコピーします。

ついでに、ブラウザで開くこともできるようにしておきます。

スクリーンショット

「Copy to Clipboard」につながる/bin/bashスクリプトの内容は以下の通りです。

1
2
3
4
5
6
7
8
9
baseurl=http://wbtmiu.herokuapp.com/
file={query}
title=`grep -m1 '^title' $file | cut -c 8-`
date=`grep -m1 '^date' $file | sed 's/-/\//g' | cut -c 7-16`
pid=`grep -m1 '^pid' $file | sed 's/^pid: //'`
arg="[${title} | 豆腐メンタルは崩れない](${baseurl}${date}/${pid})"
echo $arg

その下のスクリプトの内容は以下の通りです。

1
2
3
4
5
6
7
8
baseurl=http://wbtmiu.herokuapp.com/
file={query}
date=`grep -m1 '^date' $file | sed 's/-/\//g' | cut -c 7-16`
pid=`grep -m1 '^pid' $file | sed 's/^pid: //'`
arg="${baseurl}${date}/${pid}"
open $arg

少し見づらいですが、修飾キーで分岐するようになっています。CtrlでMarkdownリンクをコピー、ShiftでURLを開きます。

ユーザごとに異なるURL規則に対応する?

やろうと思えば、_config.ymlからpermalinkを読み込んでURL変換することもできなくはないです。

タイトルは利用するテーマ次第なので厳しいですが、URLさえ特定できれば、Web経由で取得できるはずです。が、そこまでやりたくないのでここで終了。

ローカルの何かをインクリメンタルサーチするAlfred Workflow

リベンジ:静的サイトジェネレータの自動タグ補完的な何か

以前、静的サイトジェネレータの自動タグ補完的な何かを作ろうとしました。

この時は、問題が2つあって頓挫しました。

  1. 重複しないタグリストが取得できない
  2. インクリメンタルサーチのやり方がわからん

どちらも解決したので、あらためて作ってみました。

タグリストは生成済みコンテンツから取得する

利用するテーマによるでしょうが、現在利用中のLightテーマでは、プロジェクトフォルダ内のpublic/tagsフォルダに、タグ毎のタグ名/index.htmlが生成されていました。

これを利用すれば、タグリストは比較的簡単に取得できます。

Script Filterによるローカルの何かのインクリメンタルサーチ

ここが本題。

Alfred Workflowにおいて、インクリメンタルサーチして選択候補を表示するには、Script Filterを利用します。

Script Filterのスクリプトにおいては、XMLドキュメントを標準出力にエコーすることで選択候補が生成されます。個々の選択候補はitem要素に該当するので、検索結果に応じてitem要素を生成すればいいわけです。

bashスクリプトの場合、public/tagsフォルダのfind結果からforループを形成すればよろしい。

Alfred Workflowの構成はこのように。

スクリーンショット

Script Filterの内容は以下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
q=`echo "{query}" | sed 's/ /\*/'`
cd ~/heroku/wbtmiu.herokuapp.com/public/tags
res=`find . -iname "${q}*" -type d`
if [ -n "$q" ];then
if [ -z "$res" ];then
cat << eob
<?xml version="1.0"?>
<items>
<item uid="none" arg="none" valid="YES" autocomplete="none" >
<title>No result</title>
<subtitle>It's new tag</subtitle>
</item>
</items>
eob
else
cat << eob
<?xml version="1.0"?>
<items>
eob
for tag in `find . -iname "${q}*" -type d`
do
tag=`echo "$tag" | sed -e 's/^\.\///' -e 's/-/ /'`
cat << EOB
<item uid="$tag" arg="$tag" valid="YES" autocomplete="$tag" >
<title>${tag}</title>
<subtitle>copy '${tag}'</subtitle>
</item>
EOB
done
echo "</items>"
fi
fi

シェルスクリプトアクションの内容は以下。(2014-10-27修正)

1
2
echo -n "{query}" | pbcopy
echo -n "{query}"

エコーに改行が含まれないように、-nオプションを付けます。

選択候補は、以下のように表示されます。

選択候補

この場合、以下のようなXMLドキュメントが生成されていることになります。

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0"?>
<items>
<item uid="Mac" arg="Mac" valid="YES" autocomplete="Mac" >
<title>Mac</title>
<subtitle>copy Mac</subtitle>
</item>
<item uid="Markdown" arg="Markdown" valid="YES" autocomplete="Markdown" >
<title>Markdown</title>
<subtitle>copy Markdown</subtitle>
</item>
</items>

しくみ

  • 部分一致が取れるようにスペースを*に置換する
  • findコマンドでタグ名を検索
    • -inameで大文字小文字を区別しない
    • -type dでディレクトリのみ対象とする
    • 出力が./Macとかになるので先頭の./を削除
    • Alfred WorkflowAlfred-Workflowとか、スペースがエスケープされているので元に戻す
  • 「検索結果なし」を判定する
    • 引数が1文字以上である
    • 検索結果(res変数)が空である
    • どちらも満たす場合は専用のXMLドキュメントを出力
  • 「検索結果なし」でない場合
    • XMLドキュメント上部の繰り返さない部分を出力
    • 検索結果ひとつごとにループ
      • item要素を出力
      • タグ名を次のアクションに渡す
    • XMLドキュメント下部の繰り返さない部分を出力

そうだ、エスケープされてるんだ。タグ名自体に-が含まれる可能性もあるから、やっぱり元ファイルから抽出したほうがいいなあ……。

ともあれ、このようにitem要素をループで生成することで、Alfred Workflowでインクリメンタルサーチを実現することができます。

記事ファイルからタグを自動補完

やりました。

シェルスクリプトからターミナルを起動して、Hexoコマンドを実行する

Alfred Workflowのシェルスクリプトから実行できないコマンド

Alfred Workflowではシェルスクリプトを実行することができますが、どうもこれだと最初からMacに入っていないコマンドが実行できないようです。

例としては、Homebrewでインストールしたパッケージ全般や、静的サイトジェネレータ(Hexo等)のコマンドがあります。

これらのコマンドは、普通にファイルとして作成したシェルスクリプトでは実行できますが、Alfred Workflowのシェルスクリプトアクションでは実行できません。そのため、Alfred Workflowでこれらのコマンドを利用する場合、ターミナルアクションを利用する必要がありました。

別にそれでもいいのですが、そうするとフォルダパスなどを変更する際に書き換える場所が増えるため、ちと面倒です。

(現在は解決済み:Alfred Workflowのシェルで追加コマンドが利用できない理由とその対策 | 豆腐メンタルは崩れない

そこで、シェルスクリプトからターミナルを起動できないのか?と考え、やってみました。

シェルスクリプトからターミナルを起動する

利用するターミナル(というか端末エミュレータ)によってはオプションでコマンドを渡せるものもあるようですが、Mac標準のターミナル.appや、私が利用しているiterm2ではそのようなオプションはないようです。

そこで、シェルスクリプトを出力するシェルスクリプトを書き、出力されたシェルスクリプトをデフォルトターミナルで開くという方法があるようです。

やってみた

Alfred Workflowのシェルスクリプトに、以下のように記述します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# dir変数にHexoプロジェクトフォルダパスを格納
dir=~/heroku/wbtmiu.herokuapp.com
# tmp変数にファイル名を格納。
# $$はプロセスIDであり、プロセスが終了する前に次のプロセスを起動した場合も、ファイル名が重複しない。
tmp=/tmp/$$myhexoterm
{
cat << EOF > ${tmp}
#!/bin/bash
# ターミナルで実行するコマンドを記述
# 成功した場合&&で次のコマンドを実行
cd $dir
hexo server
EOF
chmod +x ${tmp}
} && {
open ${tmp}
} && {
sleep 5
rm ${tmp}
echo "Runed local server"
} || {
# 失敗した場合エラーメッセージをエコー。通知する
echo "Failed"
}

このシェルスクリプトを実行すると、デフォルトターミナル(私の場合はiterm2)が起動して、プロジェクトフォルダでHexoのローカルサーバーを立ち上げます。

実行結果

このように、Alfred Workflow上はシェルスクリプトアクションでありながら、実際にはターミナル上で処理を行うことができます。

Alfred WorkflowのScript Filterを利用して、共通の値をまとめる〜シェルスクリプト編〜

Alfred WorkflowのScript Filterを利用して、共通の値をまとめる | 豆腐メンタルは崩れないからの続き。

前回は、Script Filter部分で入力テキストに基づく分岐処理を行い、実行するコマンドをターミナルアクションに引き渡しました。

今回は、シェルスクリプトアクションによる分岐処理を考えます。

シェルスクリプトには入力テキストをそのまま引き渡す

前回は、まあ実質シェルスクリプトでありながらAlfred Workflowにおけるアクションはターミナルコマンドだという代物だったわけですが、今回はシェルスクリプトアクションを試用する場合です。

そもそも、前回ターミナルコマンドを利用したのは、Alfred WorkflowのシェルスクリプトからHexoコマンドが利用できないためです。そういう事情さえなければ、判定処理などはScript Filterではなくアクション部分で実行したほうがわかりやすいし、インクリメンタルに処理しない分多少は高速化できるでしょう。

というわけで、今回はScript Filterの処理結果ではなく、入力テキストをそのままアクションに引き渡します。そのため、フォルダパスなどもアクション部分に記述します。

やってみた

Script Filterの内容は以下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
q="{query}"
read cmd arg <<< "$q"
case "$cmd" in
"d" )
t="Create draft"
st="title: "
;;
"n" )
t="Create new article"
st="title: "
;;
"p" )
t="Publish drafts"
st="\"status: p\" in \"drafts\" folder"
;;
"u" )
t="Unpublish article"
st="\"status: d\" in \"posts\" folder"
;;
"o" )
t="Open website"
st="Open in brower"
;;
"conf" )
t="Open '_config,yml'"
st="with default editor"
;;
"" )
t="Input command"
st="New/Draft/Publish/Unpublish/Open url"
;;
* )
t="Undefined command"
st="n / d / p / u / o / conf"
;;
esac
cat << eob
<?xml version="1.0"?>
<items>
<item uid="hexo" arg="$q" valid="YES" autocomplete="hexo" >
<title>(ctrl) Hexo: $t</title>
<subtitle>${st}${arg}</subtitle>
</item>
</items>
eob

シェルスクリプトの内容は以下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# Path of your hexo project folder
dir=~/heroku/wbtmiu.herokuapp.com
# Your favorite editor
editor=FoldingText
# Your site URL
url=http://wbtmiu.herokuapp.com/
q="{query}"
read cmd arg <<< "$q"
cd $dir
case "$cmd" in
[nd] )
title=`echo "$arg" | sed -e 's/ /-/g' -e 's/[\.\/]//g'`
key=`md5 -qs "$title"`
if [ $cmd = n ];then
savedir=source/_posts/
date="`date '+%Y-%m-%d %H:%M'`"
today="`date '+%Y-%m-%d'`-"
status=p
else
savedir=source/_drafts/
date=""
today=""
status=d
fi
file="${savedir}${today}${title}.md"
echo "title: $arg" > "$file"
echo "date: $date" >> "$file"
echo "pid: $key" >> "$file"
echo "status: $status" >> "$file"
echo "tags:" >> "$file"
echo "- " >> "$file"
echo "---" >> "$file"
open -a $editor "$file"
echo "Created: \"$title\""
;;
p )
cd source/_drafts
num=`grep -l '^status: p$' *.md | wc -l`
for file in `grep -l '^status: p$' *.md`
do
title=`grep -m1 'title:' $file | cut -c 8- | sed -e 's/ /-/g' -e 's/[\.\/]//g'` #クエリ文字列を置換して変数に格納
cat $file | (rm $file; sed -e "s/^date: .*$/date: `date '+%Y-%m-%d %H:%M'`/" > $file) #date項目に日時を追加
mv $file ../_posts/`date '+%Y-%m-%d'`-$title.md
done
echo "Published: $num articles"
;;
u )
cd source/_posts
num=`grep -l '^status: d$' *.md | wc -l`
for file in `grep -l '^status: d$' *.md`
do
name=`echo $file | cut -c 12-`
cat $file | (rm $file; sed 's/^\(date: \).*$/\1/' > $file)
mv $file ../_drafts/$name
done
echo "Unpublished: $num articles"
;;
o )
open $url
echo "Opened: Website"
;;
"conf" )
open _config.yml
echo "Opened: '_config.yml'"
esac

しくみ

はまあ前回と対して変わらんですね。判定→処理内容決定までの部分をInputではなくActionで行っているのが違いです。Input(Script Filter)では、入力に応じて実行される内容を表示させています。

前回は引数が空である場合をtestで判定していましたが、よく考えたらcase""の場合ってことで問題ありませんでしたな。

また、シェルスクリプトの分岐処理では、ndの処理における共通部分をまとめるために、caseにおいて[nd](nまたはd)で判定した後、内部でさらにifによる分岐を行っています。これにより、出力されるテンプレートを変更する場合も、書き換えるのが1ヶ所で済みます。

また、従来のWorkflowと比べると、関連する内容がひとつのスクリプトにまとめられているため、一括置換などの処理も容易になり、メンテナンス性を向上させることができました。