読者です 読者をやめる 読者になる 読者になる

😃 mattintosh note 📝

Hello Raspberry Pi!

sed で awk とか grep っぽいこと

Mac OS X ShellScript

何のソースで見たか覚えてないけど Xcode のパスの取得について調べていたときのこと。

SDK のパスはコマンド一つでは取り出せず、xcodebuild -version -sdk <sdkname> から取得しなくてはいけない。

$ xcodebuild -version -sdk macosx10.6
MacOSX10.6.sdk - 'Mac OS X 10.6' (macosx10.6)
SDKVersion: 10.6
Path: /Developer/SDKs/MacOSX10.6.sdk
PlatformVersion: 1.0
ProductBuildVersion: 10M2518
ProductCopyright: 1983-2011 Apple Inc.
ProductVersion: 10.6
ProductName: Mac OS X
ProductUserVisibleVersion: 10.6

grep してからパイプで○○ってなると長くなるのでこういうのは awk を使ってた。

awk '/^Path: / { print $2 }'

んで、どっかのソースコードを見てたらこんな感じの記述をしててびっくり(うろ覚え)。

xcodebuild -version -sdk macosx10.6 | sed -n '/^Path: /{;s/^.* //;p;}'

これの結果は、

/Developer/SDKs/MacOSX10.6.sdk

になる。

最初は意味不明だったけど sed のマニュアルの function の項を読んでみるとそもそも sed は1行で書かなくていいものらしい(というかそっちの方が本来の書き方?)。改行しない場合は ; が区切りになる。なので上のコマンドは書き方を変えると以下のようになる。すごく awk っぽい。意味は「Path: で始まる行を s/^.* // で置換し、p で標準出力する」という意味(-n はパターンスペースの標準出力抑制なのでマッチした部分以外は出力されない)。grep と sed を sed だけで済ませられる。

sed -n '/^Path: / {
    s/^.* //
    p
}'

-n を使わない書き方もあり、これは「マッチした行だけ置換して標準出力してそれ以外は削除(パターンスペースを削除して次のサイクルへ)」という意味。正規表現のフラグと合わせて s/reg/rep/gp ももちろん可能。s/reg/rep/ の reg の部分とアドレスが同じならば省略することもできる(以下の場合は ^Path: が正規表現として使われる)。q を使うとパターンスペースの内容を出力して終了する。2行目の場合は「マッチした行を置換したら出力して終了」ということ。

sed '/^Path: /s///p;d'
sed '/^Path: /{;s///;q;};d'

また、s/reg/rep/ 部分(function)は改行もしくはセミコロンで区切れば連続して使うことができる。クオートを使わない場合はバックスラッシュでセミコロンをエスケープする。

sed 's/foo/bar/;s/hoge/piyo/'
sed s/foo/bar/\;s/hoge/piyo/

単純な文字の置き換えなら y/string/string/ があり、tr コマンドのようなことができる。これは左辺のn文字目を右辺のn文字目で置き換える。s/a/A/;s/b/B/;s/c/C/ を行なっているような感じ。

$ echo abc | sed y/abc/ABC/
ABC

function は他にも色々あり、以下のコマンドを使うと各行の先頭に >>> が追加される。(s/^/>>> / みたいな感じ)

ls | sed 'i\
>>> '

l を使うと \a や \t、$(行末)といった見えない部分を可視化してくれる。

echo "\a" | sed -n l
# 出力
\a$

最終行にあたるアドレス $ と行番号表示の = を組み合わせると行数の取得もできる。行数しか表示されないので wc より使い易いかも。

$ sed '$=;d' log.txt
49328

function は否定条件もあり、以下のようにすると「"123" にマッチする行は出力しない」になる(この場合は素直に d すればいいんだけど)。awk なら !~ とか grep なら -v みたいなことができる。

$ printf '123\n456\n789' | sed -n '/123/!p'
456
789


awk の NR == n は 5s/reg/rep/p のようにアドレスを指定すればできるし print $NF も正規表現を駆使すればなんとかできそう。

アドレスやファンクションの他にパターンスペース、ホールドスペースという仕組みもあるのでそれを使えばもっと複雑なこともできそう。