はじめに
シェルまたはシェルスクリプトである出力のすべての行に行番号を付けたいと思った時、どのコマンドを使いますか? その候補は複数ありますが、その一つに nl
コマンドを使う方法があります。しかし nl
コマンドを使って行番号を付けるのは良くない考えです。なぜならおそらくあなたが期待したとおりに行番号が付けられない場合があるからです。この記事では nl
コマンドとは本来どのようなコマンドであるかを解説します。
なぜnlコマンドを使って行番号を付けてはいけないのか?
論より証拠というわけで、nl
コマンドを使うとどのように行番号をつくのかを示しましょう。その答えなら知ってるよ、デフォルトでは空行に行番号が付かないんでしょ? という人のために -ba
オプションを指定して空行にも行番号を付けるようにします。
まず次のようなファイル (file.txt) を用意し・・・
\:\:\:
ページ1ヘッダ
\:\:
foo
bar
baz
\:
ページ1フッタ
\:\:\:
ページ2ヘッダ
\:\:
FOO
BAR
BAZ
\:
ページ2フッタ
nl -ba
を実行すると次のような出力が得られますが、おそらくこれはあなたが期待した通りの結果ではありませんよね?
$ nl -ba file.txt
ページ1ヘッダ
1 foo
2 bar
3 baz
ページ1フッタ
ページ2ヘッダ
1 FOO
2
3 BAR
4
5 BAZ
ページ2フッタ
nlコマンドは論理ページごとに行番号を付けるコマンド
nl
コマンドは、本来すべての行に行番号をつけるコマンドではなく「論理ページ」というページの概念を前提として、ページごとに行番号を付けるコマンドです。デフォルトでは次のような区切り行を使って、ヘッダとボディとフッタを区別します。
区切り行 | 意味 |
---|---|
\:\:\: |
Header |
\:\: |
Body |
\: |
Footer |
もちろんこのような区切り行を使うことはめったにないので、だいたい上手く動くでしょう。わかっていてやっているのなら良いのですが、nl
コマンドを紹介している記事などで、こういう問題があることを注意喚起しているものを見かけません。ドキュメントをしっかり読めばこのような仕様は分かるはずなのですが、動かしてみた、動いた、これで良しと考える人が多いように感じます。
もちろん論理ページごとに行番号を付けたいのであれば、nl
コマンドを使用してください。それは nl
コマンドの本来の想定している使い方です。
区切り行は無効にできない
ここで区切り行で区切られるというのなら、-d
オプションに空文字を指定して区切り行を無効にすればいいじゃないか?と考えるかもしれません。
$ nl -ba -d '' file.txt
1 \:\:\:
2 ページ1ヘッダ
3 \:\:
4 foo
5 bar
6 baz
7 \:
8 ページ1フッタ
9 \:\:\:
10 ページ2ヘッダ
11 \:\:
12 FOO
13
14 BAR
15
16 BAZ
17 \:
18 ページ2フッタ
たしかに動いているように思えます。ただし「GNU 版では」です。
-d
オプションに空文字を指定した場合、BSD 系 Unix ではデフォルトの「\:\:\:
」「\:\:
」「\:
」を指定した意味となり、Solaris 11 ではエラーになります。
移植性なんか知ったことじゃない。BSD 系 Unix や Solaris 11 で動かなくても構わない。というのであれば別に nl
コマンドを使っても構いませんが、移植性を考えないならもっといい方法がありますね。
行番号を付けるなら「cat -n」を使う
すべての行に行番号を付ける場合、最も覚えやすく簡単に書けるのは cat -n
です。ちなみに空行を省く場合は cat -b
(AIXを考慮するなら cat -nb
)を使います。
$ cat -n file.txt
1 \:\:\:
2 ページ1ヘッダ
3 \:\:
4 foo
5 bar
6 baz
7 \:
8 ページ1フッタ
9 \:\:\:
10 ページ2ヘッダ
11 \:\:
12 FOO
13
14 BAR
15
16 BAZ
17 \:
18 ページ2フッタ
-n
オプションも -b
オプションも POSIX では標準化されていません。しかし GNU、BSD 系 Unix、System V 系 Unix すべてで動作します。移植性は高いです。
nlはPOSIXで必須のコマンドではない
cat
コマンドの -n
や -b
オプションは POSIX に準拠してないじゃないか!とこだわる人には、nl
コマンドも POSIX で必須となっていませんよと伝えておきます。
POSIX の nl コマンドの仕様 をよく確認しましょう。XSI というマークが付いていますよね? これはオプション機能であり POSIX では実装が必須となっておらず、UNIX の認証を取るときだけ必要なコマンドの証です。UNIX の認証を取るというのは事実上 System V 系 Unix 用の仕様で GNU や BSD 系 Unix では nl
コマンドは実装されていなくてもよく、POSIX の規定通りに実装しなくても良いという意味です。
つまり POSIX に本当にこだわるのであれば nl
コマンドに依存しないほうが良いということです。現実的な問題を一つ示しておきます。BusyBox の nl
コマンドには -d
オプションが実装されていません。もっとも Busybox の nl
コマンドは区切り文字の仕様自体に対応しておらずヘッダやフッタの概念がないため Busybox は nl -ba
ですべての行に行番号が追加されるという違いがあります。
POSIXにこだわりたいならawkで書く
どうしても POSIX で標準化されている範囲で実装したいというの人ために awk の実装を書いておきますが、cat
コマンドの -n
や -b
オプションは POSIX で標準化されていないと言うだけで移植性があるので、使う必要はないでしょう。
$ awk '{printf "%6d\t%s\n", NR, $0}'
$ awk '/^$/{print; next}; {printf "%6d\t%s\n", ++i, $0}'
まとめ
シェルまたはシェルスクリプトですべての行に行番号を付けたいなら cat -n
を使用しましょう。