argius note

プログラミング関連

シェルスクリプトとPandocでMarkdownをまとめてHTMLに変換してindexファイルも作る

シェルスクリプトとPandocを使って、MarkdownをまとめてHTMLに変換します。
それだけでは芸がありませんので、「シェルスクリプトだけ(sed,awkは使う)」「indexファイルも作る」のところでちょっとだけ工夫をしています。


環境

作成例

今回を含めたこのブログ自体の記事を使って作成しました。

f:id:argius:20170427230201p:plain

コード例と解説

#!/bin/sh

# エラー終了の関数
errexit() {
  printf "\033[31m[ERROR]\033[0m" ; echo " $1"
  echo "使い方: $0 入力dir 出力dir"
  exit 1
}

generator=`basename $0`
echo "\"$generator\" - MarkdownをまとめてHTMLに変換してindexファイルも作る"
echo ""

# Pandocの有無をチェック
test "`which pandoc 2>/dev/null`" || errexit "Pandocがありません"

# 引数チェック
test "$1" = "" && errexit "入力dirが指定されていません"
test "$2" = "" && errexit "出力dirが指定されていません"

srcdir=$1
outdir=$2

# dirの有無をチェック
test -d "$srcdir" || errexit "入力dirが存在しません"
test -d "$outdir" || errexit "出力dirが存在しません"

indexfile=$outdir/index.html
stylefilename=style.css

echo "HTMLファイルの生成を開始."
# index.htmlの前半
cat << EOT > $indexfile
<!DOCTYPE html>
<html><head><meta charset="utf-8" />
<meta generator="$generator" /><title>Documents</title>
<link rel="stylesheet" href="$stylefilename">
</head><body><h1>Documents</h1>
<p>"$generator"によって`date "+%Y-%m-%d %H:%M:%S"`に生成されました。</p>
<ul>
EOT
# ループ開始 
for f in `ls -t $srcdir/*.md` # mdファイルを新しい順に取得
do
  name=`basename $f`
  htmlname=`echo $name | sed s/.md$/.html/`
  /bin/echo -n "  変換: $name -> $htmlname ..."
  title=`head -n 1 $f | sed 's/#//' | sed 's/^<space>+//'` # タイトルは先頭行を使用
  test "$title" = "" && title=$name # タイトルが無い場合はmdファイル名を設定
  mtime=`date -r $f "+%Y-%m-%d %H:%M:%S"` # mdファイルの最終更新日時
  tempfile=`mktemp temp-XXXXXX`
  ( echo "% $title" ; cat $f | awk "2<=NR" ) > $tempfile # 先頭行を差し替え
  pandoc -s -f markdown -t html5 -c $stylefilename -o $outdir/$htmlname $tempfile
  rm $tempfile
  echo ".. 完了"
  echo "<li><a href="./$htmlname">$title</a><br />(最終更新日時:${mtime})</li>" >> $indexfile
done
# index.htmlの後半
cat << EOT >> $indexfile
</ul></body></html>
EOT
echo "完了しました."


前半のほとんどが準備処理です。
後半のforループの箇所に注目してください。

タイトルを取り出す

先頭行は、# titleの形式を想定していますが、このままPandocで変換するとページのタイトルを付けてくれないので、その対処のために、まずはタイトルを取り出しておきます。

headで先頭行を取得して、#とスペースを取り除きます。

先頭行が空の場合は、Markdownファイル名を設定します。

ファイルの最終更新日時

いつの時点のMarkdownファイルなのかを分かるようにするために、indexファイルにファイルの最終更新日時を書いておきます。

環境によっては使えませんが、date -r <file>でファイルの最終更新日時(mtime)を取得することができます。
(実はこれ、今回初めて知りました…)

先頭行を差し替え

Pandocの拡張書式でタイトルを処理できるように、先頭行を改竄差し替えます。差し替えた結果は、mktempで一時ファイルを作ってそこに出力します。Pandocにはその一時ファイルを渡します。

具体的な方法は、まず、取り出しておいたタイトルを% $titleの形式で1行目に書き出し、続けて元のファイルの2行目から出力します。

「2行目から出力」というところは、AWK先生にお願いしたら、2<=NRでできるそうです。
AWKは、テキストファイルをレコードとして操作するときにとても便利ですね。今までスクリプト言語で何とかしてしまうことが多かったから、全然使いこなせていません。

indexファイル

indexファイル(index.html)には、集めた情報をリスト形式で出力します。
ここでは、HTMLファイルパス(aタグに使用)、タイトル、最終更新日時を出力しています。

また、generator情報をmetaタグとコンテンツの両方に出力しています。

スタイルシート

スタイルシートは、./style.cssにしています。好きなスタイルシートを置きましょう。
私は、下記をお借りしてきました。ちょっとだけカスタマイズして使っています。

https://gist.github.com/dashed/6714393/
GitHub-like CSS for pandoc standalone HTML files (perfect for HTML5 output).

その他

上記以外には特に説明は必要ないと思いますが、ちょっとだけ補足。

ファイルの順番は、ls -tを使って最終更新日時の新しい方から取得するようにしています。

今回の処理には複雑な分岐がありませんので、if文を使わずに短絡評価(&&,||)だけで処理しています。

実行結果

変換するMarkdownファイルは、今回の記事自体です。
結果イメージは冒頭にある通りです。

  • 入力の一部(今回の記事)
# シェルスクリプトとPandocでMarkdownをまとめてHTMLに変換してindexファイルも作る

カテゴリー: Unix-like

シェルスクリプトとPandocを使ってMarkdownをまとめてHTMLに変換します。  
それだけでは芸がありませんので、「シェルスクリプトだけ(`sed`,`awk`は使う)」「indexファイルも作る」のところでちょっとだけ工夫をしています。

---

## 環境


$ markdown-to-html-with-gen-index ./markdowns .
"markdown-to-html-with-gen-index" - MarkdownをまとめてHTMLに変換してindexファイルも作る

HTMLファイルの生成を開始.
  変換: 20170427-htmlgen-from-md-with-sh-and-pandoc.md -> 20170427-htmlgen-from-md-with-sh-and-pandoc.html ..... 完了
  変換: 20170410-java-game-maze.md -> 20170410-java-game-maze.html ..... 完了
  変換: 20170405-python-2017-winter.md -> 20170405-python-2017-winter.html ..... 完了
  変換: 20170406-python-1st-year.md -> 20170406-python-1st-year.html ..... 完了
  変換: 20170403-iPhone-barcode-reader.md -> 20170403-iPhone-barcode-reader.html ..... 完了
  変換: 20170328-java-firefox-history-dump.md -> 20170328-java-firefox-history-dump.html ..... 完了
完了しました.
$

あとがき

最近は再びシェルスクリプトをいじることが多くて、遊んでいたらこんなの出来たので公開してみました。
シェルスクリプトは、堅いプログラミングには向きませんが、小規模のツールを作るには覚えておくと便利ですね。
あと、今回はPandocがあってこその成果でもあります。

(おわり)