「ファイルの文字列置換」に関する補足
以前のエントリが某所に軽く誤解を与えたようなので、ちょっとだけ補足。
なんで、
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 オプションあるな。