awkによるFronto Matter置換処理のメモ

ワードプレスに投稿した記事を「WordPress to Hugo Exporter」というプラグインを使ってファイルに落とし、Hugoによる静的サイトへの移行を試している。ただ、この落としてきた記事ファイルは初期状態のままでは使うことができず、置換処理が必要。テキストファイルの置換ですぐに思いついたのが、sedやawk、シェルだ。記事ファイルのFront Matter(記事の設定領域)に冗長な記述が残っているので、今回はawkでこの冗長箇所を削除してみた。

最初に断っておくが、awkを試しているうちにHugo移行用の置換処理としては役不足と気づいたので、本番の置換処理ではperlかpythonを使おうかと思っている。今回はその役不足を認識するまでの作業、つまりawkで期待通りの置換ができると思っていたところまでの作業を紹介する。 (役不足と言ってしまったが、今回落としてきた記事ファイルの内容が一部特殊な構造になっているから使えないというだけであり、通常使用では十分使える機能だ。)

WordPress to Hugo Exporterで落としてきた記事ファイルの上部(Front Matter)は、次のようになっている(表示はファイルの途中まで)。

 1---
 2url: /hugo-github-mac/
 3keni_layout_post:
 4  - layout-basic
 5pvc_views:
 6  - 5
 7keni_post_fb_time:
 8  - 1613041097
 9categories:
10  - Hugo
11  
12---
1313  とりあえずHugo+GitHubにより作成した記事がネット閲覧可能となるまでの最短と思われる手順を示す。
14(以下、省略)

3〜8行目はワードプレスで賢威というテーマを使っていたために載っており、Hugoで静的サイトを作る際には冗長で不必要だ。また11行目の空行も不要。これらの部分をawkで削除することを考える。

あとで示すawkスクリプトをwp2hugo.awkというファイル名で作成。awkコマンドでこのawkスクリプトと置換対象ファイル(2021-02-11-hugo-github-mac.md)を指定して実行した結果が下図。

期待どおりに冗長箇所が消えていることを確認。

 1BEGIN{in_fm = 0; in_keni = 0; in_pvc = 0}
 2/^---/{print; in_fm = !in_fm; next}
 3!in_fm{print; next}
 4/^$/{next}
 5/^keni/{in_keni = !in_keni}
 6/^pvc/{in_pvc = !in_pvc}
 7{output = 1}
 8in_keni || in_pvc{output = 0}
 9output{print}
10in_keni && /^ /{in_keni = !in_keni}
11in_pvc && /^ /{in_pvc = !in_pvc}

さて、スクリプトファイル(wp2hugo.awk)の簡単な説明をしよう。上がそのファイルの内容である。

1行目のBEGINは初期処理で1回のみ実施される。in_fm, in_keni, in_pvc という変数の初期設定を行なっている。 2行目以下は、置換対象ファイルの各行について繰り返し処理される。つまり、置換対象ファイルの1行目について上記スクリプトの2〜11行目を実行、置換対象ファイルの2行目について上記スクリプトの2〜11行目を実行、・・・、置換対象ファイルの最終行について上記スクリプトの2〜11行目を実行、という感じで処理を繰り返す。現在処理中の行が変わっても変数は自動で初期化される、なんてことはないので勘違いしないように。

2行目の/^—/は、”—から始まる単語”にマッチする場合という条件を意味している。マッチすれば{}の中を処理する。printは現在処理中の行の内容を出力する処理、セミコロンは次の処理との区切り文字、!(否定演算子)を使ってin_fmのon/off切り替え、右端のnextは現在行について以下の処理(3〜11行目)をスルーさせるコマンドだ。

3行目は、!in_fmがTrueだったら{}内を処理をするということ。4行目は、現在行が空行の場合は以下の処理をスルーするという意味だ。5行目はkeniから始まる単語が見つかった場合の処理、6行目はpvcから始まる単語が見つかった場合の処理。少し飛ばして9行目でoutputがTrueだったら行の内容を出力している。10, 11行目にみられる/^ /は、単語の先頭が空白の行であれば・・・という条件になる。

このスクリプトには、output, in_fm, in_keni, in_pvc という変数があり、これら変数の値が行ごとに変わることで、printにより行の内容が出力される。ただしprintは、スクリプトの9行目だけでなく2, 3行目にもあることに注意。

変数が行ごとに変わる様子(遷移)を下図に示してみた。「後in_keni」「後in_pvc」とは、スクリプトの9行目のprint後という意味。紛らわしいかもしれないが、10, 11行目の処理は必要なので、この行の処理による変化を示すために載せた。

さて、awkスクリプトと同様の処理をpythonで記述してみたのだが(以下コード参照)、かなりコード量が増えた。ただ、柔軟性は上がるし、awkにある正規表現の限界もない。一長一短あるのだから、それを理解した上でケースバイケースで使い分ければ良いのかと思っている。

 1import fileinput, re
 2
 3in_fm = False
 4in_keni = False
 5in_pvc = False
 6
 7fm_pat = re.compile('^---')
 8bl_pat = re.compile('^$')
 9keni_pat = re.compile('^keni')
10pvc_pat = re.compile('^pvc')
11ws_pat = re.compile('^ ')
12
13for line in fileinput.input():
14    line = line.rstrip()
15
16    if fm_pat.match(line):
17        print(line)
18        in_fm = not in_fm
19        continue
20
21    if not in_fm:
22        print(line)
23        continue
24
25    if bl_pat.match(line):
26        continue
27
28    if fm_pat.match(line):
29        continue
30
31    if keni_pat.match(line):
32        in_keni = not in_keni
33
34
35    if pvc_pat.match(line):
36        in_pvc = not in_pvc
37
38    output = True
39    if in_keni or in_pvc:
40        output = False
41    if output:
42        print(line)
43
44    m = ws_pat.match(line)
45    if in_keni and m:
46        in_keni = not in_keni
47    if in_pvc and m:
48        in_pvc = not in_pvc

参考サイト

  1. awkコマンド(テキストの加工やパターン処理をする
  2. 【 awk 】コマンド(応用編)――テキストの加工とパターン処理、制御構文

関連ページ