나는 최근에 물었다.하나의 질문다른 특정 문자 뒤에 나타나는 개행 문자를 제거하는 방법에 대해 설명합니다.
Unix 텍스트 처리 도구는 매우 강력하지만 거의 모두 텍스트 줄을 처리하므로 입력이 사용 가능한 메모리에 맞는 대부분의 경우 괜찮습니다.
그러나 개행 문자가 포함되지 않은 대용량 파일에서 일련의 텍스트를 바꾸려면 어떻게 해야 합니까?
예를 들어 입력을 한 줄씩 읽지 않고 <foobar>
다음으로 바꾸시겠습니까? \n<foobar>
(한 줄만 있고 길이가 2.5G이기 때문입니다.)
답변1
이런 문제에 직면했을 때 가장 먼저 생각한 것은 기록 구분 기호를 변경하는 것입니다. 대부분의 도구에서 이는 \n
기본적으로 설정되어 있지만 변경할 수 있습니다. 예를 들어:
진주
perl -0x3E -pe 's/<foobar>/\n$&/' file
설명하다
-0
: 입력 레코드 구분 기호를 주어진 문자로 설정합니다.16진수 값. 이 경우에는>
16진수 값 으로 설정했습니다3E
. 일반적인 형식은 입니다-0xHEX_VALUE
. 이것은 라인을 관리 가능한 덩어리로 나누는 트릭일 뿐입니다.-pe
: 주어진 스크립트를 적용한 후 각 입력 줄을 인쇄합니다-e
.s/<foobar>/\n$&/
: 간단한 교체.$&
이 경우 일치하는 콘텐츠는 입니다<foobar>
.
앗
awk '{gsub(/foobar>/,"\n<foobar>");printf "%s",$0};' RS="<" file
설명하다
RS="<"
: 입력 레코드 구분 기호를 로 설정합니다>
.gsub(/foobar>/,"\n<foobar>")
foobar>
:의 모든 경우를 로 바꿉니다\n<foobar>
.RS
로 설정되어 있기 때문에 입력 파일에서<
모든 것이 제거되므로(이것이 작동 방식입니다) 일치 (없이 )하고 로 바꿔야 합니다 .<
awk
foobar>
<
\n<foobar>
printf "%s",$0
: 교체 후 현재 "줄"을 인쇄합니다.$0
은 현재 레코드awk
이므로 이전 레코드가 모두 유지됩니다<
.
다음을 사용하여 생성된 2.3GB 단일 행 파일에서 테스트했습니다.
for i in {1..900000}; do printf "blah blah <foobar>blah blah"; done > file
for i in {1..100}; do cat file >> file1; done
mv file1 file
awk
perl
사용된 메모리 양은 무시할 수 있습니다 .
답변2
게사르 (일반 검색 및 바꾸기)바로 이 목적에 매우 유용한 도구입니다.
이 질문에 대한 대부분의 답변은 레코드 기반 도구와 다양한 트릭을 사용하여 문제에 맞게 조정합니다. 예를 들어 기본 레코드 구분 기호를 입력에서 자주 발생하는 것으로 가정되는 문자로 전환하여 각 레코드가 처리하기에 너무 크지 않도록 하는 등의 작업을 수행합니다.
대부분의 경우 이는 완벽하게 괜찮고 읽기에도 좋습니다. 나는 , bourne shell 과 같이 쉽게 사용할 수 있는 도구를 사용하여 쉽고 효과적으로 awk
해결할 수 있는 tr
문제를 좋아합니다 sed
.
이진 검색을 수행하고 임의의 내용이 포함된 임의의 대용량 파일을 바꾸는 것은 이러한 표준 UNIX 도구에 적합하지 않습니다.
여러분 중 일부는 이것이 부정 행위라고 생각할 수도 있지만 작업에 적합한 도구를 사용하는 것이 어떻게 잘못될 수 있는지 모르겠습니다. 이 경우 gsar
권한을 가지고 호출되는 C 프로그램입니다.일반 공중 라이선스 v2, 그래서 두 버전 모두에 이 매우 유용한 도구에 대한 패키지가 없다는 사실에 매우 놀랐습니다.파푸아 뉴기니,빨간 모자,도 아니다우분투.
gsar
바이너리 변형 사용Boyer-Moore 문자열 검색 알고리즘.
사용법은 매우 간단합니다.
gsar -F '-s<foobar>' '-r:x0A<foobar>'
이는 -F
"필터" 모드, 즉 stdin
읽기 및 쓰기를 의미합니다 stdout
. 파일을 조작하는 방법도 있습니다. -s
검색 문자열과 -r
대체 문자열을 지정합니다. 콜론 표기법을 사용하여 임의 바이트 값을 지정할 수 있습니다.
대소문자를 구분하지 않는 패턴( -i
)은 지원되지만 알고리즘은 검색 문자열의 길이를 사용하여 검색을 최적화하므로 정규식은 지원되지 않습니다.
이 도구 는 grep
.gsar -b
gsar -l
grep -l
wc
이 도구는 다음에 의해 작성되었습니다.토모데 샤베르그(초기) 및한스 피터 베른(개선하다).
답변3
대상 문자열과 교체 문자열의 길이가 같은 좁은 경우에는메모리 맵구조하러 올 수 있습니다. 이는 현장에서 교체를 수행해야 하는 경우 특히 유용합니다. 기본적으로 파일을 프로세스의 가상 메모리에 매핑하고 64비트 주소 지정을 위한 주소 공간이 매우 큽니다.파일이 반드시 한꺼번에 물리적 메모리에 매핑되는 것은 아닙니다., 따라서 머신에서 사용할 수 있는 실제 메모리 크기의 몇 배에 달하는 파일을 처리할 수 있습니다.
다음은 Python 예제입니다 foobar
.XXXXXX
#! /usr/bin/python
import mmap
import contextlib
with open('test.file', 'r+') as f:
with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_WRITE)) as m:
pos = 0
pos = m.find('foobar', pos)
while pos > 0:
m[pos: pos+len('XXXXXX')] = 'XXXXXX'
pos = m.find('foobar', pos)
답변4
awk는 연속 레코드에서 작동합니다. 레코드 구분 기호로 모든 문자를 사용할 수 있습니다(많은 구현에서 널 바이트 제외). 일부 구현에서는 레코드 구분 기호로 임의의 정규식(빈 문자열과 일치하지 않음)을 지원하지만 이는 레코드 구분 기호가 저장되기 전에 각 레코드의 끝에서 잘리기 때문에 문제가 될 수 있습니다 $0
(GNU awk는 변수를 RT
레코드 구분 기호로 설정합니다). 현재 레코드의 끝에서 제거되었습니다). 기본적으로 개행 문자로 설정되고 입력 레코드 구분 기호와 독립적으로 설정되는 print
출력 레코드 구분 기호로 출력을 종료 합니다 .ORS
RS
awk -v RS=, 'NR==1 {printf "input up to the first comma: %s\n", $0}'
sort
줄 바꿈( , , ...) 을 교체하여 다른 도구의 레코드 구분 기호로 다른 문자를 효과적으로 선택할 수 있습니다 .sed
tr
tr '\n,' ',\n' |
sed 's/foo/bar/' |
sort |
tr '\n,' ',\n'
많은 GNU 텍스트 유틸리티는 개행 문자 대신 널 바이트를 구분 기호로 사용하는 것을 지원합니다.