파일을 제자리에서 수정하는 방법이 있나요?

파일을 제자리에서 수정하는 방법이 있나요?

나는 다소 큰 파일(35Gb)을 가지고 있고 이 파일을 내부에서 필터링하고 싶습니다(예: 다른 파일을 위한 디스크 공간이 충분하지 않음). 특히 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년의 원래 구현에는 없었고 POSIX sh리디렉션 연산자(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개선되었는지는 확실하지 않습니다.) camh의 유능한 탐색 답변보다 훨씬 짧습니다. 내 생각에는 당신이 나보다 Perl을 더 좋아하지 않는 한 그것은 Stéphane의 것보다 조금 더 명확합니다. 누군가가 유용하다고 생각하기를 바랍니다.

동일한 작업을 수행하는 또 다른 방법은 출력을 사용할 수 있는 현재 오프셋을 보고하는 lseek(2)의 실행 가능한 버전입니다./usr/bin/잘림, 일부 Linux 시스템에서 제공됩니다.

관련 정보