파일에서 줄을 삭제하는 더 빠른 방법이 있습니까(줄 번호 제공)?

파일에서 줄을 삭제하는 더 빠른 방법이 있습니까(줄 번호 제공)?

관련 질문은여기.

대용량 파일을 편집하다 보면 중간에 몇 줄을 삭제해야 하는 경우가 종종 있습니다. 삭제하려는 행을 알고 있으며 일반적으로 다음을 수행합니다.

sed "linenum1,linenum2 d" input.txt > input.temp

또는 -i 옵션을 추가하여 인라인으로 수행할 수도 있습니다. 줄번호를 알고 있는데 스트림 편집을 피하고 특정 줄만 삭제하는 명령어가 있나요? input.txt는 최대 50GB까지 가능합니다.

답변1

파일 복사본을 쓰지 않으려면 다음과 같이 파일 자체에 파일을 쓰는 것이 좋습니다.

{
  sed "$l1,$l2 d" < file
  perl -le 'truncate STDOUT, tell STDOUT'
} 1<> file

백업 복사본이 없기 때문에 위험합니다.

또는 이를 방지하려면 sedmanatwork의 아이디어 중 일부를 훔치십시오.

{
  head -n "$(($l1 - 1))"
  head -n "$(($l2 - $l1 + 1))" > /dev/null
  cat
  perl -le 'truncate STDOUT, tell STDOUT'
} < file 1<> file

첫 번째 항목을 덮어쓰기 때문에 이 문제는 여전히 개선될 수 있습니다.l1-1이 작업을 수행할 필요는 없지만 이를 피한다는 것은 perl결국 덜 효율적일 수 있는 모든 작업을 수행하는 등 프로그래밍에 더 많이 참여한다는 의미입니다.

perl -ne 'BEGIN{($l1,$l2) = ($ENV{"l1"}, $ENV{"l2"})}
    if ($. == $l1) {$s = tell(STDIN) - length; next}
    if ($. == $l2) {seek STDOUT, $s, 0; $/ = \32768; next}
    if ($. > $l2) {print}
    END {truncate STDOUT, tell STDOUT}' < file 1<> file

다음 출력에서 ​​1000000~1000050 행의 일부 타이밍을 제거합니다 seq 1e7.

  • sed -i "$l1,$l2 d" file:16.2초
  • 첫 번째 솔루션: 1.25초
  • 두 번째 솔루션: 0.057초
  • 세 번째 솔루션: 0.48초

< file모두 동일한 원칙을 따릅니다. 파일에 대한 두 개의 파일 설명자를 엽니다. 하나는 읽기 전용 모드(0)에서 약어 for를 사용하고 다른 하나는 0< file읽기-쓰기 모드(1)에서 ( will be )를 사용합니다. 이 파일 설명자는 두 가지를 가리킵니다.1<> file<> file0<> file파일 설명 열기각각 전류가 있습니다.커서 위치그들과 관련된 파일에서.

예를 들어 두 번째 솔루션에서 첫 번째 솔루션은 fd 0에서 행 데이터를 head -n "$(($l1 - 1))"읽고 해당 데이터를 fd 1에 씁니다. $l1 - 1따라서 명령이 끝나면 커서는 두 명령 사이에 있습니다.파일 설명 열기fds 0 및 1과 관련된 항목은 3행의 시작 부분에 있습니다 $l1.

그런 다음 에서 head -n "$(($l2 - $l1 + 1))" > /dev/null동일한 head줄을 읽습니다.$l2 - $l1 + 1파일 설명 열기여전히 연결된 fd 0을 통해 fd 0의 커서는 해당 줄 다음 줄의 시작 부분으로 이동합니다 $l2.

그러나 fd 1은 으로 리디렉션되었으므로 /dev/nullfd 1을 작성한 후에는 커서를 다음으로 이동하지 않습니다.파일 설명 열기{...}fd 1이 가리킵니다.

