- GNU
awk は stdin とファイルを自動的に走査し、フィルタリング・置換・フィールド処理を1行コマンドで組み合わせて CLI のテキスト作業を処理する
/regexp/ と !/regexp/ は現在の入力行を検査する短縮構文であり、条件が真ならデフォルトで $0 が出力される
sub は最初の一致だけを、gsub はすべての一致を置換し、対象を省略すると 現在の入力行 $0 に置換を適用する
- 空白区切りのフィールド分割と
$N, NF, $NF により、awk は特定の列を取り出したり条件を付けて処理する フィールドベースの作業 に向いている
cond{action} の断片を続けて書き、BEGIN{}・END{} を加えることで、単純なフィルタを超えて文字列・数値・連想配列まで含む小さなプログラムを作れる
awk の実行方式
awk は grep、sed のようにテキストを絞り込めるうえ、より複雑な処理のためのプログラミング機能も提供する
- 入力は stdin またはファイルから受け取れ、基本的に行単位で走査しながら条件と動作を適用する
- 入力行全体には特殊変数
$0 でアクセスする
- 正確な用語は入力 record だが、この章では行単位の説明として扱う
フィルタリングとデフォルト出力
- 正規表現は
/.../ 形式で書き、文字列が正規表現に一致するかを確認するときは string ~ /regexp/ 構文を使う
- 逆条件は
string !~ /regexp/ で表す
- 検査する文字列を省略すると、対象は
$0 になる
- 条件だけがあり動作がなければ、条件が真のとき
$0 が自動的に出力される
awk '/regexp/' は awk '$0 ~ /regexp/{print $0}' の短縮形
awk '!/regexp/' は awk '$0 !~ /regexp/{print $0}' の短縮形
- 例では
at を含む行だけを出力したり、e を含まない行だけを出力したりして、基本的なフィルタリングの流れを示す
1 を使う慣用的な出力
- 条件式では 0 ではない数値 と 空でない文字列 は真と評価される
awk '1' はすべての入力行に対して常に真となる条件を与える慣用表現である
- 動作がないとき、真の条件は
$0 の出力につながるため、awk '1' は入力全体をそのまま出力する
- 結果は
awk '{print $0}' または単純な cat と同じである
置換: sub と gsub
awk は検索と置換のために sub と gsub 関数を提供する
sub(/:/, "-") は各入力行で最初の : だけを - に置き換える
gsub(/:/, "-") は各入力行のすべての : を - に置き換える
- 2つの関数は第1引数に一致させる正規表現、第2引数に置換文字列を取る
- 入力文字列を別途指定しなければ、デフォルトの対象は
$0 である
- 置換が成功すると、対象入力が変更される
- 置換ブロックの後ろにある
1 はブロック外の条件式として解釈され、変更された $0 を再び出力する
awk '{sub(/:/, "-")} 1' は awk '{sub(/:/, "-"); print $0}' と同じ結果
print だけを書いてもデフォルトの出力対象は $0 である
grep, sed, awk を選ぶ基準
フィールドベースの処理
awk は特に フィールドベースの処理 によく使われる
- デフォルトでは入力行を空白で分割し、各フィールドには
$N でアクセスする
$2 は2番目のフィールド
$NF は最後のフィールド
NF は現在の入力行にあるフィールド総数
table.txt の例は、列単位の処理を簡潔に示している
- 各行の2番目のフィールドを出力
- 最後のフィールドが負数の行だけを出力
- 1番目のフィールドでだけ
b を B に変更
- 例題ファイルは example_files ディレクトリにある
1行コマンドの構造
awk 'cond1{action1} cond2{action2} ... condN{actionN}'
文字列と数値
配列
awk の配列は 連想配列 であり、キーと値のペアとして動作する
- キーは数値にも文字列にもできるが、数値キーは内部的に文字列へ変換される
- 多次元配列も使用できる
- 例では
student["id"], student["name"] のような文字列キーで値を保存し、アクセスしている
- キーの存在有無は
"id" in student という形で確認する
- 詳細は gawk manual: Arrays を参照
練習と次のステップ
1件のコメント
Hacker News の意見
awk が好きでかなりよく使っているが、主な用途の一つは 状態を持つ sed のように使うこと
たとえば、ある行が直前の特定の行の後に出てきたときだけマッチさせる、といった具合で、臨時のリンターを作るときに便利
最近は、非常に大きなテーブルで問題になり得る
CREATE INDEXがCONCURRENTLYなしで入っているマイグレーションファイルを探すチェックを作ったが、SQL 文は複数行にまたがることがあるので単純なマッチングは難しかったawk なら複数行にわたって「create 文の中にいる」「インデックスを作成している」といった 状態追跡 ができるので、ざっくり組んだスクリプトが1年ほど問題なく動いている
前の行が特定のパターンだったときだけ現在の行を出力するには、
sed -ne 'x' -e '/PREV/ {x; /CURR/ p; x}'のようにできる例:
echo -e "PREV\nCURR\nCURR\nCURR\nPREV\nRED" | sed -ne 'x' -e '/PREV/ {x; /CURR/ p; x}'はCURRだけを出力するこれは sed の hold buffer を使う方法で、
-nでデフォルト出力を抑えたうえで、pで必要な行だけ出力するxは現在の行と hold buffer を交換し、/PREV/ { ... }ブロック内で再び交換したあと、現在の行がCURRを含むときだけ出力する最後の
xは、マッチが重なる場合に備えて元に戻すためのものもちろん awk スクリプトのほうがずっと単純で直接的である可能性が高いが、sed でもこうできる
これを学ぶ時間は awk に使ったほうがよかったかもしれないが、https://www.grymoire.com/Unix/Sed.html のチュートリアルが非常に良く、sed について知っていることのほとんどをここで学んだ
このスレッドを読んでいる人なら、2週間前の “Ask HN: Share a shell script you like” にも興味がありそう
コメントは78件で、期待したほど盛り上がらなかったが参考になる: https://news.ycombinator.com/item?id=37112991
昨年の記事もある: https://news.ycombinator.com/item?id=32467957 (374ポイント、コメント294件)
Lisp などと同じく Awk にも少し関心を持ち続けていて、2022年に cppawk を作った: https://www.kylheku.com/cgit/cppawk/about/
cppawk は Awk に プリプロセス機能 を拡張してくれる
複数の節をサポートするループマクロがあり、節を組み合わせて並列反復やデカルト積の反復を作ることができ、ユーザー拡張も可能
簡単なマクロを5つ書けば新しい節を定義できる
Awk を使うなら役に立つかもしれず、複数の man page で文書化されていて、gawk と mawk で動く単体テストも備えている
古いシステム管理者の感覚が出ているだけかもしれないが、同じことを単に Perl で書くのに比べて awk の利点が何なのかよく分からない
ジュニアのシステム管理者が作ったひどいシェルスクリプトをたくさん見てきて、そのたびに「テキスト処理の部分は Perl ならずっときれいだったのに」と思っていた
私にとって awk は、数か月触っていなくても10分あれば感覚を取り戻せる数少ない言語の一つ
直感的な面があり、一般的なコマンドラインツールとも自然によく合う
ただしこの記事は一時的な用途の短い ワンライナー についてのもので、その場合は Perl より sed/awk のほうがよい
すでに Perl を知っているなら、あえて道具をさらに学ばず、そのまま Perl を使い続けてもよい
言語自体は「そこそこ」だが、十分使い物になる
Perl でも当然すべてできるが、awk が無料でやってくれるボイラープレートを自分で書く必要がある
Perl 言語の利点は、私にとって awk を捨てるほど大きくなく、awk を「スクリプティング」よりもデータ処理や一回限りのファイル加工に主に使っているので、Perl の深さが恋しくはならない
より深い機能が必要なら別のところへ行く
今の世代が Perl を学んでいないため、ストリーム処理という考え方を awk で新たに発見しているように見える
awk は1970年代後半の基準では優れたアイデアで、模倣する価値のある本物の革新だった
その後 Perl がそれを模倣し、さらに上回ったが忘れられたため、再び awk が発見されている様子は少し気恥ずかしい
gawk であまり知られていない点の一つは、たいてい有用な 拡張機能 が一緒に提供されていること
readdir()、ord()、chr()、gettimeofday()、sleep()のような機能にアクセスできるhttps://www.gnu.org/software/gawk/manual/html_node/Extension-Samples.html
awk のワンライナーは本当に強力
難しい問いは、より複雑な awk プログラミングに投資する価値があるのかという点
処理作業が複雑なロジックを要求する場合、awk はそれを提供してくれるが、初期のコンピューティング時代のような短く難解な形で提供する
とはいえ現代的な代替を選ぶとそれはそれで負担があり、特に pandas のように直感的ではない場合があり、性能問題まで生じることもある
個人用の共通関数ファイルでさえ十分にサポートされている感じがせず、サードパーティライブラリを取り込む方法も適切なものがなさそう
ヘルパー関数が必要になり、複数行に分け始めると、たいてい Python に移る
するとプログラムが2〜3倍に膨らんで、ため息が出る
Ruby は awk の代替として優れているが、職場の同僚が Ruby を知らなければ保守を期待しにくい
最近はそうした読みにくいコマンドを AI に貼り付けると、段階的にかなりうまく説明してくれるので、悪くなくなった
それでも単体テスト可能性のために Python 数行のほうを好むが、素早いデータ加工作業ではそもそも単体テストが不要な場合もある
新版の “CLI text processing with GNU awk” 電子書籍を公開
GNU awkコマンドを初級から上級まで、数百の例と練習問題で段階的に学べる。フィールド処理、フィルタリング、複数ファイル処理、複数レコードに依存する解法、ファイル間のレコード・フィールド比較、入力順を保った重複検出、正規表現などを深く扱うPDF/EPUB: https://learnbyexample.gumroad.com/l/gnu_awk(2023年8月31日まで無料)
Web版: https://learnbyexample.github.io/learn_gnuawk/
Markdown ソースと例ファイル: https://github.com/learnbyexample/learn_gnuawk
練習用の対話型 TUI アプリ: https://github.com/learnbyexample/TUI-apps/blob/main/AwkExercises
バンドルでは grep、sed、awk、perl、ruby のワンライナー集 Magical one-liners が $5: https://learnbyexample.gumroad.com/l/oneliners/new_awk_release、全13冊の電子書籍バンドルが $12: https://learnbyexample.gumroad.com/l/all-books/new_awk_release
フィードバックは、誤字、コードのミス、うまく合っていた部分やそうでなかった部分など、何でも大きな助けになる
以前の議論としては https://news.ycombinator.com/item?id=15549318 と https://news.ycombinator.com/item?id=22758217 がある
$5 や $15 を払うだけの有用性はあるかもしれないが、個人用のワンライナーファイルにすでに保存してある内容と変わらないかもしれず、確認するためにお金を払うのはためらわれる
Web版は8月31日以降も引き続き無料のままなのか気になる
以前 AWK でハートを描く コードゴルフをしたことがある: https://gist.github.com/auselen/906a53b47a7d616b080dbef85eb8f776
awk 利用の 99.9% は、連続する空白を捨てながら行を分割する用途
例:
echo "key: value" | awk '{print $1}'もっと単純な代替があるとよい
Nim 製だが大きな障壁ではないかもしれず、
bu/内の他のツールにも関心を持てるかもしれないcutでも可能:echo "key: value" | cut -wf 2ただし実際により単純かどうかは議論の余地がある
確認したところ GNU cut には
-wがないので BSD 専用結局 awk を使うほうがよさそう
どちらも cut/awk の代替で、正規表現ベースの区切りにも対応しているが、先頭/末尾の空白除去は記憶ではできない
自作の https://github.com/learnbyexample/regexp-cut は
awkを使って正規表現ベースの分割、負のインデックスなどを提供するcut風ツールで、awk のデフォルト動作のおかげで先頭/末尾の空白も処理するecho "key: value" | awk '{print $2}'と言いたかったのだと思う以前、bash から移植した diff2html スクリプトを awk で書いたことがあるが、当然の理由でずっと速かった
bash スクリプトより awk のほうがはるかに読みやすく、一晩で言語を学び、デバッグし、バグを理解して修正できた
慣用的な awk の書き方なのかは分からないが、本当に良い言語だと感じた
https://github.com/berry-thawson/diff2html/blob/master/diff2html.sh