SED
# Sed
文字列を全置換したり、
行単位で抽出したり、
削除したりする
~~~
$ cat filename | sed 's/hoge/fuga/g'
$ cat filenmae | sed -e 's/hoge/fuga/g' -e 's/foo/bar/g'
~~~
# OverWrite a File
Without `-i` option sed only output the result to the Standard Output.
`-i` option : Overwrite the file;
~~~
$ sed 's/hoge/fuga/g' filename -i
~~~
# replace string in a file
~~~sh
$ cat oldtext | sed -e 's/xxx/XXX/g' > newtext
~~~
or
~~~sh
sed -e 's/xxx/XXX/g' oldtext > newtext
~~~
~~~sh
cat ... | sed -e ... | sed -e ...
cat ... | sed -e ... -e ... //各行ごとにすべての置換を実行
~~~
~~~sh
sed -n -e '11,20!d' -e 6,15p
sed -n -e '11,20!d;6,15p'
~~~
`-e` のあとの
コマンドは s, d , pなどがある。
## sコマンド
例
~~~
sed -e 's/abc/ABC/g'
echo xx34 | sed -e 's/xx\(.\)/-& \1-/g'
# => -xx3 3-4
echo xx34 | sed -E 's/xx(.)/-& \1-/g'
# => -xx3 3-4
# 3行目のみを全置換の対象とする
# gを付けているのでその行に複数マッチすれば全部置換される
sed 3s/abc/ABC/g
# 3行目から5行目のみを全置換の対象とする
sed 3,5s/abc/ABC/g
# 3行目から5行目のみを全置換の対象とする
sed '3,$s/abc/ABC/g'
~~~
-> 各プログラミング言語での正規表現で置換
## dコマンド 2013/10/01
~~~
#行を削除する。
sed d
# 1行目から5行目を削除して6行目以降を出力
sed 1,5d
~~~
! を付けると、逆に対象行以外を削除するコマンドになる。
~~~
# 1行目から行目のみを出力
# ! はシェルが特別に解釈してしまうので、
# シングルクオーテーションを付けるか \ でエスケープが必要
sed '1,5!d'
sed 1,5\!d
~~~
## 以下の例はpコマンドでの例だが、s, y, d コマンドなどでも使える。
~~~
# 先頭の行のみを出力
sed -n -e 1p
# 最後の行のみを出力
# \ はシェルのエスケープ
sed -n -e \$p
# 6行目から15行目を出力
sed -n -e 6,15p
# 奇数行のみを出力
sed -n -e 1~2p
# 1行目、6行目、11行目、16行目、、、を出力
sed -n -e 1~5p
# 正規表現にマッチする行を出力
sed -n -e /xxx/p
# これは以下と同じ
grep -e xxx
# 逆に正規表現にマッチしない行を出力(pコマンドの例ではないが)
sed -e /xxx/d
# これは以下と同じ
grep -v -e xxx
# 正規表現にマッチする行を出力
# 先頭に \ を付ければ、正規表現を囲む記号はなんでもよい
sed -n -e '\%xxx%p'
# 正規表現にマッチする行から3行分を出力
# 正規表現にマッチするごとに最低3行が出力される
sed -n -e /xxx/,+3p
# 正規表現にマッチする行から最後までを出力
# $ が最後という意味になる
sed -n -e '/xxx/,$p'
# 20行目から23行目までの4行分を出力
sed -n -e 20,+3p
~~~
# 基本正規表現と拡張正規表現 2014/10/18
sedで使える正規表現は基本正規表現と拡張正規表現(extended regular expressions)の2種類ある。
オプションをなにも付けないか `-e` で実行すると基本正規表現で、 `-e` の代わりに `-E` または `-r` を付けると拡張正規表現になる。
基本正規表現と拡張正規表現とで扱いが異なるのは以下の7文字のみである。
~~~
+ ? { } ( ) |
~~~
これらの文字の前にバックスラッシュでエスケープするかどうかで、正規表現での特殊な意味になるか、単にその文字そのものの意味になるかが、基本正規表現と拡張正規表現とで変わる。拡張正規表現という名前の割には機能が上がっているわけではない。
以下は自分の環境(GNU sed 4.2.2)で検証した結果。
まずは + の例。拡張正規表現では + で直前の文字が1文字以上の意味になり、 \+ で + そのものを表すが、基本正規表現では逆になる(?)。 (その説明だと2番目の実行例が説明できない)
~~~
$ echo '+' | sed 's/+/OK/g'
OK
$ echo '+' | sed 's/\+/OK/g'
OK
$ echo '+' | sed -E 's/+/OK/g'
sed: -e expression #1, char 8: Invalid preceding regular expression
$ echo '+' | sed -E 's/\+/OK/g'
OK
$ echo 'x' | sed 's/x+/OK/g'
x
$ echo 'x' | sed 's/x\+/OK/g'
OK
$ echo 'x' | sed -E 's/x+/OK/g'
OK
$ echo 'x' | sed -E 's/x\+/OK/g'
x
~~~
次は ? の例だが、これは + と同様。拡張正規表現では ? で直前の文字が0文字または1文字の意味になり、 ?+ で ? そのものを表すが、基本正規表現では逆になる(?)。 (やはり2番目の実行例が説明できない)
~~~
$ echo '?' | sed 's/?/OK/g'
OK
$ echo '?' | sed 's/\?/OK/g'
OK
$ echo '?' | sed -E 's/?/OK/g'
sed: -e expression #1, char 8: Invalid preceding regular expression
$ echo '?' | sed -E 's/\?/OK/g'
OK
$ echo 'x' | sed 's/x?/OK/g'
x
$ echo 'x' | sed 's/x\?/OK/g'
OK
$ echo 'x' | sed -E 's/x?/OK/g'
OK
$ echo 'x' | sed -E 's/x\?/OK/g'
x
~~~
続いて {} の例。{} は正規表現でとしては以下のような意味がある。
~~~
{n}
~~~
直前の文字がちょうどn文字の連続
~~~
{n,}
~~~
直前の文字がn文字以上の連続
~~~
{n,m}
~~~
直前の文字がn文字以上m文字以下の連続
+ や ? と同様に拡張正規表現ではそのまま使えるが、基本正規表現ではバックスラッシュを付けないといけない。
~~~
$ echo '{' | sed 's/{/OK/g'
OK
$ echo '{' | sed 's/\{/OK/g'
sed: -e expression #1, char 9: Invalid preceding regular expression
$ echo '{' | sed -E 's/{/OK/g'
sed: -e expression #1, char 8: Invalid preceding regular expression
$ echo '{' | sed -E 's/\{/OK/g'
OK
$ echo 'xx' | sed 's/x{2}/OK/g'
xx
$ echo 'xx' | sed 's/x\{2\}/OK/g'
OK
$ echo 'xx' | sed -E 's/x{2}/OK/g'
OK
$ echo 'xx' | sed -E 's/x\{2\}/OK/g'
xx
~~~
() や | も同様に拡張正規表現ではそのまま使えるが、基本正規表現ではバックスラッシュを付けないといけない。逆にその文字そのものは、基本正規表現ではそのまま書けばよいが、拡張正規表現ではバックスラッシュが必要。
~~~
$ echo '(' | sed 's/(/OK/g'
OK
$ echo '(' | sed 's/\(/OK/g'
sed: -e expression #1, char 9: Unmatched ( or \(
$ echo '(' | sed -E 's/(/OK/g'
sed: -e expression #1, char 8: Unmatched ( or \(
$ echo '(' | sed -E 's/\(/OK/g'
OK
$ echo 'a|b' | sed 's/a|b/OK/g'
OK
$ echo 'a|b' | sed 's/a\|b/OK/g'
OK|OK
$ echo 'a|b' | sed -E 's/a|b/OK/g'
OK|OK
$ echo 'a|b' | sed -E 's/a\|b/OK/g'
OK
~~~
# 正規表現の中で ‘{}’ を使うには 2014/10/15
正規表現で文字数を指定する {} は上で説明したとおり \ でエスケープするか、オプション -E または -r を付ける必要がある。
## xxxx $\mapsto$ XXXX
~~~
sed 's/x{4}/XXXX/g' # => NG
sed 's/x\{4\}/XXXX/g' # => OK
sed "s/x{4}/XXXX/g" # => NG
sed "s/x\{4\}/XXXX/g" # => OK
sed "s/x\\\{4\\\}/XXXX/g" # => NG
~~~
# 正規表現の中の () で囲まれた部分を使って置換するには 2015/02/01
置換後の文字列には、\1, \2 などを指定することで、正規表現にマッチしたグループに置き換えられる。
~~~ sh
$ echo "bc abcc" | sed 's/b\(c*\)/\1/g'
=> c acc
$ echo "bc abcc" | sed -e 's/b\(c*\)/\1/g'
=> c acc
$ echo "bc abcc" | sed -E 's/b(c*)/\1/g'
=> c acc
~~~
正規表現の中の () には \ でエスケープしないといけない。 -e の代わりに -E をつけると、上で説明したとおり拡張正規表現になってエスケープが不要になる。
-> 正規表現のグルーピングにマッチした文字列を使うには
# タブ文字を全置換するには 2013/06/17
~~~
# タブ文字をスペースに全置換
sed '/\t/ /g'
~~~
# 正規表現にマッチする行を削除するには 2013/06/17
~~~
# '2013' が含まれる行を削除
sed '/^2013/d'
~~~
~~~
# 空行を削除
sed '/^$/d'
~~~
# 正規表現にマッチする行を抽出するには 2013/09/25
~~~
# '2013' が含まれる行のみを出力
sed '/^2013/!d'
# または
sed -n '/^2013/p'
~~~
~~~
# 空行の数をカウント
sed -n '/^$/p' | wc -l
~~~
# 1つ以上の連続するスペースまたはタブ文字を1つのスペースに全置換するには 2014/08/21
~~~
# タブ文字をスペースに全置換
sed -E 's/[\t ]+/ /g'
~~~
# 標準入力ではなくファイルの中のテキストを全置換してファイルを置き換えるには 2014/10/15
GNU sedであれば、-i オプションを付ければ、ファイルを直接書き換えることができる。
~~~
sed -i -e 's/置換前/置換後/g' ファイル名
~~~
-i がないと、全置換後の結果を標準出力に吐き出して、ファイルは書き換えない。
ちなみに以下のようにしてもうまくいかない。
~~~ sh
cat foo.txt | sed -i -e 's/.../.../g' > foo.txt
~~~
ファイルからの入力と結果の書き出しは並列で実行されるため、標準入力から読み込もうにも結果を書き出すために先にファイルサイズが0になってしまい、結果として foo.txt はからのファイルになる。
!!! Tip
BSDやMacに入っている sed は GNU sedではなく、-i オプションがない
# `find` + `sed` ディレクトリの中にあるたくさんのファイルの文字列を全置換するには 2014/10/07
GNU sedがあれば、置換対象のファイルを find などでリストアップして、sedの-iオプションを使う。
~~~
find . -name "*.txt" | xargs sed -i 's/置換前/置換後/g'
~~~
または
~~~
find . -name "*.txt" -exec sed -i 's/置換前/置換後/g' {} \;
~~~
find でマッチしたファイルのほとんどが置換されるならこれでもいいと思うが、ごく一部のファイルだけであれば grep を使って以下のようにしたほうがいいかもしれない。上記 find のやり方だと、置換文字列が含まれていないファイルもタイムスタンプは変わってしまう。
~~~
grep -rl 置換前 | xargs sed -i 's/置換前/置換後/g'
~~~
カレントディレクトリの中にあるすべてのファイルのすべての行の先頭に // を追加する例
~~~
find . -type f -exec sed -i 's/^/\/\//g' {} \;
~~~
ディレクトリの中にあるたくさんのファイルの文字列を全置換する方法
http://d.hatena.ne.jp/hydrocul/20111211/1323595015
2つ以上の全置換を一括で実行するには 2013/06/17
-e で2つ以上の全置換を実行できる。
~~~
cat ファイル名 | sed -e 's/置換前/置換後/g' -e 's/別の置換前/別の置換後/g'
~~~
# 行番号で範囲を指定して抽出するには 2015/01/29
プログラミング言語でいう部分配列とかスライスみたいな。
~~~
# 2行目から4行目を抽出(4行目を含む)
cat ファイル名 | sed -n 2,4p
# 2行目のみを抽出
cat ファイル名 | sed -n 2p
# 2行目から最後まで抽出
cat ファイル名 | sed -n '2,$p'
# または
cat ファイル名 | sed -n 2,\$p
~~~
# 行番号で範囲を指定して削除するには 2013/06/14
~~~
# 2行目から4行目を削除(4行目も削除)
cat ファイル名 | sed -e 2,4d
# 2行目のみを削除
cat ファイル名 | sed -e 2d
# 2行目から最後まで削除
cat ファイル名 | sed -e '2,$d'
# または
cat ファイル名 | sed -e 2,\$d
# または head でも同じことができる
cat ファイル名 | head -n 1
# 最後の行のみを削除
cat ファイル名 | sed -e \$d
~~~
# パスなどを全置換する際にスラッシュをエスケープするのが面倒な場合 2013/06/18
`s/.../..../g` の記法のスラッシュは記号なら比較的なんでもよくて、3つ同じ記号を使っていることに意味があるので、`s@...@...@g` のようにも書ける。
例
~~~
cat ファイル名 | sed -e 's@/etc/foo/bar@/home/my/etc/foo/bar@g'
ログファイルなどの tail -f の出力をsedで処理しながら表示するには 2013/07/09
tail -f access_log | sed --unbuffered ...
# または
tail -f access_log | sed -u ...
~~~
`--unbuffered` または `-u` を付けないとsedがバッファリングをしてしまって、リアルタイムに表示されなくなってしまう。
# 空行を削除するには 2014/03/17
`^$` という正規表現と、`d` での削除コマンドを使う。
~~~
cat ファイル名 | sed '/^$/d'
~~~
空白文字だけの行も空行とみなして削除するには、
~~~
cat ファイル名 | sed '/^[[:blank:]]*$/d'
~~~
または
~~~
cat ファイル名 | sed '/^\s*$/d'
~~~
(違いは調べていない、、、)
# N行おきに抽出するには 2014/09/05
~~~
# 10行目、20行目、、、と10行おきに抽出
cat ファイル名 | sed -ne '0~10p'
# 3行目、13行目、23行目、、、と10行おきに抽出
cat ファイル名 | sed -ne '3~10p'
~~~
perlのワンライナーで以下のようにも書けるが、単純にN行おきに抽出するだけのシンプルな処理であればsedのほうが速い。
~~~
cat ファイル名 | perl -nle '$.%10==0&&print'
~~~