LoginSignup
7
4

問 awk をワンライナーで書く時のアクション区切りの「;」の数は0個? 1個? 2個? + こぼれ話

Last updated at Posted at 2023-09-26

問題

awk で複数のアクションをワンライナーで書いた時、区切りの「;」の数は、0個、1個、2個のうちどれが最も適切でしょうか? 以下の中から適切な回答(複数可)を選び、その理由も答えてください。

# A. セミコロンは不要である
echo foo | awk '{print} {print}'

# B. セミコロンは一つ必要である
echo foo | awk '{print} ; {print}'

# C. セミコロンは二つ必要である
echo foo | awk '{print} ;; {print}'

この記事は上記の問題の回答、およびその回答にまつわる Brian Kernighan と One True Awk、GNU awk との互換性、POSIX awk の標準規格、に関するこぼれ話を紹介する記事です。
.
.
.

ブコメにちゃんと文章読んでるんだろうか?と疑問になるレスがあるけど、この問いに対してオリジナルのawk開発者とPOSIXとgawk開発者は全員同じ答えを出してますよ。全員同じ答えなのに、この問いに答えがないとはどういう意味なのでしょうか。そもそもPOSIXが間違ってることなんてよくある話でPOSIXが「正」とは限らない。この記事はPOSIXの解釈が間違っていたからオリジナルのawk開発者とgawk開発者の考えに従って修正したという話なんですが。それに開発者は根拠をしっかり述べてるのにそれは作者の気持ちだろっていうのは失礼な話。

.
.
.

答え

答えは「B. セミコロンは一つ必要である」のみです。セミコロンを省略している場合は見たことがあると思いますが二つ使用している例は見たことがないと思います。しかし何故か POSIX では 2 個(以上)に対応した awk の実装が認められています。さてそれでは本題、A と C が良くない理由はなんでしょうか?

その理由はオリジナルの awk 開発者の一人、Brian Kernighan (awk の「K」の人)が

  • A. セミコロンは不要である・・・が良くない理由
    • 「怖いなぁ。古い実装との互換性のために仕方なくセミコロン省略を受け付けるけど、この動作に頼るんじゃねーぞ」と 1988 年に書いており「今どうするか決断を迫られたら 1 個のセミコロンを必須とするだろう」と言ってるから
  • C. セミコロンは二つ必要である・・・が良くない理由
    • 「実装の関係で One True Awk で動いちゃうけど意図的な仕様じゃないよ。私も Alfred も Peter も ;; を利用するコードを書いたことなんて無いし誰もそんなコード書かないでしょ?セミコロンの数は 1 個がちょうど良いよ」と言っているから

です。ちなみに Alfred Aho と Peter Weinberger はそれぞれ awk の 「A」と「W」の人です。

さて、衝撃的(?)な事実です。よく見かけるアクションの間のセミコロンの省略は非推奨だったのです。実は昔からPOSIX ではセミコロンの数は1個となっておりセミコロンの省略は認められていません。もっともすべての awk の実装で省略しても動くと思いますが。

❌ アクション間のセミコロンの省略は非推奨
echo foo | awk '{print} {print}'
#                      ↑ ここにはセミコロンを入れよう

セミコロン2個のパターンは、One True Awk 以外では(おそらく)動きません。具体的に言うと gawk、mawk、Busybox awk、goawk では動きません。しかし macOS や BSD 系 OS、Solaris 11 ではセミコロン2個が動きます。大雑把に言うと Linux ユーザーが使っている awk は動かないが、Unix ユーザーが使っている awk では動くということです。

以前の POSIX では awk の実装はセミコロン2個以上に対応しなければならないと「なっていた」ので、gawk などは POSIX に準拠してないと言うことになっていたのですが、現在(2017年以降)は POSIX が修正され2個以上の対応は要求されなくなりました。現在の POSIX の規定は「アクション間のセミコロンの数は1個で、2個以上に対応する必要はないが拡張機能として対応するのは構わない」という扱いになっています。

こぼれ話

この話は 2014年4月18日の 本家の One True Awk の開発者である Brian Kernighan のメールおよび、過去の修正履歴の FIXES ファイルからです。

