web-dev-qa-db-ja.com

最初にファイル(同じ場所の同じiノード)を切り捨てることは可能ですか?

新しいファイル(> newfile)に書き込んで元に戻すことなく(mv newfile file)、fileの後続バイトを削除することができます。それはtruncateで行われます:

truncate -s -1 file

先頭のバイトを削除することは可能ですが、(inodeを変更する)移動することにより(tailのバージョンによっては):

tail -c +1 file > newfile ; mv newfile file

だから:ファイルを移動せずにそれを行うには?
理想的には、トランケートのように、非常に大きなファイルでも数バイトだけ変更する必要があります。

注:sed -iはファイルのiノードを変更するため、たとえ役立つ場合でも、この質問のIMOに対する回答ではありません。

4
Isaac

_ksh93_の場合:

_tail -c+2 < file 1<>; file
_

(ここで、_<>;_は、リダイレクトされるコマンドが成功した場合に最後にファイルを切り捨てる標準_<>_演算子のksh93固有のバリアントです)。

最初のバイトを削除します(ファイルの残りの部分を上書きし、最後で切り捨てます)。

同じことをshを使って行うことができます:

_{
  tail -c+2 < file &&
    Perl -e 'truncate STDOUT, tell STDOUT'
} 1<> file
_

nsparseスパースファイルになることに注意してください(ただし、後で_fallocate -d_を使用してホールを再掘ることもできます)。

読み取り/書き込みエラーが発生すると、tailはおそらくファイルを部分的に上書きしたまま保釈します(たとえば、abcdefghは、bcddefghの書き換え後に失敗すると、最終的にbcdになる可能性があります)。エラー時に書き込みオフセットを報告するように上記を調整して、データを回復する方法を知ることができます。まだ_ksh93_を使用:

