sed コマンド

文字列を全置換したり、行単位で抽出したり、削除したり、いろいろなテキスト処理のできるコマンド。処理内容はコマンドラインパラメータで指定して、非対話的に一括処理できる。

sedで書ける処理であれば、処理内容にもよるが、perlのワンライナーで書くよりも速いことが多い。

sedというコマンド名は Stream EDitor の略らしい。

基本的な使い方

標準入力をなにかしら処理して標準出力に出力するので、パイプとして使える。まあたいていのUNIXツールはパイプとして使えるのだが。

ファイル名を指定すればそのファイルを読み込んで処理して標準出力に出力する。

ファイル名を指定してオプション -i を使えば、そのファイルを読み込んで、結果をそのファイルに上書きする。ファイル自体を編集したい場合には便利。 (-i はGNU sed限定)

処理内容は、置換処理や行の削除、選択などいろいろできて、オプション -e で指定する。

パイプで使う場合の例

$ cat ./hoge.txt | sed -e 's/xxx/XXX/g' > ./hoge-new.txt

ファイル名を指定して使う場合の例

$ sed -e 's/xxx/XXX/g' ./hoge.txt > ./hoge-new.txt

ファイルを書き換えてしまう例(複数のファイルを指定できる)

$ sed -i -e 's/xxx/XXX/g' ./hoge.txt ./hoge2.txt ./hoge3.txt

-e オプションがない場合はオプション以外の最初の引数が処理内容とみなされるので、たいていは -e を省略できる。処理内容を指示する引数に拡張正規表現を使いたい場合は、 後述のように -E または -r を付ける。

環境によっては -E-r がサポートされていないみたい。

複数の処理をさせるには

-eオプションを複数指定すれば、コマンドを複数実行することができる。またはコマンドを ; で区切って複数並べることもできる。

コマンドが複数ある場合、コマンド1つずつ全行操作するのではなく、各行ごとにコマンドをすべて実行していく。つまり、以下の2つの方法は結果が違う可能性がある。

$ cat ... | sed -e ... | sed -e ...
$ cat ... | sed -e ... -e ...

例えば、1つ目のコマンドに行番号を指定して行を削除する処理があり、 2つ目のコマンドに行番号を指定して出力する処理がある場合、上記2つの方法のいずれかによって行番号がずれる可能性がある。

-e で指定する処理内容

-e のあとのパラメータは、アドレス、コマンド1文字、コマンドパラメータの順に書くことで、処理内容を表す。アドレスはどの行を処理対象とするかを表す。すべての行を処理する場合はアドレスを省略できるので、コマンドの1文字から始まることになる。コマンドは s, d, pなどがある。

$ sed -e 's/xxx/XXX/g' # sコマンド (アドレスがなくて、sのあとはパラメータ)
$ sed -e '30,31d'      # dコマンド (dの前にアドレスがあり、パラメータがない)
$ sed -n -e '30p'      # pコマンド (これもアドレスがあり、パラメータがない)

sコマンド

sコマンドは正規表現で置換処理をする。

$ sed -e 's/abc/ABC/g'

最後の g はすべてのマッチした文字列を置換することを意味する。 g がなくても全行で置換を実行するが、1行に2つ以上マッチした場合は 1つ目しか置換されない。

g がなくても全行の置換をするが、1行に複数マッチする場合でも各行の最初のマッチしか置換をしない。

区切り記号の / は他の記号でもよく、パスの置換などで置換対象に / が含まれている場合は、! など他の記号を使ったほうが便利。

上の例では abc が置換前の正規表現、ABC が置換後の文字列。

置換後の文字列に & を指定するとマッチした文字列の部分が出力され、 \1, \2… を指定するとマッチした文字列のうち正規表現内でカッコでグルーピングされた部分が出力される。

$ echo xx34 | sed -e 's/xx\(.\)/-& \1-/g'
## => -xx3 3-4

$echo xx34 | sed -E 's/xx(.)/-& \1-/g'
## => -xx3 3-4

-e-E の違いは基本正規表現と拡張正規表現を参照。

-n オプションを追加してpフラグを付けると、マッチした行のみを出力する。

sコマンドの前に数字を置くと、その行のみがマッチングの対象になる。(アドレスという)

## 3行目のみを全置換の対象とする
## gを付けているのでその行に複数マッチすれば全部置換される
$ sed 3s/abc/ABC/g

