sedを使ったメモ。

前提

  • GNU sed 4.9

GNU版sedをMac + home-managerで導入する

nixpkgsだとgnusedから導入可能。ただMacに標準で入っているBSD版sedと被る & PATHの順序を変える方法がわからないので、別名でsedが実行できるようにしてaliasを作る。

詳細は Nixで既存パッケージのバイナリ名を別名に変える方法 を参照。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{ pkgs, ... }:
{
  home.packages = with pkgs; [
    (runCommand "my-gnused" {} ''
        mkdir -p $out/bin
        ln -s ${pkgs.gnused}/bin/sed $out/bin/gnused
    '')
  ];

  programs.zsh = {
    shellAliases = {
      sed = "gnused";
    };
  };
}
1
2
3
4
5
6
7
8
9
bombrary@bombrary-macbookair:~/dotfiles% sed
Usage: gnused [OPTION]... {script-only-if-no-other-script} [input-file]...

  -n, --quiet, --silent
                 suppress automatic printing of pattern space
      --debug
                 annotate program execution
  -e script, --expression=script
                 add the script to the commands to be executed

BSD版とGNU版の違い

あまり詳しいことは知らないが微妙に違う。

自分が見つけたのは以下の点。

BSD版は、 i コマンドで改行する必要がある

下記は2行目にhiを挿入する例だが、GNU版だと i text でできるが、BSD版だと “”: command i expects \ followed by text" というエラーが出る。

1
2
3
4
5
6
7
8
9
bombrary@bombrary-macbookair:~/dotfiles% echo "aaa\nbbb\nccc" | sed '2i hi'
aaa
hi
bbb
ccc

bombrary@bombrary-macbookair:~/dotfiles% echo "aaa\nbbb\nccc" | /usr/bin/sed '2i hi'
sed: 1: "2i hi
": command i expects \ followed by text

これはそもそも、

1
i text

という文法がGNU版のみで定義された代替のsyntaxであり、本来は

1
2
i \
text

のようにバックスラッシュ+改行で入れる必要があるため。

3.2 sed commands summary

i\
text
insert text before a line.

i text
insert text before a line (alternative syntax).

実際、その通りにやるとBSD版でも動く。

1
2
echo "aaa\nbbb\nccc" | /usr/bin/sed '2i \
hi'
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
bombrary@bombrary-macbookair:~/dotfiles% echo "aaa\nbbb\nccc" | sed '2i \
hi'

aaa
hi
bbb
ccc

bombrary@bombrary-macbookair:~/dotfiles% echo "aaa\nbbb\nccc" | /usr/bin/sed '2i \
hi'

aaa
hi
bbb
ccc

セクションで区切られたテキストの編集例

以下のような、セクションを [...] で区切ったファイル input.txt があるとする。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[section1]
elem1
elem2
elem3
elem4

[section2]
elem1
elem2

[section3]
elem1
elem2

範囲を絞った操作

「section2」たけ表示したい場合。

  • /aaa/,/bbb/ で aaa でマッチしたところから bbbでマッチしたところまでを処理範囲にする
  • それに対し p コマンドで出力
  • -n オプションは p コマンドでの出力結果のみを出す
1
cat input.txt | sed '/^\[section2\]/,/^$/p' -n
1
2
3
4
bombrary@bombrary-macbookair:~% cat input.txt | sed '/^\[section2\]/,/^$/p' -n
[section2]
elem1
elem2

末尾に要素を追加する

例えば、「section2の末尾にelem3を追記したい」というケース。

  • /^\[section2\]/,/^$/ で範囲を絞る
  • さらに以下で、hiを /^$/ がマッチした行に挿入
    1
    2
    
    /^$/i \
    text
    
  • { ... } はコマンドをグルーピングするためのブロック
1
2
3
cat input.txt | sed -e '/^\[section2\]/,/^$/{/^$/i \
hi
}'

一行で /^\[section2\]/,/^$/{/^i/i hi} と書きたいところだが、これだと hi} までが文字列と解釈されてしまう。 -e で分けることでそれが回避できるみたい。

1
cat input.txt | sed -e '/^\[section2\]/,/^$/{/^$/i hi' -e '}'
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
bombrary@bombrary-macbookair:~% cat input.txt | sed -e '/^\[section2\]/,/^$/{/^$/i hi' -e '}'

[section1]
elem1
elem2
elem3
elem4

[section2]
elem1
elem2
hi


[section3]
elem1
elem2

ただこの条件だと末尾の [section3] には対応できない。 [section3] の場合は a コマンドで追記すると言う条件分岐が必要になる。

ここまで考え出すと、sedでわざわざやるか?と言う気もしてくる。

終わりに

ちょっとした整形には便利と言うイメージがあるので使いこなせるようにはなりたい。