「これsedでできるんだけど、どうやるんだっけ…」という状況ありますよね。
あなたのためにまとめておきました。
sedってなに?
sedはファイルやパイプラインからデータを受け取り、なんらかの加工をして結果を返します。
マニュアルよるとsedは「stream editor」です。
sedの構文
GNUのマニュアルからの引用ですが、下記になります。
1 |
sed OPTIONS... [SCRIPT] [INPUTFILE...] |
[SCRIPT]とは"s/foo/bar/g"のようなヤツのことです。あとで紹介する"1i foo"や"1d"もそうです。
"-e"オプションで直後に[SCRIPT]が来ることを示します。"-e"が無い場合は、最初のオプションではないパラメータがSCRIPTと見なされます。
また、[INPUTFILE]が存在しない場合、sedは標準入力を読み込みます。
下記の例では、"Hello World"の文字列を受け取り、"World"を"Japan"に変換します。
1 2 |
$ echo "Hello World" | sed -e "s/World/Japan/g" Hello Japan |
シングルクオート?ダブルクオート?
上記の例では、[SCRIPT]をダブルクオートで囲いました。これは無くても動きます。
1 2 |
$ echo "Hello World" | sed -e s/World/Japan/g Hello Japan |
しかしこの記法では、[SCRIPT]にスペースを含めたときに問題が起きます。
1 2 3 4 5 6 7 |
# sedがSCRIPTの固まりを理解できずエラーになる $ echo "Hello World" | sed -e s/ /,/g sed: -e expression #1, char 2: unterminated `s' command # うまくいく(ダブルクオートを使用) $ echo "Hello World" | sed -e "s/ /,/g" Hello,World |
ダブルクオートとシングルクオートでは、エスケープ処理の有無に違いがあります。
1 2 3 4 5 6 7 8 9 10 11 |
# ダメ(bash的に"!"は特別な意味をもつ) $ echo "Hello World" | sed -e "s/ /!/g" bash: !/g": event not found # OK ("!"をエスケープする) $ echo "Hello World" | sed -e "s/ /\!/g" Hello!World # OK (シングルクオートを使用) $ echo "Hello World" | sed -e 's/ /!/g' Hello!World |
シングルクオートを使用することでbashによる展開等を行わず、sedに文字列をそのまま渡してあげるイメージですね。
この記事では基本的にダブルクオートを使用します。
sedのバージョン
検証に使用したsedのバージョンです。OSはCentOS 7.1を使用しました。
1 2 |
$ sed --version sed (GNU sed) 4.2.2 |
枯れたツールですのでバージョン間の差異はさほど気にしなくていいですが、GNUとBSDではいくつかの点で挙動が異なりますのでお気をつけください。
この記事ではGNUのsedを対象とします。(お手元のLinuxにはこちらのsedがインストールされているかと思います)
文字列変換の基本
あえて言うほどではないこと
"World"を"Japan"に変換します。先ほどの例と同じです。
1 2 |
$ echo "Hello World" | sed -e "s/World/Japan/g" Hello Japan |
最後のgは"すべて"という意味です。gが無い場合は、初めに見つけたものだけを変換します。
1 2 3 4 5 6 7 8 9 10 11 |
# すべての"o"を"_"に変換 $ echo "Hello World" | sed -e "s/o/_/g" Hell_ W_rld # はじめに見つかった"o"を"_"に変換 $ echo "Hello World" | sed -e "s/o/_/" Hell_ World # 二つ目に見つかった"o"を"_"に変換 $ echo "Hello World" | sed -e "s/o/_/2" Hello W_rld |
ファイルの中身を書き換える
ファイルを渡した場合のデフォルトの出力先は標準出力ですが、"-i"オプションを渡すことでファイルの書き換えができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# サンプルファイルの準備 $ echo "Hello World" > sample.txt # 標準出力に表示 $ sed -e "s/World/Japan/g" sample.txt Hello Japan # ファイルを更新したいが、これだとダメ # 注意: ファイルが空になります! $ sed -e "s/World/Japan/g" sample.txt > sample.txt $ cat sample.txt $ # こうする $ sed -i -e "s/World/Japan/g" sample.txt $ cat sample.txt Hello Japan |
パイプでつなげる
sedの結果は標準出力にでるので、それを利用してパイプで連結させることができます。
1 2 |
$ echo "abc" | sed -e "s/a/A/g" | sed -e "s/b/B/g" | sed -e "s/c/C/g" ABC |
ただし、上記のように単純にsedの書き換えをつなげたいだけならば、"-e"を複数使用したほうがよいでしょう。
1 2 |
$ echo "abc" | sed -e "s/a/A/g" -e "s/b/B/g" -e "s/c/C/g" ABC |
話は多少それますが、小文字から大文字に変えたいだけであれば下記の記法も使用できます。
1 2 3 4 5 6 7 |
# ちょっとややこしいので説明は省略 $ echo "abc" | sed -e 's/\(.*\)/\U\1/' ABC # そもそもsedを使わない方が分かりやすい $ echo "abc" | tr [a-z] [A-Z] ABC |
変換したい文字列に"/"が含まれている場合
区切り文字を変えましょう。sedやシェル的に特別な意味をもたない記号・文字であれば何でもよいです。
(個人的にはコロン(:)が見やすくておススメです)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# "/"のままだとダメ $ echo "/a/b/c" | sed "s//a/b/c//x/y/z/g" sed: -e expression #1, char 6: unknown option to `s' # "\"でクオートする。(見た目的におすすめしない) $ echo "/a/b/c" | sed -e "s/\\/a\\/b\\/c/\\/x\\/y\\/z/g" /x/y/z # シングルクオートを使えば多少はマシになるが... $ echo "/a/b/c" | sed -e 's/\/a\/b\/c/\/x\/y\/z/g' /x/y/z # ":"を使う $ echo "/a/b/c" | sed -e "s:/a/b/c:/x/y/z:g" /x/y/z # "@"を使う $ echo "/a/b/c" | sed -e "s@/a/b/c@/x/y/z@g" /x/y/z |
文字列の挿入・削除
挿入・削除の基本
基本は置き換えです。
1 2 3 4 5 6 7 |
# "ABC"のあとに"D"を挿入(つまり"ABC"を"ABCD"で置き換え) $ echo "ABC DEF" | sed -e "s/ABC/ABCD/" ABCD DEF # "ABC"を削除(つまり"ABC"を""で置き換え) $ echo "ABC DEF" | sed -e "s/ABC//" DEF |
行頭・行末の挿入・削除
行頭・行末に挿入・削除も可能です。
下記の内容のファイル(sample.txt)があるとします。
1 2 |
AAA BBB CCC DDD EEE FFF |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# 行頭に挿入(ZZZ+半角スペース) $ sed -e "s/^/ZZZ /" sample.txt ZZZ AAA BBB CCC ZZZ DDD EEE FFF # 行末に挿入(半角スペース+ZZZ) $ sed -e "s/$/ ZZZ/g" sample.txt AAA BBB CCC ZZZ DDD EEE FFF ZZZ # 行頭のAAAを削除(行頭以外のAAAは削除されない) $ sed -e "s/^AAA //" sample.txt BBB CCC DDD EEE FFF # 行末のCCCを削除(行末以外のCCCは削除されない) $ sed -e "s/CCC$//g" sample.txt AAA BBB DDD EEE FFF |
行の挿入・削除
分量が多くなってきたので、ここからは実行結果を省略しコマンドのみ紹介します。
行の挿入
1 2 3 4 5 6 7 8 9 10 11 |
# 1行目に"foo"という行を挿入 $ sed -e "1i foo" sample.txt # "abcde"という文字列がある前の行に"foo"を挿入 $ sed -e "/abcde/i foo" sample.txt # "abcde"という文字列がある次の行に"foo"を挿入 $ sed -e "/abcde/a foo" sample.txt # 最終行に"foo"を挿入 $ sed -e '$a foo' sample.txt |
行の削除
1 2 3 4 5 6 7 8 9 10 11 12 |
# 1行目を削除 $ sed -e "1d" sample.txt # 3から5行目を削除 $ sed -e "3,5d" sample.txt # 最終行を削除 # 注: シングルクオートを使用しています $ sed -e '$d' sample.txt # "abcde"という文字列が含まれる行を削除 $ sed -e "/abcde/d" sample.txt |
sedと正規表現
使用できる特殊文字
\*, \?, $, ^, \nなどが使用可能です。
すべてを知りたい方はマニュアルをご覧ください。
基本的な使用例
マッチした文字列を削除する例を示します。もちろん文字列の置き換え、行の削除や挿入と組み合わせることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# "abcd", "ab d", "ab@d"などにマッチ. 注:"."はスペースにもマッチする $ sed -e "s/ab.d//g" sample.txt # "a", "ad"にマッチ. 注:?の前に"\"が必要 $ sed -e "s/ab\?//g" sample.txt # "ab","abc","abcdefgh"にマッチ $ sed -e "s/ab.*//g" sample.txt # 行頭のabcにマッチ sed -e "s/^abc//g" sample.txt # 行末のxyzにマッチ sed -e "s/xyz$//g" sample.txt |
マッチした部分の取り出し
正規表現にマッチした部分を取り出し、その後で使用することができます。
1 2 |
$ echo "id:Synergy" | sed -e "s/id:\(.*\)/Hello,\1/g" Hello,Synergy |
上記では、"id:"に続く文字列が取り出され、それを利用しています。
簡単に説明すると、取り出したい箇所をエスケープした丸かっこでくくると、"\1"で参照可能になります。
また、取り出す箇所を2つ、3つと増やすこともできます。その場合は丸かっこを複数使い、\2,\3などと取り出します。
下記の例では、"/etc/passwd"からuid,gidを取得し、整形して表示しています。
1 2 3 4 |
$ head -3 /etc/passwd | sed -e "s/^[^:]*:[^:]*:\([^:]*\):\([^:]*\).*/uid:\1,gid:\2/g" uid:0,gid:0 uid:1,gid:1 uid:2,gid:2 |
適切な例が思い浮かばず複雑になってしまいましたが、ここで気をつけないといけないのは、
".*"は最短マッチでは無く、".*?"もサポートされていないことです。
sedと最短マッチ
まず下記を見てください。
1 2 3 |
# "title"だけ取り出したい $ echo "<title>TECHSCORE</title>" | sed -e "s/<\(.*\)>.*/\1/g" title>TECHSCORE</title |
意図しない結果になってしまいました。先ほど述べたとおり、最短マッチしてくれないわけです。
少し工夫が必要です。
1 2 |
$ echo "<title>TECHSCORE</title>" | sed -e "s/<\([^>]*\).*>/\1/g" title |
うまくいきました。
[^>]* に注目してください。これは">"を含まない文字列にマッチします。
上記の例でいうと、sedは"<"の次の文字からマッチさせていき、">"を見つけたら、その前でマッチをストップします。
使い方あれこれ
ここまでで触れられなかった使用方法を紹介します。
範囲指定
ファイルの行を指定して、sedに処理をさせることができます。
1 2 3 4 5 6 7 8 |
# 1行目から5行目を対象とする $ sed -e "1,5s/foo/bar/g" sample.txt # 5行目から10行目を対象とする $ sed -e "5,10s/foo/bar/g" sample.txt # 10行目から最終行まですべて対象とする $ sed -e "10,\$/foo/bar/g" sample.txt |
ここで使用している"1,5"などの箇所は、sedのマニュアルでは"address"と呼ばれます。
行の表示
単純に指定した行を表示させることができます。
1 2 3 4 5 6 7 8 9 10 11 |
# 1から4行目までを表示(5行目以降を削除 / "head -4 sample.txt"と同じ) $ sed -e '5,$d' sample.txt # これでも同じ $ sed -ne "1,4p" sample.txt $ 5行目以降を表示 $ sed -ne "5,\$p" sample.txt # 2行目だけ表示 $ sed -ne "2p" sample.txt |
エスケープ処理が面倒なときに
SCRIPTの囲みをダブルクオートからシングルクオートに変更します。
1 |
$ sed -e '10,$/foo/bar/g' sample.txt |
大文字・小文字を区別しない
sコマンド(ex:"s/foo/bar/g")のiフラグにより、大文字、小文字に鈍感になります。(case-insensitive)
1 2 3 4 5 6 7 |
# マッチしない $ echo "Hello World" | sed -e "s/WORLD/Japan/g" Hello World # マッチする $ echo "Hello World" | sed -e "s/WORLD/Japan/gi" Hello Japan |
複数のファイルを処理
引数に複数ファイルを並べることができます
1 |
$ sed -e "s/foo/bar/g" sample1.txt sample2.txt sample3.txt |
便利パターン
良く使いそうなパターンを紹介します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# タブをスペース2つに変換 $ sed -e "s/\t/ /g" sample.txt # 空白行を削除 $ sed -e "/^$/d" sample.txt # コメント行(#から始まる行)を削除 $ echo "#abc" | sed -e "/^#/d" # コメント行(スペースの連続が行頭に続く行も含む)を削除 $ echo " #abc" | sed -e "/^ *#/d" # 上記に加えて、空行もいっしょに削除 $ sed -e "/^$\|^ *#/d" sample.txt # あるディレクトリ以下のファイルをまとめて処理 # 注: ファイルを書き換えています $ find /path/to/dir -name "*.txt" | xargs sed -i -e "s/foo/bar/g" |
むすびに
いかがでしたでしょうか。
みなさまに素敵なsedライフが訪れますように。