_unset -v offset
{ tail -c+2 < file || false >#((offset=CUR)); } 1<>; file
_

その後、_$offset_が設定されると、正常に書き込まれたデータの量が含まれます。

Linux(3.15以降)およびext4またはxfsファイルシステムでは、collapsefallocate() systemを使用したファイルシステムブロックサイズの倍数のサイズまたはオフセットの範囲またはバイト呼び出しまたはfallocateユーティリティ。

だから例えば

_fallocate -c -l 8192 file
_

ファイルの最初の8192バイトを削除します(FSのブロックサイズが8192の約数であると仮定した場合)。ファイルの残りの部分を書き換える必要はありません。しかし、 FSブロックサイズの倍数ではないセクションを削除したい。

4

たとえば、fileの最初の9バイトを同じiノードに切り捨てるには、次のようにします。

dd if=file of=file bs=1 skip=9 conv=notrunc

実際の値に応じて、プロセスがより効率的になるようにbsおよびskipを調整できます。

入力時にいくつかのブロック(bsバイト)をスキップし、ブロックごとにファイルへのコピーを開始します。

1

これは、spongemoreutilsを使用して行うことができます。 tailを使用して最初の3バイトを削除する例を以下に示します

tail -c +4 file | sponge file
1
Arjuna

「非常に大きなファイル」の意味に依存します。あなたの限界は何ですか?

すべてをawk文字列としてメモリに読み込み、部分文字列を元のファイルに書き戻すことができます。ある段階では、awkには元のファイルとsubstrが同時に含まれますが、おそらく0.5 GBの実行可能なソリューションです。 awkは私のラップトップで毎秒約80 MBを実行します。

Cでは、書き込みの開始ポインタを移動するだけなので簡単です。

1
Paul_Pedant

ここにいくつかのアイデア(主に理論)があり、それは質問のiノード部分(簡単な部分)を扱いません。

最初からブロック全体を削除することは可能だと思います(しかし、それを行うためのツールを知らない)。ただし、整数のブロックを削除する必要があるのは珍しいことであり、(説得力のある使用例がない限り)実装する価値はありません。

次に、ファイルシステムに、ファイルをブロックの途中から開始させる方法が考えられます。 (テールマージについては疑問に思います。それを使用できるかどうかは疑問ですが、ファイルシステムが複雑になり、ファイルシステムのハックが必要になります)。別の方法としては、ファイルの最初の部分を無視するFuseオーバーレイファイルシステムがあります。これはスタートを削除しませんが、それを削除したように見えます。これを段落1のアイデアと組み合わせると(機能させることができる場合)、ほとんどすべてのストレージが元に戻ります。

ファイルの先頭/中間/末尾からブロックを削除するカーネル呼び出しは fallocate です。ただし、ext4(エクステントベースのファイルのみ)のLinuxカーネル3.15以降とLinuxシステムのXFSにのみ実装されています。また、穴を開けてファイルをまばらにすることもできます(ブロックの制限はありませんが、報告されるサイズは変わりません)。 -@isaacに感謝します。

ツールもあります(シェルから使用できますfallocate

1
ctrl-alt-delor

これはCではかなり簡単で、同じiノードを使用し、作業ファイルを使用しません。それを正しくするために注意が必要です。デバイスのブロックサイズ(例4096)を見つけるには、おそらく予備クエリが必要ですが、2の任意の累乗で十分な場合もあります(例64K)。

キャタピラーとしてデータフローを視覚化します。前進させ、データを新しい場所に移動させます。

ファイルを読み取り/書き込みで開き、システムコールですべてを実行して読み取り/書き込みを行い、FILE *ルーチンでのバッファリングの問題を回避します。

先頭(N)のファイルから削除されるバイト数は、完全なブロックの数といくつかのスペアバイトです(これらのコンポーネントのいずれかまたは両方がゼロになる可能性があります)。

Aにシークし、X * 4096バイトを読み取ります。ここで、Xは(効率のために)大きくなるように選択されていますが、ばかばかしくはありません。たぶん4MBのバッファはスイートスポットでしょう。

0にシークし、そのバッファーを、占有する必要があるブロックの完全な数に書き込みます。私が紙で見ることができる限り、それは決してそれ自体を折り返すことはできません-次の未読バイトは前のブロックにはありません。

ファイルがなくなるまですすぎ、繰り返します(両方のシークで4MBずつステップアップ)。ショートブロックを適切に処理します。

これにより、最後のNバイトの余分なコピーが残り、システムコールで切り捨てることができます。

パフォーマンスは問題ないはずです。書き込みはブロック単位で行われます。理論的には、各読み取りはオーバーラップのため2つのブロックアクセスを必要としますが、連続読み取りはそれを回避します(たとえば、4MBは1024ではなく1025ブロックを読み取ります)。

これはddコマンドを使用したスクリプトで実行できると思いましたが、ddのブロックサイズオプションはシークと読み取りに適用されるため、非常に非効率になります。

テスト方法:ランダムデータの100MBファイルを取得します。次に、それをNバイトの小さなファイルに追加します。コード、cksumを実行し、ファイルが追加したものと同じであることを確認します。時間を計る。 0、<1ブロック、正確な数のブロック、いくつかのブロック+ビット、ファイル全体など、さまざまなNの値でテストします。

バウンティを獲得するためにコードを作成してテストする必要がありますか?

1
Paul_Pedant

私は、この問題に対して信頼できるその場での更新と思われるものを持っています。 bashでのテストハーネス(48行)とCプログラム(140行)があります。私のテストは5400rpmドライブを搭載したラップトップ上で行われるため、きらめきはありません。

Catを使用して7 GBのファイルを7 GBのファイルの前に配置し(3m:31秒かかります)、64 GBのバッファーを使用して7 GBをファイルの前に書き換え、最後(3m:48s)を縮小できます。 。だから私はどちらの場合もディスク制限されていると思います。ファイルの破損をチェックするのにちょうど1分30秒かかります。

コードを少し片付け、日曜日に投稿します。

このテストスクリプトは、テスト方法を説明します。

#! /bin/bash

function Usage { expand -t 4 <<'[][]'
#.. Test package for xFront C program:

    Usage: ./xFrontTest padFile mainFile
[][]
}

#.. Test package for xFront C program: ./xFrontTest padFile mainFile

#.. Strategy:
#.. Makes the binary every time in case we forget after an edit.
#.. Makes a test file by appending the two named files.
#.. Finds the size to remove off the front by sizing padFile.
#.. Finds the cksum of mainFile for verification purposes.
#.. Calls the test with appropriate args.
#.. Checks the result file checksum matches the original.

#### Script Body Starts Here.

    [[ -f "${1}" && -f "${2}" ]] || { Usage; exit 2; }

    cc -o xFront xFront.c || { echo 1>&2 "Failed to compile xFront"; exit 3; }

    WORK="./FixedFile"
    echo 1>&2 "Making: ${WORK}"
    time cat "${1}" "${2}" > "${WORK}"

    ls -ilrt --time-style=full-iso "${1}" "${2}" "${WORK}"

    X_SIZE="$( wc -c < "${1}" )"
    echo ./xFront "${X_SIZE}" "${WORK}"
    time ./xFront "${X_SIZE}" "${WORK}"

    ls -ilrt --time-style=full-iso "${1}" "${2}" "${WORK}"

    M_CKSUM="$( cksum < "${2}" )"
    W_CKSUM="$( cksum < "${WORK}" )"

    echo 1>&2 "Old checksum: ${M_CKSUM}"
    echo 1>&2 "New checksum: ${W_CKSUM}"

    [[ "${M_CKSUM}" == "${W_CKSUM}" ]] || { echo 1>&2 "CKSUM FAIL"; exit 1; }

    echo 1>&2 "CKSUM SUCCESS"
    exit 0

これは、私のバックアップシステムのファイルを使用したテストの出力です。

Paul--) time ./xFrontTest Sudoku.tar Users.tar
Making: ./FixedFile

real    3m31.474s
user    0m0.124s
sys 0m32.996s
6302103 -rw-r--r-- 1 paul paul 6913402880 2019-12-07 21:51:38.362431068 +0000 Users.tar
6302076 -rw-r--r-- 1 paul paul    7004160 2019-12-07 21:51:38.598424089 +0000 Sudoku.tar
6302102 -rw-r--r-- 1 paul paul 6920407040 2019-12-07 22:10:24.553044325 +0000 ./FixedFile
./xFront 7004160 ./FixedFile
skEnd = 6920407040
Used 104 cycles of 67108864 buffer.

real    3m48.606s
user    0m0.012s
sys 0m27.344s
6302103 -rw-r--r-- 1 paul paul 6913402880 2019-12-07 21:51:38.362431068 +0000 Users.tar
6302076 -rw-r--r-- 1 paul paul    7004160 2019-12-07 21:51:38.598424089 +0000 Sudoku.tar
6302102 -rw-r--r-- 1 paul paul 6913402880 2019-12-07 22:14:16.838170978 +0000 ./FixedFile
Old checksum: 738098870 6913402880
New checksum: 738098870 6913402880
CKSUM SUCCESS

real    10m39.589s
user    1m32.496s
sys 1m18.960s
0
Paul_Pedant

コード。エラーメッセージはより明確になる可能性がありますが、機能的には完全です。実験により、64 MBのバッファは1 MBのバッファよりも優れていることがわかりました。

//
// xFront.c: Remove Leading bytes from a large file efficiently.

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

#define Hint(TX) fprintf (stderr, "%s\n%s\n", Usage, TX)

static char *Usage =

  "\nUsage: xFront size filename"
  "\n   size      number of bytes to be removed from the beginning of the file"
  "\n   filename  name of the file to be modified.";

// #### Structure definition for file management.

#define MAX_BUF (1 * 1024 * 1024)

typedef struct {

    size_t  szBuf;
    void *  buf;
    char *  fn;
    int     status;
    int     fd;
    int     cycles;
    off_t   r_offset;
    off_t   w_offset;
} FileManager_t;

static FileManager_t fixFM = {

    MAX_BUF,
    NULL,
    NULL,
    0,
};

static FileManager_t *pFM = & fixFM;


// #### File copying function.

void xFront (FileManager_t *pFM)

{
off_t r_offs; ssize_t r_size;
off_t w_offs; ssize_t w_size;

    while (1) {
        r_offs = lseek (pFM->fd, pFM->r_offset, SEEK_SET);
        if (r_offs < 0) { pFM->status = 1; return; }
        r_size = read (pFM->fd, pFM->buf, pFM->szBuf);
        if (r_size < 0) { pFM->status = 2; return; }
        if (r_size == 0) { pFM->status = 0; return; }
        pFM->r_offset += r_size;

        w_offs = lseek (pFM->fd, pFM->w_offset, SEEK_SET);
        if (w_offs < 0) { pFM->status = 3; return; }
        w_size = write (pFM->fd, pFM->buf, r_size);
        if (w_size != r_size) { pFM->status = 4; return; }
        pFM->w_offset += w_size;

        pFM->cycles++;
    }
    return;
}

// #### Entry function.

int main (int argc, char **argv, char **envp)

{
long int xSize;
off_t skEnd;
char *e;

    if (argc != 3) { Hint ("Missing args"); return (2); }

    // Validate the count to be removed from the front of the file.
    xSize = strtol (*(argv + 1), &e, 10);
    if (*e != '\0' || xSize < 0) { Hint ("Bad size"); return (2); }

    // Open the file for update, and check.
    pFM->fn = *(argv + 2);
    pFM->fd = open (pFM->fn, O_RDWR);
    if (pFM->fd < 0) { perror (pFM->fn); return (2); }

    // Find the file length, and maybe use a short buffer.
    skEnd = lseek (pFM->fd, (off_t) 0, SEEK_END);
    fprintf (stderr, "skEnd = %ld\n", skEnd);
    if (xSize >= skEnd) { close (pFM->fd); Hint ("Size too big"); return (2); }
    if (pFM->szBuf > skEnd) pFM->szBuf = skEnd;

    pFM->buf = malloc (pFM->szBuf);
    if (pFM->buf == NULL) {
        fprintf (stderr, "Cannot allocate %ld for file buffer\n", pFM->szBuf);
        return (1);
    }
    // Update the file in-situ.
    pFM->cycles = 0;
    pFM->r_offset = xSize;
    pFM->w_offset = 0L;
    xFront (pFM);
    fprintf (stderr, "Used %d cycles of %ld buffer.\n", pFM->cycles, pFM->szBuf);

    // We have scuffed up the file somehow.
    if (pFM->status != 0) {
        fprintf (stderr, "Copying failed with status %d\n", pFM->status);
    }

    // Trim the duplicate data from the end of the file.
    if (pFM->status == 0) {
        pFM->status = ftruncate (pFM->fd, (off_t) (skEnd - xSize));
        if (pFM->status < 0)  perror (pFM->fn);
    }

    // Tidy up.
    if (pFM->buf != NULL) free (pFM->buf);
    pFM->status = close (pFM->fd);
    if (pFM->status < 0) { perror (pFM->fn); return (2); }

    return (0);
}
0
Paul_Pedant

printfsedを使用してこれを行うことができますが、を使用せずにファイルをその場で編集することは非常に危険です一時的なコピーと この素晴らしい記事を読むのに十分にお勧めすることはできません 始める前に.

また、ファイルが大きすぎてメモリが不足している場合は失敗する可能性があることに注意してください

printf '%s\n' "$(sed '1d' test.txt)" > test.txt

そうは言っても、これはiノードを変更せずにファイルをオンザフライで機能させ、変更するはずです。

0
Leo