yyyy/mm/dd
で開始と終了を入力すると、毎日分のリンクURLを出すスクリプト。
成果物
前回
計画
このスクリプトがどんなものかはgithubを見てもらうとして、ここではどんな感じで作ったか書き残しておく。
方針
ゴール
以下のようなリンクを作る。日付がインクリメントされて指定した期間分だけ出力される。
* [http://ytyaru.hatenablog.com/entry/2000/01/01/000000:title] * [http://ytyaru.hatenablog.com/entry/2000/01/02/000000:title] * [http://ytyaru.hatenablog.com/entry/2000/01/03/000000:title] ...
過程
URLの一般化
はてなブログのベースURL。
- 基礎:
http://ytyaru.hatenablog.com/
- 記事:
http://ytyaru.hatenablog.com/entry/YYYY/mm/dd/HHMMSS
変数にすると以下。
${SCHEME}${ID}.${DOMAIN}/entry/${DATE}/${TIME}
URLのうち変化しうる全項目を一般化すると以下。
${SCHEME}${ID}.${DOMAIN}/entry/${DATE}/${TIME}
変数 | 値の例 |
---|---|
SCHEME |
http:// |
ID |
ytyaru |
DOMAIN |
hatenablog.com ,hatenablog.jp ,hateblo.jp ,hatenadiary.com ,hatenadiary.jp |
DATE |
YYYY/mm/dd |
TIME |
HHmmss |
リンクコード作成
以下のような工程を踏む。
- はてなブログURL作成:
${SCHEME}${ID}.${DOMAIN}/entry/${DATE}/${TIME}
- URLリンク
- 箇条書き
今回は「はてな記法+Markdown」の* [${URL}${OPT}]
形式を使う。理由は、タイトル文字列を取得せずとも自動的に表示してくれるから。
スクリプト化
前回のコードを確認する。
USER=ytyaru TIME=000000 START=2021/11/27 END=2022/03/17 DAYS=$(expr \( `date --date "${END}" +%s` - `date --date "${START}" +%s` \) / 86400) seq 0 ${DAYS} | xargs -n1 -i date -d${START}+{}days +%Y/%m/%d | sed 's/^/\* \[http:\/\/'"${ID}"'.hatenablog.com\/entry\//g' | sed 's/$/\/'"${TIME}"':title\]/g' | xsel -bi
コマンド化するときの入力値を考える。
hatenalink.sh -u ytyaru -t 000000 -s 2021/11/27 -e 2022/03/17
各引数はそれに対応するシェル変数を用意する。別のスクリプトで調整できるようにするため。
変数 | デフォルト値 | 意味 |
---|---|---|
USER |
ytyaru |
URLに用いるはてなID(ユーザ名?) |
TIME |
000000 |
URLに用いる記事の時刻(記事URLが年月日時分秒の形式であること) |
END |
実行時の日付 | URLに用いる記事の日時(終了) |
START |
END から一週間前 |
URLに用いる記事の日時(開始) |
実行時、各引数のデフォルト値をファイルから読み込むようにする。毎回入力するのは面倒なため。ユーザ固有値を編集しやすくするため。
USER=${USER:-ytyaru} TIME=${TIME:-000000} END=${END:-$(date "+%Y/%m/%d")} START=${START:-$(date -d '${END} 7 day ago' '+%Y/%m/%d')}
アプリを実行すると以下のような流れに分岐する。
- 実行
- コマンド分岐
- サブコマンド(メイン処理せず正常終了)
- ヘルプ表示
- バージョン表示
- メイン処理
- 引数の受け渡し
- デフォルト値の読込
- コマンド引数の取得
- 引数の妥当性確認(バリデート)
- リンク出力
- 引数の受け渡し
- 他
- ヘルプ表示
- サブコマンド(メイン処理せず正常終了)
- コマンド分岐
アプリ実行したときの出力結果は、大まかに言って以下のように分岐する。
- コマンド
- 正常系
- リンク出力
- ヘルプ表示
- バージョン表示
- 異常系(存在しないパラメータを指定した)
- ヘルプ表示
- 正常系
出力内容の詳細は以下。
- 出力
このアプリで欲しいものだけをstdout
に出すようにしている。これは変数で値を受け取ることができるようにするためだ。たとえばRES=$(app.sh 2>/dev/null)
のようにすれば、出力結果であるリンクだけを取得できる。もし警告やデバッグなどが出力されたとしても、それらはすべてstderr
にあり、それらは/dev/null
で捨てているため。
出力型 | 概要 |
---|---|
成果物 | 本質。このアプリで求める出力結果 |
エラー | 失敗。成果物を出力できず異常終了する。その原因と対処を表示する。 |
警告 | 補完。入力値が不正だが自動補正して続行した。意図しない結果かもしれないため警告を表示する。 |
デバッグ | 詳細。開発者用に変数値などを表示する。 |
bashではreturn
値は0
〜255
の整数値しか取得できない。そこで、標準出力された文字列を変数で受け取ることによって文字列の戻り値を得るようにする。しかし、成果物とそれ以外が混在してしまう。そこで、出力先ファイルディスクリプタを分けることで取捨選択できるようにする。
所感
細かいことを調べたり、煮詰めていくのに時間がかかってしまった。
コードの不満点は、コマンド引数の解析を抽象化してDRYに書けなかった所。たとえば-D
フラグは引数順序に関係なく最初に実行するようにgetopts
とは別枠でコードを書かねばならなかった。サブコマンドについても同じく別枠で書くことになる。しかもexit
するので処理の流れがわかりにくい。getopts
でサブコマンドのhelp
,version
をするときはexit
するなど他の引数の処理と異なるのが気になる。
getopts
よりもっと包括的で楽に書けるライブラリはないのか? 色々考えると、そもそもシェルの仕様がクソすぎてコマンド解析が大変になってしまうという結論に至った。CLIの宿命と思って諦めるしかないのか。
CLIについては、できるだけ引数が必要にならないように作るのが最善。でも、実際そんなコマンドって無いんだよなぁ。わかりやすくて短いコマンド名にも限りがあるからすぐ飽和してしまう。だから応用が効くよう引数を受け取って汎用的にしたいのに先述のザマだよ……。かといってコマンド名に日本語や漢字を使おうものなら表記ゆれで死ぬだろう。
対象環境
- Raspbierry pi 4 Model B
- Raspbian buster 10.0 2019-09-26 ※
- bash 5.0.3(1)-release
$ uname -a Linux raspberrypi 4.19.75-v7l+ #1270 SMP Tue Sep 24 18:51:41 BST 2019 armv7l GNU/Linux