やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

はてなブログ記事のリンクを日付連番に作る

 yyyy/mm/ddで開始と終了を入力すると、毎日分のリンクURLを出すスクリプト

成果物

前回

計画

 このスクリプトがどんなものかはgithubを見てもらうとして、ここではどんな感じで作ったか書き残しておく。

方針

 Bashスクリプト製でサクッと作りたい。

ゴール

 以下のようなリンクを作る。日付がインクリメントされて指定した期間分だけ出力される。

* [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

リンクコード作成

 以下のような工程を踏む。

  1. はてなブログURL作成: ${SCHEME}${ID}.${DOMAIN}/entry/${DATE}/${TIME}
    1. はてな記法
      1. 末尾オプション(:title, :bookmark, :detail, :barcode
  2. URLリンク
    1. HTML: <a href="$URL">$TITLE</a>
    2. Markdown A: [$TITLE]($URL)
    3. Markdown B: [$TITLE][$ID], [$ID]:$URL
    4. はてな記法: [${URL}${OPT}]
  3. 箇条書き
    1. HTML: <ul><li><a href="$URL">$TITLE</a></li></ul>
    2. Markdown A: * [$TITLE]($URL)
    3. Markdown B: * [$TITLE][$ID], [$ID]:$URL
    4. はてな記法Markdown: * [${URL}${OPT}]

 今回は「はてな記法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')}

 アプリを実行すると以下のような流れに分岐する。

  • 実行
    • コマンド分岐
      • サブコマンド(メイン処理せず正常終了)
        • ヘルプ表示
        • バージョン表示
      • メイン処理
        1. 引数の受け渡し
          1. デフォルト値の読込
          2. コマンド引数の取得
        2. 引数の妥当性確認(バリデート)
        3. リンク出力
        • ヘルプ表示

 アプリ実行したときの出力結果は、大まかに言って以下のように分岐する。

  • コマンド
    • 正常系
      • リンク出力
      • ヘルプ表示
      • バージョン表示
    • 異常系(存在しないパラメータを指定した)
      • ヘルプ表示

 出力内容の詳細は以下。

  • 出力
    • 出力先:ファイルディスクリプタ1(stdout)
      • リンクを出力する(他の出力は一切なし)
    • 出力先:ファイルディスクリプタ2(stderr)
      • エラー(異常終了)
        • TIMEが6桁でない
        • TIMEが数字でない
        • STARTが有効な日付でない
        • ENDが有効な日付でない
      • 警告(継続するが警告を表示する)
        • 指定したID,DOMAINにおけるURLが存在しない(HTTP-CODE200でない)
        • 指定したIDは無料はてなブログドメイン内に存在しない(HTTP-CODE200でない)
      • デバッグ出力(-D引数フラグがあるときのみ)

 このアプリで欲しいものだけをstdoutに出すようにしている。これは変数で値を受け取ることができるようにするためだ。たとえばRES=$(app.sh 2>/dev/null)のようにすれば、出力結果であるリンクだけを取得できる。もし警告やデバッグなどが出力されたとしても、それらはすべてstderrにあり、それらは/dev/nullで捨てているため。

出力型 概要
成果物 本質。このアプリで求める出力結果
エラー 失敗。成果物を出力できず異常終了する。その原因と対処を表示する。
警告 補完。入力値が不正だが自動補正して続行した。意図しない結果かもしれないため警告を表示する。
デバッグ 詳細。開発者用に変数値などを表示する。

 bashではreturn値は0255の整数値しか取得できない。そこで、標準出力された文字列を変数で受け取ることによって文字列の戻り値を得るようにする。しかし、成果物とそれ以外が混在してしまう。そこで、出力先ファイルディスクリプタを分けることで取捨選択できるようにする。

所感

 細かいことを調べたり、煮詰めていくのに時間がかかってしまった。

 コードの不満点は、コマンド引数の解析を抽象化してDRYに書けなかった所。たとえば-Dフラグは引数順序に関係なく最初に実行するようにgetoptsとは別枠でコードを書かねばならなかった。サブコマンドについても同じく別枠で書くことになる。しかもexitするので処理の流れがわかりにくい。getoptsでサブコマンドのhelp,versionをするときはexitするなど他の引数の処理と異なるのが気になる。

 getoptsよりもっと包括的で楽に書けるライブラリはないのか? 色々考えると、そもそもシェルの仕様がクソすぎてコマンド解析が大変になってしまうという結論に至った。CLIの宿命と思って諦めるしかないのか。

 CLIについては、できるだけ引数が必要にならないように作るのが最善。でも、実際そんなコマンドって無いんだよなぁ。わかりやすくて短いコマンド名にも限りがあるからすぐ飽和してしまう。だから応用が効くよう引数を受け取って汎用的にしたいのに先述のザマだよ……。かといってコマンド名に日本語や漢字を使おうものなら表記ゆれで死ぬだろう。

対象環境

$ uname -a
Linux raspberrypi 4.19.75-v7l+ #1270 SMP Tue Sep 24 18:51:41 BST 2019 armv7l GNU/Linux