ファイルを適切に変更する方法はありますか?

ファイルを適切に変更する方法はありますか?

私はかなり大きなファイル(35 Gb)を持っていて、このファイルを内部でフィルタリングしたいと思います(たとえば、他のファイルのための十分なディスク容量がありません)。特にgrepを実行し、いくつかのパターンを無視したいと思います。方法はありますか?他のファイルを使用せずにこれを?

foo:以下を含むすべての行をフィルタリングしたいとします。

答え1

これはシステムコールレベルで可能です。プログラムは、ターゲットファイルを切り捨てることなく書き込み用に開くことができ、標準入力から読み取った内容を書き込み始めることができます。 EOFを読み込むと出力ファイルが切り捨てられることがあります。

入力から行をフィルタリングするため、出力ファイルの書き込み位置は常に読み取り位置より小さくなければなりません。これは、新しい出力で入力が破損してはならないことを意味します。

ただし、これを実行できるプログラムを見つけることは問題です。dd(1)を開くときに出力ファイルを切り捨てないオプションがありますが、conv=notrunc末尾でも切り捨てられず、元のファイルコンテンツがgrepコンテンツの後に残ります(同様のコマンドを使用grep pattern bigfile | dd of=bigfile conv=notrunc)。

システムコールの観点からは非常に簡単なので、小さなプログラムを書いて、小さな(1MiB)フルループバックファイルシステムでテストしました。必要に応じて動作しますが、まず別のファイルでテストしたいと思います。ファイルを上書きすることは常に危険です。

オーバーライド.c

/* This code is placed in the public domain by camh */

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

int main(int argc, char **argv)
{
        int outfd;
        char buf[1024];
        int nread;
        off_t file_length;

        if (argc != 2) {
                fprintf(stderr, "usage: %s <output_file>\n", argv[0]);
                exit(1);
        }
        if ((outfd = open(argv[1], O_WRONLY)) == -1) {
                perror("Could not open output file");
                exit(2);
        }
        while ((nread = read(0, buf, sizeof(buf))) > 0) {
                if (write(outfd, buf, nread) == -1) {
                        perror("Could not write to output file");
                        exit(4);
                }
        }
        if (nread == -1) {
                perror("Could not read from stdin");
                exit(3);
        }
        if ((file_length = lseek(outfd, 0, SEEK_CUR)) == (off_t)-1) {
                perror("Could not get file position");
                exit(5);
        }
        if (ftruncate(outfd, file_length) == -1) {
                perror("Could not truncate file");
                exit(6);
        }
        close(outfd);
        exit(0);
}

次のように使用できます。

grep pattern bigfile | overwrite bigfile

私は主に他の人がそれを試す前にコメントできるようにこれを投稿しています。たぶん他の人が似たようなことをして、もっとテストされたプログラムを知っているかもしれません。

答え2

Bourneに似たシェルの場合:

{
  cat < bigfile | grep -v to-exclude
  perl -e 'truncate STDOUT, tell STDOUT'
} 1<> bigfile

なぜか人々は40代を忘れる傾向があるようだ。基準読み取り+書き込みリダイレクト演算子です。

読み取り+書き込みモードで開いてbigfile(ここで最も重要なのは)stdoutしばらく(一人で)開いたときに切り捨てられません。終了後に一部の行を削除して内部のどこかを指す場合は、その時点以降のコンテンツを削除する必要があります。したがって、このコマンドは現在の場所(から返される)からファイルを切り捨てます。bigfilecatstdingrepstdoutbigfileperltruncate STDOUTtell STDOUT

(これはcatGNUのためのものですgrep。そうしないと、stdinとstdoutが同じファイルを指していると文句を言うでしょう。)


