sed が活躍する場面は多いですが、いざという時に限って思い通りに動かない。
そんなことってありませんか?
Linux や Mac など、シェルの種類(sh, bash, zsh, ksh)によって挙動が異なるからさらに厄介。
ここでは、過去に自分が役に立った sed の構文を備忘録として残していきます。
sedで最後の行だけ置換する
以前、yaml ファイル内のプレースホルダーの変数を抜き出すことにトライしました。
その値を JSON に置き換えて、ECS のタスク定義を生成しようというものを PHP で実装。

この時も、JSON の最後のカンマをどうしようか悩んだ記憶があります。
今回は複数のキーと値から JSON を生成して、最後のカンマを除去するのをシェルで実現したいと思います。
swagger の JSON ファイルを例にしてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | { "paths" : { "/hoge" : { "get" : { }, "post": { } }, "/fuga" : { "get" : { } }, "/hogefuga" : { "get" : { } } } } |
このような JSON から path 部分のみを抜き出すとしたら、jq コマンドを使うのが早いですね。
1 | $ cat swagger.json | jq .paths | jq keys |
このパスから新たな JSON を構築していきます。
jq の -r オプションはダブルクォーテーションを除去しているだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/bin/sh OUTPUT_FILE="output.json" PATH_LIST=`cat swagger.json | jq .paths | jq keys` len=$(echo ${PATH_LIST} | jq length) for i in $( seq 0 $((${len} - 1)) ); do path=$(echo ${PATH_LIST} | jq -r .[${i}]) cat << EOL >> ${OUTPUT_FILE} "${path}": { "key": "val" }, EOL done |
これでは JSON の最後にカンマが残って syntax エラーになるので sed のアドレスを利用します。
sed [オプション] [アドレス] [コマンド] [対象ファイル]
通常、アドレスを省略することが多いのですが、覚えておくと便利です。
以下ではアドレスに最終行の「$」を指定しました。
1 | sed -i -e '$ s/,//g' ${OUTPUT_FILE} |
このアドレスもそうですが、コマンドにも「s」以外に便利なものが揃っているので覚えておきたいですね。
sedでシングルクォートをエスケープする
sed でシングルクォートをエスケープしようとしたら構文として認識されない。
そんな状況に陥ったことがありませんか?
例えば以下の文字列が書かれたファイルがあったとします。
aaa’bbb
ここで以下のようにシングルクォーテーションを置換対象にしてしまうと、コマンドが成立していない状態となります。
(当たり前といえば当たり前ですが)
1 | $ sed -e 's/'//g' hoge.txt |
シングルクォーテーションではエスケープしてくれないというより、囲んでいる文字の問題ですね。
ということで、下記のようにダブルクォーテーションで置換対象を定義しましょう。
1 | $ sed -e "s/'//g" hoge.txt |
ちなみに、同じことを perl で行うと以下の通り。
1 | $ perl -i.bak -ne "s/'//g;print;" hoge.txt |
シングルクォートを使ってシングルクォートのエスケープ
ちなみに、置換対象のシングルクォートをエスケープしてシングルクォートで囲むとどうなるか。
ダブルクォーテーションで囲みたくない時は試してみたくなりますよね。
以下のように、両サイドをシングルクォートで囲んでも問題ないことがわかります。
1 | $ sed -e 's/'\''//g' hoge.txt |
sedで特定のディレクトリ内のテキストファイルから特定の文字を除去する
sed で特定のディレクトリ内のテキストファイルから、特定の文字を除去したい場合はどうするか。
過去に自動生成したソースコードに変なユニコード文字が混ざっていて、除去しないとビルドできない状況になったことがあります。
そんな時に活躍したのが以下のコマンド。
1 | $ find hoge -name "*.txt" | xargs sed -i 's/[ABC]//g' |
sedでhtmlのタグを除去
html のタグを除去する正規表現を試してみました。
以下のリンクタグの html が書かれたテキストファイル hoge.txt を例とします。
1 | <a href="https://example.org/">https://example.org/</a> |
ここから a タグを除去して、リンク名のみを抽出します。
1 2 3 | $ cat hoge.txt | sed -e 's/<[^>]*>//g' https://example.org/ |
最初は、<.*> でやっていましたが、これだとリンクタグで挟んでいる文字列も除去されてしまったので考え直してみました。
sedで連続する半角スペースを1つにまとめる
MySQL のスロークエリを定期的にレポートするシェルを書いていたのですが、発行される SQL に改行(\nや\r)やらタブやら連続するスペースが混在していて、ちょっと苦戦していました。
\r が混ざっていたのにはなかなか気付かなかった・・・。
最終的には、改行もタブも半角スペースに置き換えたのですが、元々連続しているスペースは正規表現で一気に撲滅しちゃいます。
下記のようなスペースが混ざった文字列をファイル出力します。
1 | $ echo "a b c d e" > rilakkuma.txt |
これを一気に・・・。って、定番な正規表現なのですが。
1 | $ sed -e 's/\s\+/ /g' rilakkuma.txt |
まとめ
sed でハマったパターンについて紹介してきました。
自分がやりたいと思うことに限って、微妙に上手くいかないことが多い印象があります。
ただ、sed は覚えておいて損はないコマンドなので、基本操作は理解しておきたいですね。
ただ、今回紹介したものが最善と言われると疑問なので、ブラッシュアップしていけたらと。
単なる自分の備忘録ですが、また使う機会がくることを期待しつつ残しておきます。