こんにちは、mizuno_asです。
sedは便利ですよね。自動化する際や手順書を作る際、人間が対話的にやらなければならない操作をコマンドで自動化できるのはすばらしいです。
sedには入力元ファイルを上書きする-iオプションが存在します。みなさんも設定ファイルをコマンドラインから書き換えるような際に、よく使うのではないでしょうか。ところがsedの-iオプションはノーケアで使うと、シンボリックリンクを破壊することはご存知でしょうか?
Photo via Visualhunt
例として以下のように、テキストファイルにシンボリックリンクを張っておきます。
$ echo hoge > test.txt $ ln -s test.txt link $ ls -l 合計 4 lrwxrwxrwx 1 h-mizuno h-mizuno 8 1月 15 11:57 link -> test.txt -rw-rw-r-- 1 h-mizuno h-mizuno 5 1月 15 11:57 test.txt
この状態で置換をかけてみます。
$ sed -i -e 's/hoge/fuga/' link
はい、この通りです。
$ ls -l 合計 8 -rw-rw-r-- 1 h-mizuno h-mizuno 5 1月 15 12:00 link -rw-rw-r-- 1 h-mizuno h-mizuno 5 1月 15 11:57 test.txt $ cat link fuga $ cat test.txt hoge
なぜこのようなことが起こるのでしょうか? straceで、sedが呼び出しているシステムコールを追ってみましょう。
$ strace sed -i -e 's/hoge/fuga/' link execve("/bin/sed", ["sed", "-i", "-e", "s/hoge/fuga/", "link"], [/* 66 vars */]) = 0 (...略...) open("link", O_RDONLY) = 3 ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7ffe518240b0) = -1 ENOTTY (Inappropriate ioctl for device) fstat(3, {st_mode=S_IFREG|0664, st_size=5, ...}) = 0 umask(0700) = 02 open("./sedairHHD", O_RDWR|O_CREAT|O_EXCL, 0600) = 4 umask(02) = 0700 fcntl(4, F_GETFL) = 0x8002 (flags O_RDWR|O_LARGEFILE) fstat(3, {st_mode=S_IFREG|0664, st_size=5, ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f98e28ae000 read(3, "hoge\n", 4096) = 5 fstat(4, {st_mode=S_IFREG, st_size=0, ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f98e28ad000 write(4, "fuga\n", 5) = 5 read(3, "", 4096) = 0 fchown(4, 1000, 1000) = 0 fchmod(4, 0100664) = 0 close(3) = 0 munmap(0x7f98e28ae000, 4096) = 0 close(4) = 0 munmap(0x7f98e28ad000, 4096) = 0 rename("./sedairHHD", "link") = 0 close(1) = 0 close(2) = 0
上記の例では、sedは以下のような挙動を取っています。
- linkというファイルをリードオンリーでopen(2)する(fd=3)
- ./sedairHHDという一時ファイルをopen(2)する(fd=4)
- fd3から”hoge\n”をread(2)する
- fd4へ置換結果(“fuga\n”)をwrite(2)する
- fd3をclose(2)する
- fd4をclose(2)する
- 一時ファイルの名前をlinkにrename(2)する
このように、新規作成したファイルでシンボリックリンクを上書きしてしまっているため、既存のリンクが破壊されてしまうのです。
これを防ぐためには、–follow-symlinksというオプションを指定します。するとファイルをopen(2)した後に、lstat(2)とreadlink(2)でシンボリックリンクを辿ってくれます。そして最後にテンポラリファイルをrename(2)する際、リンク元を置き換えるように挙動が変化するのです。
たとえばCentOSでは、/etc/sysconfig/selinuxは/etc/selinux/configへのシンボリックリンクになっています。なのでそれを知らずに
# sed -i -e 's/^SELINUX=.*/SELINUX=disabled/' /etc/sysconfig/selinux
とすると ((いしかわさんごめんなさい)) 、設定ファイルがsplit brainしてしまいます。くれぐれも気をつけましょう。
余談ですが、似たような話として、Emacsの自動バックアップ機能がハードリンクを切る現象も有名です。
Emacsでは変数make-backup-filesにnil以外が設定されていると、編集したファイルのバックアップを自動的に作成します。この際オリジナルファイルをrenameした上で新しいファイルを作成するという挙動を取るため、オリジナルファイルにハードリンクが張られていると、そのハードリンクはリネームされたバックアップファイルの方を向いてしまうのです。これはEmacsのデフォルト動作のため、ハードリンクが張られたファイルをEmacsで編集すると、意図しない問題を引き起こす可能性があります。
これを防ぐには変数backup-by-copyingにnil以外を設定してください。するとEmacsはリネームではなく、コピーでバックアップを作成するようになります。参考までに。