## 3行目から5行目のみを全置換の対象とする
$ sed 3,5s/abc/ABC/g

## 3行目から5行目のみを全置換の対象とする
$ sed '3,$s/abc/ABC/g'

-> 各プログラミング言語での正規表現で置換

dコマンド

dコマンドは行を削除する。

sed d とすると、すべての行が削除されてなにも出力しなくなってしまう。普通は後述のアドレスの機能を使って削除する行を指定して、残りを出力させる。

## 1行目から5行目を削除して6行目以降を出力
$ sed 1,5d

! を付けると、逆に対象行以外を削除するコマンドになる。

## 1行目から行目のみを出力
## ! はシェルが特別に解釈してしまうので、
## シングルクオーテーションを付けるか \ でエスケープが必要
$ sed '1,5!d'
$ sed 1,5\!d

pコマンド

行を単に出力する。通常は -n オプションと組み合わせる。 sedコマンドでは処理結果をデフォルトで出力するが、 -n オプションを付けるとデフォルトの出力がされなくなり、 pコマンドの出力のみになる。

アドレス

コマンドの実行対象行を指定する行番号をアドレスという。行番号だけでなくや正規表現で指定して、その正規表現にマッチする行を実行対象とすることもできる。

アドレスは -e のあとの処理内容を表すコマンドの前に付ける。

以下の例は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

# 逆に正規表現にマッチしない行を出力(dコマンドでの例)
sed -e /xxx/d
# これは以下と同じ
grep -v -e xxx

# 正規表現にマッチする行を出力
# 先頭に \ を付ければ、正規表現を囲む記号はなんでもよい
sed -n -e '\%xxx%p'

# 1つ目の正規表現にマッチする行から2つ目の正規表現にマッチする行までを出力
sed -n -e /xxx/,/yyy/p

# 正規表現にマッチする行から3行分を出力
# 正規表現にマッチするごとに最低3行が出力される
sed -n -e /xxx/,+3p

# 正規表現にマッチする行から最後までを出力
# $ が最後という意味になる
sed -n -e '/xxx/,$p'

# 20行目から23行目までの4行分を出力
sed -n -e 20,+3p

sコマンドでアドレスの機能を使うと、該当する行だけ置換処理が実行され、それ以外の行は置換せずにそのまま出力される。

オプション

-e script
処理内容を指定する。
-E
拡張正規表現を使う。基本正規表現と拡張正規表現を参照。
-i, --in-place
結果を標準出力せずにファイルを置き換える。
-n
修理する各行の自動出力をしない。 -p コマンドによる出力のみになる。
-r
拡張正規表現を使う。-E と同じなのかな(?)
--help
ヘルプを表示
--version
バージョンを表示

基本正規表現と拡張正規表現

sedで使える正規表現は基本正規表現と拡張正規表現(extended regular expressions)の2種類ある。

オプションをなにも付けないか -e で実行すると基本正規表現で、 -r を付けるかまたは -e の代わりに -E を使うと拡張正規表現になる。

基本正規表現と拡張正規表現とで扱いが異なるのは以下の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

HOWTO

大文字小文字を変換するには

全部大文字にするには \U というのを使うとよい。U はたぶん Upper の略。

head main.go | sed -E 's/(.*)/\U\1/'
# または
head main.go | sed 's/\(.*\)/\U\1/'

小文字にするには \L というのを使うとよい。L はたぶん Lower の略。

head main.go | sed -E 's/(.*)/\L\1/'
# または
head main.go | sed 's/\(.*\)/\L\1/'

正規表現の中で {} を使うには

正規表現で文字数を指定する {} は上で説明したとおり \ でエスケープするか、オプション -r を付けるかまたは -e の代わりに -E を使う必要がある。

# xxxx を 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

正規表現の中の () で囲まれた部分を使って置換するには

置換後の文字列には、\1, \2 などを指定することで、正規表現にマッチしたグループに置き換えられる。

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 をつけると、上で説明したとおり拡張正規表現になってエスケープが不要になる。

-> 正規表現のグルーピングにマッチした文字列を使うには

タブ文字を全置換するには

## タブ文字をスペースに全置換
$ sed '/\t/ /g'

正規表現にマッチする行を削除するには

# '2013' が含まれる行を削除
sed '/^2013/d'

# 空行を削除
sed '/^$/d'

正規表現にマッチする行を抽出するには

# '2013' が含まれる行のみを出力
sed '/^2013/!d'
# または
sed -n '/^2013/p'