1 まあ、<>70年代後半からBourneシェルにいたが、もともとは文書化されておらず、正しく実装されていません。ash1989年の元の実装にはなく、POSIXshリダイレクト演算子(90年代初頭からPOSIXのsh基盤であったksh88)にもかかわらずFreeBSDに追加されず、移植性にsh優れていました。15歳おそらくより正確です。また、指定されていない場合、デフォルトのファイル記述子はすべてのシェルで0ですが、ksh932010年のksh93t +では0から1に変更されました(以前のバージョンとの互換性とPOSIX準拠)。

答え3

sed内部を使用してファイルを編集できます(ただし、中間の一時ファイルが作成されます)。

以下を含むすべての行を削除するにはfoo:

sed -i '/foo/d' myfile

以下を含むすべての行を維持しますfoo

sed -i '/foo/!d' myfile

答え4

これは古い問題ですが、私が見るには長い問題であり、これまで提案されているよりも一般的で明確な解決が可能であると思います。クレジットが必要なクレジット:<>Stéphane Chazelasが言及した更新演算子を考慮していない場合は、それを見つけたかもしれません。

ファイルを開くアップデートのためBourneシェルでは使用が制限されています。シェルではファイルが見つからず、ファイルが古い長さより短い場合は新しい長さを設定することもできません。ところが直しやすいのに属さないのがおかしいですね/usr/bin

これは働きます:

$ grep -n foo T
8:foo
$ (exec 4<>T; grep foo T >&4 && ftruncate 4) && nl T; 
     1  foo

(ステファンに送る帽子のヒント):

$ { grep foo T && ftruncate; } 1<>T  && nl T; 
     1  foo

(私はGNU grepを使用しています。彼が答えを書いてから何か変更された可能性があります。)

それ以外はそうではありません。/usr/bin/ftruncate。数十行のCコードについては、以下を参照してください。これカットユーティリティは、任意のファイル記述子を所望の長さに切り捨てる。デフォルトは標準出力と現在位置です。

上記のコマンド(最初の例)

  • 更新のためにファイル記述子4を開きますT。 open(2) と同様に、この方法でファイルを開くと、現在のオフセットが 0 になります。
  • grepその後正常に進み、TシェルはT記述子4を介して出力をリダイレクトする。
  • カット記述子4でftruncate(2)を呼び出して、長さを現在のオフセット値に設定します(具体的には、grepそのままにしてください)。

その後、サブシェルが終了し、記述子4が閉じます。これはカット:

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int
main( int argc, char *argv[] ) {
  off_t i, fd=1, len=0;
  off_t *addrs[2] = { &fd, &len };

  for( i=0; i < argc-1; i++ ) {
    if( sscanf(argv[i+1], "%lu", addrs[i]) < 1 ) {
      err(EXIT_FAILURE, "could not parse %s as number", argv[i+1]);
    }
  }

  if( argc < 3 && (len = lseek(fd, 0, SEEK_CUR)) == -1 ) {
    err(EXIT_FAILURE, "could not ftell fd %d as number", (int)fd);
  }


  if( 0 != ftruncate((int)fd, len) ) {
    err(EXIT_FAILURE, argc > 1? argv[1] : "stdout");
  }

  return EXIT_SUCCESS;
}

ftruncate(2) は、このように使用すると移植性がないことに注意してください。絶対に一般的に言えば、書き込まれた最後のバイトを読み取り、O_WRONLYファイルを再度開き、バイトを見つけて書き込んで閉じます。

この質問が5年を経たことを考えると、解決策は明確ではないと言いたいと思います。それは利点を取る実装する<>あいまいな新しい記述子と演算子を開きます。ファイル記述子を介してinodeを操作するための標準ユーティリティは思い出されません。 (構文は次のようになりますが、改善されたかどうかftruncate >&4はわかりません)。私の考えでは、あなたが私よりPerlを好きではない限り、それはStéphaneのものよりも少し明確です。誰かが役に立つと思います。

同じタスクを実行する別の方法は、出力を使用できる現在のオフセットを報告するlseek(2)の実行可能バージョンです。/usr/bin/切り捨て、いくつかのLinuxシステムで利用可能です。

関連情報