「ファイルの文字列置換」に関する補足

以前のエントリ某所に軽く誤解を与えたようなので、ちょっとだけ補足。
なんで、

cat < filename | sed 's/AAA/BBB/' > filename

が駄目かって、" > file"としているために、shell がファイルを open してサイズを 0 に切り詰めちゃうから。UNIX システムコールでいうところの

fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC);

に相当する*1。リダイレクションの処理だとこの前後に close(1) やら dup(fd)やら有るけど*2、その辺は面倒なので省略。
で、一見うまく言っているように見えるのは単に、shell が "cat < filename" と "sed 's/AAA/BBB/' > filename" を評価してプロセスを生成するタイミングが多少ずれるから、0 に切り詰められる前に cat が全部読み込んでしまうからで、要するにファイルサイズとタイミングの問題でたまたまうまくいっているだけ。多分。
で、よくある

sed 's/AAA/BBB/' < filename > filename

がタイミングとか関係なく駄目なのは、リダクレクション用の open は exec より先に行われるから、読み込みしようとした時点では確実にファイルサイズが0になっているということ。
とりあえず、Linux では O_TRUNC 付きで open してしまうとすでに open しているファイルも読めなくなるのは確認した。適当なソースで。POSIX 的にはどうなのかは知らないけど。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define TEXT "abcdefghijklmnopqrstuvwxyz\n"
#define BUFFER_SIZE 1024

int main(int argc, char** argv)
{
    int fd_in, fd_out;
    const char* filename;
    char buffer[BUFFER_SIZE];
    int n_read;

    if (argc != 2) {
        fprintf(stderr, "usage: %s filename\n", argv[0]);
        exit(2);
    }

    filename = argv[1];
    if ((fd_in = open(filename, O_RDONLY)) == -1) {
        perror(filename);
        exit(3);
    }

    if ((fd_out = open(filename, O_CREAT | O_WRONLY | O_TRUNC)) == -1) {
        perror(filename);
        close(fd_in);
        exit(3);
    }

    while ((n_read = read(fd_in, buffer, BUFFER_SIZE)) != 0) {
        write(1, buffer, n_read);
    }


    write(fd_out, TEXT, strlen(TEXT));

    close(fd_in);
    close(fd_out);

    return 0;
}

そういえば zsh なら

for i in *; do cp =(sed 's/AAA/BBB/' "$i") "$i"; done

なんてのもありか?bash/sh も考慮に入れてもう少し真面目にやろうとすれば

for i in *; do
    dir=`dirname "$i"`
    fname=`mktemp -p "$dir"`
    sed 's/AAA/BBB/' "$i" > "$fname"
    mv "$fname" "$i"
done

な感じか。さらに真面目にやろうとすると trap で signal 処理とか有るけど、まぁ、この位でそれはやりすぎというか、おとなしく Perl, Ruby or GNU sed あたりを使っとけという話。あ、今見たら Super sed にも -i オプションあるな。

*1:zsh では O_NOCTTY も指定されている。あと noclobber が設定されていたりすると違う挙動になる

*2:dup2 が使えるなら dup2(fd, 1)