上記のメールは、POSIX から GNU awk に対しての質問に対して(CC で指定されていた)Brian Kernighan が答えたものです。質問の内容は「(以前の) POSIX awk の文法の定義では以下のように解釈できるが実際の awk の実装の動きと異なっている。POSIX awk の文法の定義に異議が申し立てられているが、あるべき仕様がはっきりしない。意見を聞かせてくれ」という内容です。なにげにここ重要なポイントですよ。POSIX が awk の仕様を定めてるのではなく、POSIX が実際の awk の仕様に合わせて変更しようとしているのです。

以前の POSIX では awk の文法の定義では以下のように解釈できました。

  1. awk '{print} ;; {print}' は許可されている
    • awkの実装はこの文法に対応しなければならないという意味
  2. awk '{print} {print}' が許可されない
  3. awk '/foo/; {print}' が許可されない
    • 補足 awk '/foo/{print}; {print}'と同じ意味

この定義を見て多くの人は多分このように考えると思います。「1. 書いたこと無いけどそんな書き方できるんだ」「2. よく見かける書き方だね」「3. よく見かける書き方だね」

POSIX awk の文法の定義は、オリジナルの awk である One True Awk(別名 nawk)の動作を元に標準化されているのですが、上記の解釈は(少なくとも現在の) One True Awk とも異なっています。一言でいうと「POSIX awk の文法の定義おかしくね?」ということです。だから POSIX に疑問が提起されたわけですね。その議論の内容が以下のスレッドです。

この話が GNU awk のメーリングリストに投稿されたのは、GNU awk では「;;」を受け付けておらず、一部の人はこの動作を「GNU awk のバグではないか?」と考えていたけどはっきりしなかったからです。

一連の議論を踏まえて最終的に POSIX.1-2008 (Issue 7) の Technical Corrigendum 2(技術的正誤表) で 2017 年にawk の文法の解釈が以下のように修正されています。

  1. awk '{print} ;; {print}' は許可されている
    • awkの実装はこの文法に対応しなければならない
    • → awk の実装はこの文法に対応する必要はない
    • → 良くない書き方だが、awk の実装は拡張機能として対応しても良い
  2. awk '{print} {print}' が許可されない
    • → 今まで通り awk の実装はこの文法に対応する必要はない
  3. awk '/foo/; {print}' が許可されない
    • → POSIX awk の文法の定義のバグで訂正された

1.に関して拡張機能として対応しても良いとなっている理由は、これまでの One True Awk の実装が ;; に対応しており、対応していたとしても POSIX 準拠違反になるわけではないことを明確にするためでしょう。もし ;; を禁止してしまったら誰かが ;; を使っていた時に互換性が保てなくなってしまうため、One True Awk は下手に仕様を変更するわけには行きません。POSIX は One True Awk の実装を尊重しているわけです。なおこの話は POSIX awk の RATIONALE に明記されています。

Earlier versions of this standard required implementations to support multiple adjacent <semicolon>s, lines with one or more <semicolon> before a rule (pattern-action pairs), and lines with only <semicolon>(s). These are not required by this standard and are considered poor programming practice, but can be accepted by an implementation of awk as an extension.

複数のセミコロンについて

