나는 다소 큰 파일(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
잠시 동안 (혼자) 열 때 잘림 현상이 없습니다. 종료 후 일부 줄을 삭제하고 이제 내부 어딘가를 가리키면 해당 지점 이후의 콘텐츠를 삭제해야 합니다. 따라서 이 명령은 현재 위치( 에서 반환됨)에서 파일을 자릅니다.bigfile
cat
stdin
grep
stdout
bigfile
perl
truncate STDOUT
tell STDOUT
(이것은 cat
GNU를 위한 것입니다 grep
. 그렇지 않으면 stdin과 stdout이 동일한 파일을 가리키는 경우 불평할 것입니다.)
1 글쎄요, 비록 <>
70년대 후반부터 Bourne 쉘에 있었지만 원래는문서화되지 않았으며 올바르게 구현되지 않았습니다.. ash
1989년의 원래 구현에는 없었고 POSIX sh
리디렉션 연산자(90년대 초반부터 POSIX의 sh
기반 이었음 ksh88
)임에도 불구하고 FreeBSD에 추가되지 않아 sh
이식성이 뛰어났습니다.15세아마도 더 정확할 것입니다. 또한 지정되지 않은 경우 기본 파일 설명자는 모든 셸에서 0이지만 ksh93
2010년 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 시스템에서 제공됩니다.