sed と正規表現

あるディレクトリーにある共有ライブラリーを列挙して、

$ ls android-ndk-r5/platforms/android-5/arch-arm/usr/lib/lib*.so
android-ndk-r5/platforms/android-5/arch-arm/usr/lib/libGLESv1_CM.so
android-ndk-r5/platforms/android-5/arch-arm/usr/lib/libGLESv2.so
android-ndk-r5/platforms/android-5/arch-arm/usr/lib/libc.so
android-ndk-r5/platforms/android-5/arch-arm/usr/lib/libdl.so
android-ndk-r5/platforms/android-5/arch-arm/usr/lib/liblog.so
android-ndk-r5/platforms/android-5/arch-arm/usr/lib/libm.so
android-ndk-r5/platforms/android-5/arch-arm/usr/lib/libstdc++.so
android-ndk-r5/platforms/android-5/arch-arm/usr/lib/libthread_db.so
android-ndk-r5/platforms/android-5/arch-arm/usr/lib/libz.so

ここからファイル名だけ抜き出したい、ということで sed を使おうとおもったんだけれど、なにぶん sed に不慣れなものでとても苦労したという話。
最終的に置き換えに使う正規表現は s/.*\/\(.*\)/\1/ になった。

$ ls android-ndk-r5/platforms/android-5/arch-arm/usr/lib/lib*.so | sed 's/.*\/\(.*\)/\1/'
libGLESv1_CM.so
libGLESv2.so
libc.so
libdl.so
liblog.so
libm.so
libstdc++.so
libthread_db.so
libz.so

/\([^/]\+\/\)\+\(.*\.so\)/

対象の文字列のパターンは xxx/ がなんどか繰り替えされ、 xxx.so というライブラリー名が残る、というものにみえる。
だから「/ を含まない文字列の繰り返しに / を連結した文字列」の繰り返しと、残りの .*\.so という具合に考えたんだけれど、 sed正規表現 ([^/]+\/)+ が xxx/ の繰り返しにマッチしてくれない。
グループをつくるには () をエスケープせねばならない。そして一回以上をの繰り返しをあらわす + もエスケープしないといけないようだ。さらに選択マッチングの [] の中では / はエスケープしなくてもよいよう。そんなこんなが xxx/ の繰り返しへのマッチングを難しくしてくれたわけだ。
もろもろをあわせて最終的に、 xxx/ の繰り返しと残りの xxx.so という形にマッチングさせる sed正規表現は /\([^/]\+\/\)\+\(.*\.so\)/ ということがわかった。
ふたつめのグループを取り出せばよいので 's/\([^/]\+\/\)\+\(.*\.so\)/\2/' を sed に渡すと、やはりライブラリーの名前だけが取り出せる。

$ ls android-ndk-r5/platforms/android-5/arch-arm/usr/lib/lib*.so | sed 's/\([^/]\+\/\)\+\(.*\.so\)/\2/'
libGLESv1_CM.so
libGLESv2.so
libc.so
libdl.so
liblog.so
libm.so
libstdc++.so
libthread_db.so
libz.so

まあでも、 / が出てくるまで .* で任意文字列の最長マッチをして、残りの .* を取り出す s/.*\/\(.*\)/\1/ がシンプルでいいやね。