シェルスクリプト・sed・awk ── テキスト処理の道具立て
はじめに
このコラムではここまで、Makefile や CI の随所でシェルのワンライナーを書いてきました。シェルスクリプトと sed・awk は、ビルドの糊としてだけでなく、原稿の一括置換や簡単な集計・チェックにも効きます。
3 つの道具はざっくり次の分担です。
- grep … 該当行を探す
- sed … 行単位で置換・削除する
- awk … 列を切り出す、条件で処理する、集計する
シェルスクリプトの基礎
先頭にシバンを書き、エラーで止まる設定を入れておきます。
#!/usr/bin/env bash
set -euo pipefail # 未定義変数・コマンド失敗・パイプ失敗で停止
src="draft.tex"
count=$(grep -c '\\section' "$src") # コマンド結果を変数へ
echo "セクション数: $count"
for f in chapters/*.tex; do
echo "processing $f"
done
要点は次のとおりです。
- 変数展開は必ず
"$var"とダブルクォートで囲む(空白や特殊文字で壊れないように) - コマンド置換は
`...`ではなく$(...) - 直前のコマンドの成否は
$?、if コマンド; then ...でそのまま分岐できる - パイプ
|で連結、>で上書き、>>で追記、2>&1で標準エラーを標準出力へ
sed の実用
sed は「行ごとにルールを適用する」道具です。もっとも使うのは置換 s/検索/置換/。
sed 's/ / /g' in.html # 全ての を空白に
sed -E 's/([0-9]+)年([0-9]+)月/\1-\2/g' f # 拡張正規表現と後方参照
sed '/^%/d' main.tex # % で始まる行(コメント)を削除
sed -n '10,20p' main.tex # 10〜20 行目だけ表示
sed -e 's/“/``/g' -e 's/”/'"''"'/g' f # 複数のルールを続けて
-i(ファイル直接編集)は macOS と Linux で書き方が違います。 GNU sed はsed -i 's/.../.../' f、BSD/macOS の sed はsed -i '' 's/.../.../' f(空の拡張子が必須)。スクリプトを配る場合は、後述のとおりコンテナで GNU 版に揃えると悩まずに済みます。
原稿処理の例:
# 行頭の全角スペースによる字下げをやめる(段落先頭の を削除)
sed -E 's/^ +//' draft.tex
# ハイフン・マイナス・全角ダッシュを 1 種類に寄せる
sed 's/[‐―−]/-/g' draft.tex
awk の実用
awk は入力を「レコード(既定は行)× フィールド(既定は空白区切り)」として扱い、パターン { アクション } を並べます。
awk '{ print $1, $3 }' data.txt # 1 列目と 3 列目
awk -F'\t' '{ print $2 }' data.tsv # タブ区切りの 2 列目
awk 'NR % 2 == 0' file # 偶数行だけ
awk '/error/ { c++ } END { print c }' log # "error" を含む行数
# 章ごとの行数を数える
awk '/^\\chapter/ { name=$0; next } { n[name]++ } END { for (k in n) print n[k], k }' main.tex
# 数値列の合計と平均
awk '{ sum += $1 } END { print sum, sum/NR }' nums.txt
BEGIN{} は処理前、END{} は処理後に 1 回。NR は行番号、NF はその行のフィールド数、連想配列(n["key"])で集計できます。「1 行 1 レコードで、列を見て何かする/数える」なら awk が素直です。
組み合わせる
複数ファイルにまたがる処理は find や grep -rl と組みます。
# 全 .tex から「表記ゆれ」候補を洗い出す
grep -rn -E '(Web|web)' --include='*.tex' .
# 置換をファイル横断で(まず対象を確認してから実行)
grep -rl 'oldname' --include='*.tex' . | while IFS= read -r f; do
sed -i 's/oldname/newname/g' "$f"
done
# パイプで受け渡す:抽出 → 整形 → 集計
grep -o '\\cite{[^}]*}' main.tex | sed 's/.*{//; s/}//' | sort | uniq -c | sort -rn
安全に使うために
sed -iの前に差分を確認。まずは-iなしで実行して出力を眺め、Git で管理下に置いてから置換してgit diffで確認する。- 正規表現は最小限に当てる。
.*を安易に使うと意図より広く食う。 - マルチバイト。日本語がからむときは
LC_ALL/ ロケールの影響を受けることがある。想定と違う挙動ならLC_ALL=Cと比べてみる。 while readはIFS= read -r。これを省くと行頭末尾の空白が削れ、バックスラッシュが消える。
移植性の話
- シェル:
bash固有の書き方(配列、[[ ]])を使うならシバンはbashにする。POSIXshで足りるなら移植性が上がる。 - sed / awk / grep:macOS は BSD 版、Linux は GNU 版で、
sed -i・grep -P・awkの拡張などに差がある。 - 揃える手:スクリプトを Docker の Linux イメージ内で走らせれば、どの手元でも GNU 版で動く。CI と挙動を一致させたいときにも有効。
関連記事
- Git と GitHub の基本 — 一括置換を版管理下で差分確認しながら行う
- Docker の基本 — コンテナ内で実行して GNU 版に揃える
- docx2tex — Word からの変換後に残るクセを掃除する
- latexmk — ビルドの下ごしらえをスクリプトでまとめる
まとめ
- grep で探し、sed で行を置換し、awk で列を見て集計する ── まずこの分担を押さえる。
- シェルスクリプトは
set -euo pipefailと"$var"のクォートを徹底するだけで事故が激減する。 - 破壊的な一括置換は「確認してから」「Git 管理下で」。
sed -iの macOS/Linux 差に注意。 - 環境差が気になるならコンテナ内で実行して GNU 版に揃える。
原稿の表記ゆれ検出やビルドの下ごしらえは、この 3 つでかなりのところまで手が届きます。