昨日のシェル芸ヴェンキョウカイ(問2)で話題になった、BSD系環境で制御コードを置換するはなしです。
sedではやれないけどtrなら……という話です。

テストデータ

問2;https://github.com/ryuichiueda/ShellGeiData/tree/master/vol.18/Q2

内容

概要

問2の模範解答は

1
2
3
comm <(sort a) <(sort b) | sed 's/^/\t/' |
sed 's/\t\t\t/c /' | sed 's/\t\t/b /' | sed 's/\t/a /' | sort

となっています。
これは「先頭にtabを突っ込んで、タブの連続を置換していく」という解き方で、GNU sedの利用を前提としています。今回は解説のため、まずはBSDのsedとtrを使った別解を書いてみます。
tabの代わりにカンマ(,)を使用するという発想です。

1
2
3
4
5
6
7
8
% comm <(sort a) <(sort b) | tr '\011' ',' |  sed 's/^/,/' | sed 's/,,,/c /' | sed 's/,,/b /' | sed 's/,/a /' | sort
a 川崎
a 鹿島田
b 南多摩
b 登戸
c 分倍河原
c 谷保

trの部分の解説

trの前で止めるとこうなっていて

1
2
3
4
5
6
7
8
% comm <(sort a) <(sort b)
                分倍河原
        南多摩
川崎
        登戸
                谷保
鹿島田

※commコマンドの話は後でしますが、データがタブ区切りになっていますtrの後で止めるとこうなっています。

1
2
3
4
5
6
7
8
% comm <(sort a) <(sort b) | tr '\011' ','
,,分倍河原
,南多摩
川崎
,登戸
,,谷保
鹿島田


さて、manによると
>
    \octal

バックスラッシュに続き、1〜3 桁の 8 進数が続いたものは、その値 を符号化した文字を表現します。この 8 進数の並びに続いて数字を 文字として指定したい場合には、8 進数の並びが 3 桁となるよう に、8 進数の上位桁 (左) に 0 を埋めてください

とのことです。つまり

1
2
tr '\011' ','

は、ASCIIコードでtab(16進数で09、8進数で11)をカンマ(,)に置換する、ということになります。


どんでん返しその1

さっきの、sedのmanの続きです……

>
    \character

バックスラッシュに続く、特定の特殊な文字は、特殊な値に対応しています。\a <ベル文字> \b <バックスペース> \f <フォームフィード> \n <改行> \r <復帰> \t <水平タブ> \v <垂直タブ>

trは普通にバックスラッシュ文字が使えました……そういえばそうだった……
つまり

1
2
% comm &lt;(sort a) &lt;(sort b) | tr &#39;\t&#39; &#39;,&#39; | sed &#39;s/^/,/&#39; | sed &#39;s/,,,/c /&#39; | sed &#39;s/,,/b /&#39; | sed &#39;s/,/a /&#39; | sort

でいいってことですよ奥さん!! 

どんでん返しその2

……ここまで書いといて何ですが、printf使えばsedでもやれますFreeBSDのsedで\rを取り除く方法 - Qiita

1
2
comm &lt;(sort a) &lt;(sort b) | sed "s/^/`printf &#39;\t&#39;`/" | sed "s/`printf &#39;\t\t\t&#39;`/c /" | sed "s/`printf &#39;\t\t&#39;`/b /" | sed "s/`printf &#39;\t&#39;`/a /" | sort


以上!


参考URL