따라서 시작 시 cat커서는 다음 위치에 있습니다.파일 설명 열기fd 0이 가리키는 위치는 다음 줄의 시작 부분에 있는 $l2반면, fd 1의 커서는 여전히 $l1줄 3의 시작 부분에 있습니다. 즉, 해당 두 번째 head줄은 입력에서는 삭제를 위해 건너뛰지만 출력에서는 삭제하지 않습니다. 이제 번째 행은 그 뒤의 다음 행으로 덮어쓰여 cat지며 , 이런 식으로 계속됩니다.$l1$l2

catfd 0의 파일 끝에 도달하면 반환됩니다. 그러나 fd 1은 아직 덮어쓰이지 않은 파일의 위치를 ​​가리킵니다. 이 섹션은 사라져야 하며 이제 파일 끝으로 이동된 삭제된 줄이 차지하는 공간에 해당합니다. 우리에게 필요한 것은 현재 fd 1이 가리키는 정확한 위치에서 파일을 자르는 것입니다.

이는 ftruncate시스템 호출을 통해 수행됩니다. 불행하게도 이를 수행할 수 있는 표준 Unix 유틸리티가 없으므로 fd 1 과 관련된 현재 커서 위치를 perl제공하는 데 의존합니다. 우리는 Perl의 시스템 호출 인터페이스 를 tell STDOUT사용하여 이 오프셋에서 파일을 자릅니다 ftruncate.truncate

head세 번째 솔루션에서는 첫 번째 명령의 fd 1 쓰기를 시스템 호출로 대체합니다 lseek.

답변2

이는 사용하기에 좋은 접근 방식입니다 sed. 분명히 파일을 스트리밍하고(긴 파일에는 문제 없음) 쉽게 일반화하여 더 많은 작업을 수행할 수 있습니다. 하지만 네가 원한다면단순한파일을 편집하는 방법제자리에,가장 간단한 방법은 다음을 사용하는 것 ed입니다 ex.

(echo 10,31d; echo wq) | ed input.txt

무제한 크기(및 RAM이 허용하는 한 줄)의 파일을 처리하도록 보장되는 더 나은 접근 방식은 perl파일을 내부에서 편집하는 다음 한 줄의 코드입니다.

perl -n -i -e 'print if $. < 10 || $. > 31' input.txt

설명하다:

-n:각 줄에 스크립트를 적용합니다. 다른 출력은 생성하지 않습니다.
-i: 파일을 제자리에서 편집합니다( -i.bck백업 목적으로).
-e ...: 10~31행을 제외한 모든 행을 인쇄합니다.

답변3

50GiB를 읽고 써야 한다면~ 할 것이다무엇을 하든 오랜 시간을 투자하세요. 줄의 길이가 고정되어 있지 않거나 삭제할 줄이 어디에 있는지 알 수 있는 다른 방법이 없으면 삭제할 마지막 줄까지 파일을 읽을 수 있는 방법이 없습니다. 개행 문자만 세고 나중에 전체 블록을 복사하는 사용자 정의 프로그램이 조금 더 빠를 수도 있지만 sed(1)이것이 병목 현상이 아닐 것이라고 확신합니다. time(1)시간이 어떻게 할당되는지 이해하려면 를 사용해 보세요 .

답변4

파일을 그 자리에서 편집하려는 경우 대부분의 셸 도구는 도움이 되지 않습니다. 쓰기 위해 파일을 열 때 기존 내용을 덮어쓰지 않고 잘라내기( >) 또는 추가( )만 선택할 수 있기 때문입니다. 주목할만한 예외입니다. 바라보다>>dd파일을 제자리에서 수정하는 방법이 있나요?

export LC_ALL=C
lines_to_keep=$((linenum1 - 1))
lines_to_skip=$((linenum2 - linenum1 + 1))
deleted_bytes=$({ { head -n "$lines_to_keep"
                    head -n "$lines_to_skip" >&3;
                    cat
                  } <big_file | dd of=big_file conv=notrunc;
                } 3>&1 | wc -c)
dd if=/dev/null of=big_file bs=1 seek="$(($(wc -c <big_file) - $deleted_bytes))"

(경고: 테스트되지 않았습니다!)

관련 정보