One True Awk で ;; が使えた件について、Brian Kernighan は「実装上のもの(it's entirely an artifact of implementation)で私達はそのような柔軟性を利用する Awk プログラムを書いたことがないし、普通の Awk プログラマーも書いていないだろう、1個のセミコロンが適切である」と言っています。

Should multiple semicolons should be legal between pattern-action statements? They are legal in my current version of Awk, but it's entirely an artifact of implementation; I'm pretty sure that Al and Peter and I would never have written an Awk program to use that flexibility. And it seems unlikely that typical Awk programmers would write code that way either; one semicolon seems like just the right number.

セミコロンの省略について

Brian Kernighan のメールではアクションの間の 「;」 を省略した場合についても言及されています。アクションの間の ; を省略する書き方(awk '{print} {print}')はよく見かけますが、これは 1977年版の一番最初の awk との互換性を維持するための文法です。1977年版の awk は One True Awk よりも前の awk で、ユーザー定義関数などに対応しておらず POSIX awk に準拠していない古すぎるものです。実は初期の One True Awk では「; を省略する古い文法」は許可されておらず、1988年11月に互換性のために One True Awk の文法を変更したことが書かれています。

https://github.com/onetrueawk/awk/blob/master/FIXES.1e
(上記 FIXES.1e は元は FIXESというファイル名だったが最近変更された)

Nov 27, 1988:
	With fear and trembling, modified the grammar to permit
	multiple pattern-action statements on one line without
	an explicit separator.  By definition, this capitulation
	to the ghost of ancient implementations remains undefined
	and thus subject to change without notice or apology.
	DO NOT COUNT ON IT.

恐怖に震えながら、一行に複数のパターン-アクション文の記述を、明示的な区切り無しで許可するように文法を修正した。定義上は、この古代の亡霊の実装への屈服は、未定義のままであり、したがって予告や謝罪に無しに変更される可能性がある。その動作を当てにしてはいけない。

外国人(と一括りにしてはいけませんが)が書く文章って、このようなものをときどき見かけますよね。文章としては面白いんですが、行間読まないといけないのでつらいです。ともかく(恐怖に震えながらも)古い実装との互換性を維持するためにセミコロンなしを受け付けるように変更したということです。古い実装とはおそらく 1977年版の歴史的な awk のことでしょう。ちなみに「The AWK Programming Language(第一版)」の出版は 1988年1月、つまり11月に「; を省略する古い文法」に対応する前に出版されており、日本語版をざっと見ましたが、アクションの区切りは ; または改行と書かれており、一行で書いた時に ; を省略できるという記述は見つかりませんでした。もちろん ;; という書き方も使われていません。したがってアクション間のセミコロンを省略する書き方 awk '{print} {print}' は、ほとんどの awk の実装で動作しますが仕様としては未定義であり、POSIX では awk 開発者の感情を理由に標準化されないままとなりました。30 年の時を超えて「感情」が標準化の方針の決定に使われるなんて思いもよらなかったでしょう。私には修正履歴に感情を書くという発想すらなかったです。

補足ですが、アクション間のセミコロンはほとんどの awk で省略できると書きましたが、以下のケースでは One True Awk 以外は省略できません。

# One True Awk(BSD 系 OS や macOS)の場合は動作する
# 「1」は「1{print}」の省略形echo foo | awk '1 END{print "end"}'⏎
foo
end

# GNU awk の場合のエラー
gawk: cmd. line:1: 1 END{print "end"}
gawk: cmd. line:1:   ^ syntax error

# mawk の場合のエラー
mawk: line 1: syntax error at or near END

# Busybox awk の場合のエラー
awk: cmd. line:1: Unexpected token

# goawk の場合のエラー
<cmdline>:1:3: expected , instead of END
1 END{print "end"}
  ^

# どの awk の実装でも動作する書き方(間のセミコロンを省略してはいけない)echo foo | awk '1; END{print "end"}'

勘違いしてはいけないのは、これはアクション({print})の記述を省略したからエラーになっているのではないということです。エラーになる理由はアクションを省略したからではなくアクションの間(この例ではアクションは省略されていますが)のセミコロンが省略されているからです。そのため 1 から 1; に変更すればどの awk の実装でも動作します。おそらく One True Awk 以外の awk の実装は「The AWK Programming Language(第一版)」を参考に開発されており、このレアケースな未定義の動作に対応漏れがあったということなのでしょう。本の記述通りにアクションを区切るセミコロンは必要なのです。GNU awk のメンテナの Arnold はこのように言っています。

The gawk documentation follows this example, documenting clearly that a semicolon is required between multiple rules on one line, and NOT documenting that it can be left off. I do not plan to change this, either.

gawk のドキュメントはこの例にならって、一行の複数のルールの間には一つのセミコロンが必須であると明確に文書化しており、省略できるとは文書化していない。私もこれを変更するつもりはない。

まとめ

ということでまとめです。

  • POSIX では元々 ; の数は1個以上に対応すべきだったのが1個のみで良いとなった
  • GNU awk などは ;; が動かず POSIX 準拠ではなかったが、POSIX 改定で現在は問題ない
  • Brian Kernighan はアクション区切りの ; は省略しないほうが良いと言っている
  • POSIX や GNU awk ではアクション区切りの ; は必須ということになっている
  • 1988年に Brian Kernighan が古い実装との互換性のために ; の省略を許すように変更した
  • One True Awk で ;; が動いたのは意図して作った動作ではない
  • Brian Kernighan を含む三人の awk 開発者は ;; を使ったコードを書いたことがない
echo foo | awk '{print} {print}'
# この書き方は互換性維持のためのものであり非推奨

echo foo | awk '{print} ; {print}'
# 推奨

echo foo | awk '{print} ;; {print}'
# One True Awk でたまたま動いていた
# 以前の POSIX では awk の実装はこれをサポートする必要があったが
# 2017 年以降はサポートする必要がなくなり不適切なプログラミング習慣と明記された

アクションの区切りの ; は省略しないほうがいいとはいえ私も時々省いて書いちゃってる気がします。使い捨てのワンライナーは別にどうでもいいかなと思いますが、シェルスクリプトでは気をつけなきゃですね。あと今更ですが、awk の ; は C 言語のように文の末尾に書くものではなく区切り記号なのでわざわざ最後に書く必要はありません。「プログラミング言語AWK」でも末尾にセミコロンを書いている例は見つかりませんでした。複数の言語を使うと混乱しますが、言語ごとに適切なスタイルで書いて行きたいですね。

番外編 なーんでこんな記事書いたの?

番外編として、なぜ私がこの記事を書くに至ったかの話をします。その理由はAWKで一番の得意ワザ! シェルで文字列を自在に扱うための文字列関数(「シェル芸」に効くAWK処方箋 第4回(月刊『USP MAGAZINE 2014 July (Vol.15)』より転載)) の「4.6 AWK の区切り?」の内容がおかしな話だったからです。ちゃんと原文を調べて本当の話を知った今、突っ込みますが「Kernighan大先生はそんなこと言ってないよね?

こんな所で指摘してないで、ちゃんと報告しろと思うかもしれませんが、前に「なぜawkの乱数関数でUnix時間を取得できるのか? ~ 乱数の話とUnix時間の深い関係」の件で間違いを報告したのですが、返信なかったからね、しょうがないね。

以下は、2017 年に出版された「シェル芸」に効くAWK処方箋」(電子版)からの引用ですが、2014 年に公開された上記のリンクに先にもほぼ同じ文章が書いてあります(ウェブで公開されていることに気づかず、この記事を書いた後で見つけました)。書籍には出典元が書いていなかった(見つけられなかった)ので「Kernighan大先生からのメール」を探すのに苦労しました。

4.6 AWK の区切り?

最後に閑話休題ということで、比較的最近に話題になったAWK界隈のニュースをお知らせします。POSIXではAWKの区切りとして;;(2回のセミコロン)となっているそうです。
(中略)
ところがGNU AWKではこれに準拠していないということが最近話題になりました。
(中略)
GNU AWKのメンテナーのArnold Robbinsは、POSIXの記載自体が古い上、for 文のように区切りが全て;;になっているわけではないので(略)Brian Kernighanに聞いてみようじゃないかと提案します。
(中略)
Kernighan大先生からのメールを超意訳すると、

「いや~、すまんかった。実はAlfred(Alfred Aho。AWKの「A」の人)も、Peter(Peter Weinberger。AWKの「W」の人)も、そこまでAWKを書きこなしていたわけじゃなかったんじゃよ。1988年にPOSIXを修正しようかと思っておったんじゃが、そのままにしていたら今日また同じことになったんで、こりゃ決めんといかんな。Arnoldの言うとおりに1つのセミコロンに決定じゃ」

という感じの緩めのメールなのですが、あのKernighan大先生から全世界のAWKユーザーにメールされたというのはかなりインパクトがあったと思います。

したがって、以下の文法が適用されます。これはnawk、gawk問わずに動作します。

$ seq 1 10 | awk '/1/ ;; /2/'
1
2
10

最終的な結論「アクションの間のセミコロンは1個」という点は問題ないのですが、読んでいて私は文章のあちこちに違和感を感じました。いやそれおかしくない?と。私が感じた違和感について説明します。

全体を通してですが同書の内容には 「セミコロンの省略」の話が抜け落ちてセミコロン二個の話だけになっており、Kernighan のメールではセミコロン省略の話がメインなのに、この本(またはウェブ記事の内容)だけを読んでいるとその事に気づくことが出来ません。なんという罠。オレでなきゃ見逃しちゃうね。

最後に閑話休題ということで、比較的最近に話題になったAWK界隈のニュースをお知らせします。

余談ですが、閑話休題とは「むだ話を打ち切って、話を本題に戻すときに使う言葉」です。よくある誤用なので注意しましょう。

閑話休題、awk の話に戻ります。

POSIXではAWKの区切りとして;;(2回のセミコロン)となっているそうです。

誰もが区切り記号は ; だと思っているはずです。区切り記号に ;; を使っているのなんて見たことがありません。POSIX awk の仕様と実際の awk の仕様が矛盾しているとき、基本的に正しいのは実際の awk の仕様です。なぜなら POSIX は実際の実装を標準化する団体だからです。みんな ; しか使ってないのに「;; となっている」とは思えません。本編で解説した通り POSIX は以前から区切り記号は ; であり文法上は ;; にも対応しなければらなかったというのが正しい解釈です。そのような仕様だった理由は POSIX が参照していた One True Awk の実装がそうなっていたからです。POSIX はこの解釈は正しいものではなかったとして 2017 年に修正しました。POSIX は最初に Brian Kernighan に聞いていればこんなことにはならなかったのでしょうけどね。

GNU AWKのメンテナーのArnold Robbinsは、POSIXの記載自体が古い上、for文のように区切りが全て;;になっているわけではないので(略)Brian Kernighanに聞いてみようじゃないかと提案します。

そもそもPOSIXの記載はたいてい古いものです。基本的に 1990 年頃の Unix コマンドの動作を標準化したものだからです。当たり前の話なのでわざわざこのような指摘をするとは思えません。実際のメールを確認すると Arnold Robbinsは「POSIXの記載が古い」とか「for文のように区切りが全て;;になっているわけではない」などとは言っておらず、for文に関しては余談としてセミコロンの話とは全く関係ない文脈で登場しているだけでした。「聞いてみようじゃないかと提案」に関しては単に CC に入れていただけ(普段から Arnold は、Brian Kernighan と awk の互換性で話をしていたらしい)で「聞いてみようじゃないか」なんて言ったわけではないですが、まあ表現としてはうーん?

Alfred も Peterも、そこまでAWKを書きこなしていたわけじゃなかったんじゃよ

「AWK を書きこなす」の意味が、AWK言語を開発するという意味なのかAWKスクリプトを書くという意味なのか不明瞭ですが、いずれにしろ awk 開発の仲間に対して「(あの二人は)そこまで AWK を書きこなしていたわけじゃなかったんじゃ」なんて言うのはおかしな話ですよね。上から目線すぎでしょと。Alfred と Peter が登場する文章では、Alfred も Peter に加えて私も (Al and Peter and I) 2つのセミコロンなんて書かなかっただろうと確信 (I’m pretty sure) していると書いてあります。正しくは「わしらはアクション区切りに ;; なんて書いとらんぞ!」です。で、書きこなしていなかったから間違って実装した???

1988年にPOSIXを修正しようかと思っておったんじゃが

POSIXが標準化されたのは POSIX.1-1988 の規格名の通り 1988 年です。ただしこの時は C 言語インターフェースのみが標準化され、awk を含むコマンドが標準化されるのは 1992 年の POSIX.2-1992 です。1988 年に修正すべき POSIX の標準規格は存在しいのでおかしな話です。もちろん策定される数年前からドラフトは公開されていたわけで、それを修正するという意味と考えられなくはないですが、そもそも Brian Kernighan は POSIX 標準化委員会の人とは思えませんし、彼が独断で修正しようと決めるようなものではありません。POSIX を修正するかどうかは One True Awk とその他の実装を考慮した上で POSIX 標準化委員会が決めることです。Brian Kernighan が決められるのは彼が開発した One True Awk の実装だけです。メールには POSIX を修正しようかと思っていたという話はでてきていません。1988 年という年号が登場するのは「この年に互換性のためにセミコロンの省略を許すように文法を修正した」という話です。

そのままにしていたら今日また同じことになったんで、こりゃ決めんといかんな。Arnoldの言うとおりに1つのセミコロンに決定じゃ

「今日また同じことになった」の意味がよくわかりませんね。今日ということは過去に同じことがあったということ? 原文を読んで気づいたのですが、文脈からしてこれは「セミコロンの数は一つなのかい?二つなのかい?どっちなんだい? 一つにきーーーめる!」の話ではなく、セミコロンの省略を許すか許さないかの話です。複数のセミコロンの話は前の段落の話です。Arnold が言ったこと(やったこと)は GNU awk ではアクション間のセミコロンはドキュメント上は必要(省略できない)にしたことで、Brian Kernighan が同意しているのはそのことに対してです。

Should one semicolon be required between pattern-action statements? I agree strongly with Arnold on this one: yes.

パターン・アクション文の間に一つのセミコロンを必須とすべきか? これについては私はアーノルドに強く同意する。

という感じの緩めのメールなのですが、あのKernighan大先生から全世界のAWKユーザーにメールされたというのはかなりインパクトがあったと思います。

緩いのは超意訳後の文章であって元のメールは緩めではなく普通のメールでしょう? 「いや〜、すまんかった。」なんて言ってないですし。メール先は GNU awk のメーリングリストなので全世界のAWKユーザーは言いすぎでしょう。まあ言葉の綾だと思いますが。

次の文章は Brian Kernighan の締めの言葉です。Brian Kernighan が POSIX の awk の標準化に直接関わっているわけではないことが読み取れますね。

Hope this helps your deliberations a bit.
Thanks for all your good work on the standarization effort.

これがあなた達の審議に少しでも役に立つことを願っている。みなさんの標準化の努力に感謝します。

したがって、以下の文法が適用されます。これはnawk、gawk問わずに動作します。

$ seq 1 10 | awk '/1/ ;; /2/'

これはおそらく誤字で ;; ではなく ; が正しいはずです。gawk では動きませんしそうでないと文章との辻褄が合いません。

ね?違和感だらけでしょ? で、本には出典元が見つからなかったのでモヤモヤして原文を調べたという流れです。正直この話は大した話ではないのですが、わかります? 読みながら私が感じていた違和感、「1988年にPOSIXを修正」というあり得ない矛盾、そんなわけないだろと思いながら読んでいた私の気持ち。書籍として出版されてしまっていますし、誰かが見つけてトリビア的な話として広まったら良くないですね。私のように本を読んで混乱している人もいるでしょう。誰かが指摘しなければならないことなので、まあ、多少はね?

今回の話とは関係ありませんが、同ページにある以下の文章も気になりました。(太字は原文ママ)

 シェル芸勉強会で生まれた本来の目的ではない最大の成果の一つに"grep -o"というものがあります。grepコマンドの引数として"-o"を付けると正規表現にマッチした部分だけを抜き出す、つまり先ほどのAWKスクリプトと同じことがgrepコマンドでできてしまうのです。

太字の「本来の目的ではない」という話ですが、grep -o の本来の目的は「正規表現にマッチした部分だけを抜き出す」機能ですよね。そのことは以下のように 2001 年の時点で書かれています。実装から 10 年後ぐらいにシェル芸勉強会で grep コマンドの -o オプションが話題になった程度のことではないのでしょうか。-o オプションの知名度の向上にはつながったとは思いますが。

Version 2.5

- The new option --only-matching (-o) will print only the part of matching
    lines that matches the pattern. This is useful, for example, to extract
    IP addresses from log files.

これは、例えばログファイルの中から IP アドレスを抽出するのに便利である。

番外編のまとめ

番外編のまとめです。Brian Kernighan大先生はこう言いました。

「アクションの区切りの一つの ; は必須に決定じゃ!省略してはならんぞ!」

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4