# 空行の数をカウント
sed -n '/^$/p' | wc -l

連続するスペースまたはタブ文字を1つのスペースに全置換するには

# タブ文字をスペースに全置換
sed -E 's/[\t ]+/ /g'

連続する空行を1行にまとめるには

sedでなくてcatコマンド-s オプションで簡単にできる。

$ cat -s input.txt

標準入力ではなくファイルの中のテキストを全置換してファイルを置き換えるには

GNU sedであれば、-i オプションを付ければ、ファイルを直接書き換えることができる。

sed -i -e 's/置換前/置換後/g' ファイル名

-i がないと、全置換後の結果を標準出力に吐き出して、ファイルは書き換えない。

ちなみに以下のようにしてもうまくいかない。

cat foo.txt | sed -i -e 's/.../.../g' > foo.txt

ファイルからの入力と結果の書き出しは並列で実行されるため、標準入力から読み込もうにも結果を書き出すために先にファイルサイズが0になってしまい、結果として foo.txt はからのファイルになる。

BSDやMacに入っているsedはGNU sedではなく、-i オプションがないらしい。

ディレクトリの中を再帰的に全置換するには

-> ディレクトリの中を再帰的に全置換するには

2つ以上の全置換を一括で実行するには

-e で2つ以上の全置換を実行できる。

cat ファイル名 | sed -e 's/置換前/置換後/g' -e 's/別の置換前/別の置換後/g'

行番号で範囲を指定して抽出するには

プログラミング言語でいう部分配列とかスライスみたいな。

## 2行目から4行目までを抽出(4行目を含む)
$ sed -n 2,4p foo.txt

## 2行目のみを抽出
$ sed -n 2p foo.txt

## 2行目から最後までを抽出
$ sed -n '2,$p' foo.txt
## または
$ sed -n 2,\$p foo.txt

## 先頭から4行目までを抽出
$ sed -n 1,4p foo.txt
## または
$ sed 4q foo.txt

q はその行で終了という意味。

行番号で範囲を指定して削除するには

# 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

N行おきに抽出するには

## 1行目、11行目、21行目、、、と10行おきに抽出
$ cat ファイル名 | sed -ne '1~10p'

## 10行目、20行目、、、と10行おきに抽出
$ cat ファイル名 | sed -ne '0~10p'

perlのワンライナーで以下のようにも書けるが、単純にN行おきに抽出するだけのシンプルな処理であればsedのほうが速い。

$ cat ファイル名 | perl -nle '$.%10==0&&print'

パスなどを全置換する際にスラッシュをエスケープするのが面倒な場合

s/.../..../g の記法のスラッシュは記号なら比較的なんでもよくて、3つ同じ記号を使っていることに意味があるので、s@...@...@g のようにも書ける。

cat ファイル名 | sed -e 's@/etc/foo/bar@/home/my/etc/foo/bar@g'

ログファイルなどの tail -f の出力をsedで処理しながら表示するには

tail -f access_log | sed --unbuffered ...
# または
tail -f access_log | sed -u ...

--unbuffered または -u を付けないとsedがバッファリングをしてしまって、リアルタイムに表示されなくなってしまう。

空行を削除するには

^$ という正規表現と、dでの削除コマンドを使う。

cat ファイル名 | sed '/^$/d'

空白文字だけの行も空行とみなして削除するには、

cat ファイル名 | sed '/^[[:blank:]]*$/d'

または

cat ファイル名 | sed '/^\s*$/d'

(違いは調べていない、、、)

TSVファイルなどでヘッダ行があるファイルから正規表現で行を抽出するには

1行目がヘッダ行などでこれは正規表現に関係なく残しておいて、残りの行は正規表現にマッチしたもののみにしたい場合、 1行目を抽出するコマンド 1p と正規表現で抽出するコマンドを組み合わせる。

cat ファイル名 | sed -n -e 1p -e /パターン/p

指定の行番号に行を挿入するには

sed 3i test のように書くと、3行目に test という行を挿入する、という意味になる。

また、sed 3a test と書くと、3行目の次に test という行を挿入する、という意味になる。

$ seq 5
1
2
3
4
5
$ seq 5 | sed '3i test'
1
2
test
3
4
5
$ seq 5 | sed '3a test test'
1
2
3
test test
4
5
このサイトは筆者(hydrocul)の個人メモの集合です。すべてのページは永遠に未完成です。