큰 파일의 중간을 읽으십시오


19

1TB 파일이 있습니다. 바이트 12345678901에서 바이트 19876543212로 읽고 100MB RAM이있는 컴퓨터의 표준 출력에 저장하고 싶습니다.

이를 수행하는 펄 스크립트를 쉽게 작성할 수 있습니다. sysread는 700MB / s (정상)를 제공하지만 syswrite는 30MB / s 만 제공합니다. 모든 유닉스 시스템에 설치되어 1GB / s 정도의 속도로 전달할 수있는보다 효율적인 것을 원합니다.

나의 첫번째 아이디어는 :

dd if=1tb skip=12345678901 bs=1 count=$((19876543212-12345678901))

그러나 그것은 효율적이지 않습니다.

편집하다:

내가 어떻게 syswrite를 잘못 측정했는지 전혀 모른다. 이는 3.5GB / s를 제공합니다.

perl -e 'sysseek(STDIN,shift,0) || die; $left = shift; \
         while($read = sysread(STDIN,$buf, ($left > 32768 ? 32768 : $left))){ \
            $left -= $read; syswrite(STDOUT,$buf);
         }' 12345678901 $((19876543212-12345678901)) < bigfile

yes | dd bs=1024k count=10 | wc악몽을 피하십시오 .


와 명령bs=1M iflag=skip_bytes,count_bytes
frostschutz

답변:


21

블록 크기가 작기 때문에 속도가 느립니다. 최신 GNU dd( coreutils v8.16 + )를 사용하는 가장 간단한 방법은 skip_bytescount_bytes옵션 을 사용하는 것입니다 .

in_file=1tb

start=12345678901
end=19876543212
block_size=4096

copy_size=$(( $end - $start ))

dd if="$in_file" iflag=skip_bytes,count_bytes,fullblock bs="$block_size" \
  skip="$start" count="$copy_size"

최신 정보

fullblock옵션은 @Gilles answer에 따라 추가되었습니다 . 처음에는에 의해 암시 될 수 있다고 생각 count_bytes했지만 이것은 사실이 아닙니다.

언급 된 문제는 아래의 잠재적 인 문제 dd입니다. 어떤 이유로 든 읽기 / 쓰기 호출이 중단되면 데이터가 손실됩니다. 이것은 대부분의 경우에는 가능성이 낮습니다 (파이프가 아닌 파일에서 읽으므로 이상한 점이 줄어 듭니다).


및 옵션 dd없이는 사용하기 가 더 어렵습니다.skip_bytescount_bytes

in_file=1tb

start=12345678901
end=19876543212
block_size=4096

copy_full_size=$(( $end - $start ))
copy1_size=$(( $block_size - ($start % $block_size) ))
copy2_start=$(( $start + $copy1_size ))
copy2_skip=$(( $copy2_start / $block_size ))
copy2_blocks=$(( ($end - $copy2_start) / $block_size ))
copy3_start=$(( ($copy2_skip + $copy2_blocks) * $block_size ))
copy3_size=$(( $end - $copy3_start ))

{
  dd if="$in_file" bs=1 skip="$start" count="$copy1_size"
  dd if="$in_file" bs="$block_size" skip="$copy2_skip" count="$copy2_blocks"
  dd if="$in_file" bs=1 skip="$copy3_start" count="$copy3_size"
}

다른 블록 크기로 실험 해 볼 수도 있지만 이득은 그리 크지 않습니다. 참조 - DD로 학사 매개 변수의 최적 값을 결정하는 방법이 있나요?


@Graeme bs은 요인이 아닌 경우 두 번째 방법이 실패하지 skip않습니까?
Steven Penny

@StevenPenny, 당신이 얻는 것이 확실 skip하지 않지만 바이트가 아닌 많은 블록입니다. 이후 아마 당신은 혼란 skip_bytes첫 번째 예제의 의미로 사용 skip 된다 이 바이트?
Graeme

귀하의 bsIS 4,096더 정확하게 건너 뛸 것을 할 수 있다는 것을 의미, 4,096바이트
스티븐 페니

1
@StevenPenny, 이것이 블록 정렬에서 시작하거나 끝나지 않는 데이터를 복사하기 위해 dd첫 번째와 마지막 으로 세 가지 실행이있는 이유 bs=1입니다.
Graeme

6

bs=1dd한 번에 한 바이트 씩 읽고 쓰도록 지시 합니다. 각각 readwrite호출에 대한 오버 헤드가 있으므로 속도가 느려집니다. 적절한 성능을 위해 더 큰 블록 크기를 사용하십시오.

당신은 전체 파일을 복사 할 때, 리눅스에서 적어도, 나는 것으로 나타났습니다 cp하고 cat빠르게보다dd 크기가 큰 블록 크기를 지정하는 경우에도,.

파일의 일부만 복사하려면 tail로 파이프하십시오 head. 이를 위해서는 GNU coreutils 또는 head -c지정된 수의 바이트를 복사 해야하는 다른 구현이 필요 합니다 ( tail -cPOSIX에는 있지만 head -c그렇지 않음). 리눅스에서의 빠른 벤치 마크는 dd아마도 파이프 때문에 이보다 느리다는 것을 보여줍니다 .

tail -c $((2345678901+1)) | head -c $((19876543212-2345678901))

문제 dd신뢰할 수 없다는 것입니다 . 부분 데이터를 복사 할 수 있습니다 . 내가 아는 한, dd일반 파일을 읽고 쓸 때 안전합니다. dd는 언제 데이터 복사에 적합합니까?를 참조하십시오 . (또는 read () 및 write () partial 인 경우)signal에 의해 중단되지 않는 한에만 가능 합니다. GNU coreutils를 사용하면 fullblock플래그를 사용할 수 있지만 이식성이 없습니다.

또 다른 문제점 dd은 건너 뛴 바이트 수와 전송 된 바이트 수가 블록 크기의 배수 여야하기 때문에 작동하는 블록 수를 찾기가 어렵다는 것입니다. 여러 번의 호출을 사용할 수 있습니다 dd. 하나는 첫 번째 부분 블록을 복사하고, 하나는 정렬 된 블록을 대량으로 복사하고, 다른 하나는 마지막 부분 블록을 복사합니다.- 쉘 스 니펫에 대한 Graeme의 답변 을 참조하십시오 . 그러나 스크립트를 실행할 때 fullblock플래그를 사용하지 않는 한 dd모든 데이터를 복사 하도록기도해야 함을 잊지 마십시오 . dd복사본이 부분적인 경우 0이 아닌 상태를 반환하므로 오류를 쉽게 감지 할 수 있지만 실제로 복구 할 수있는 방법은 없습니다.

POSIX는 쉘 수준에서 제공하는 것이 더 좋습니다. 내 조언은 작은 특수 목적의 C 프로그램을 작성하는 것 (에 정확히 구현 무엇을 따라, 당신은 그것을 호출 할 수 있습니다 dd_done_right또는 tail_head이상 mini-busybox).


와우, 나는 yes | dd bs=1024k count=10 | wc전에 문제를 몰랐다 . 추잡한.
Ole Tange

4

dd:

dd if=1tb skip=12345678901 count=$((19876543212-12345678901)) bs=1M iflags=skip_bytes,count_bytes

다른 방법으로 losetup:

losetup --find --show --offset 12345678901 --sizelimit $((19876543212-12345678901))

그리고 dd, cat... 루프 장치.


이것은 리눅스 중심으로 보인다. AIX, FreeBSD 및 Solaris에서도 작동하려면 동일한 코드가 필요합니다.
Ole Tange

0

이것은 당신이 이것을 할 수있는 방법입니다 :

   i=$(((t=19876543212)-(h=12345678901)))
   { dd count=0 skip=1 bs="$h"
     dd count="$((i/(b=64*1024)-1))" bs="$b"
     dd count=1 bs="$((i%b))"
   } <infile >outfile

그게 정말 필요한 전부입니다-훨씬 더 필요하지 않습니다. 첫번째 장소에있는 dd count=0 skip=1 bs=$block_size1lseek()거의 순간적으로 일반 파일 입력을 이상. 데이터누락 되거나 다른 사실이 알려지지 않은 경우 원하는 시작 위치로 직접 검색 할 수 있습니다. 파일 디스크립터는 쉘에 의해 소유되고의 파일 디스크립터는 dd이를 단순히 상속하기 때문에 커서 위치에 영향을 미치므로 단계적으로 수행 할 수 있습니다. 실제로는 매우 단순하며, 작업에 더 적합한 표준 도구는 없습니다 dd.

그것은 종종 이상적인 64k 블록 크기를 사용합니다. 대중적인 믿음과는 달리, 블록 크기가 클수록 dd작업 속도가 빨라 지지 않습니다 . 반면에 작은 버퍼도 좋지 않습니다. dd시스템 호출에서 시간을 동기화하여 데이터를 메모리에 복사하고 다시 기다릴 필요가 없으며 시스템 호출을 기다릴 필요가 없습니다. 따라서 다음 시간이 read()마지막에 기다릴 필요가 없지만 필요한 것보다 큰 크기로 버퍼링하는 데 시간이 오래 걸리기를 원합니다 .

따라서 첫 번째 dd는 시작 위치로 건너 뜁니다. 시간 이 걸리지 않습니다 . 그 시점에서 원하는 다른 프로그램을 호출하여 stdin을 읽으면 원하는 바이트 오프셋에서 직접 읽기 시작합니다. 다른 것을 호출 하여 stdout에 카운트 블록을 dd읽습니다 ((interval / blocksize) -1).

마지막으로 필요한 것은 이전 나누기 연산 의 계수 (있는 경우) 를 복사하는 것입니다 . 그게 다야.

그런데 사람들이 증거없이 자신의 얼굴에 사실을 진술 할 때 그것을 믿지 마십시오. 그렇습니다 dd. 짧은 읽기 가 가능합니다 (정상적인 블록 장치에서 읽을 때는 그러한 것이 불가능합니다) . 이러한 것들은 dd블록 디바이스 이외에서 읽은 스트림을 올바르게 버퍼링하지 않은 경우에만 가능합니다 . 예를 들면 다음과 같습니다.

cat data | dd bs="$num"    ### incorrect
cat data | dd ibs="$PIPE_MAX" obs="$buf_size"   ### correct

두 경우 모두 모든 데이터를 dd복사 합니다 . 첫 번째 경우는 가능하다 (으로 거의 발생하지 않지만 cat) , 출력 블록의 어떤 것이 dd오므 아웃 사본이 동일 "$ NUM을"비트 바이트 ddspec'd된다 전혀 아무것도로 버퍼 구체적으로는 - 명령을 요청할 때 선. bs=나타내는 최대 때문에 블록 크기를 목적 의이 dd실시간으로 I / O이다.

두 번째 예에서는 출력 블록 크기를 명시 적으로 지정하고 dd전체 쓰기가 완료 될 때까지 버퍼를 읽습니다. count=입력 블록을 기반으로하는 것은 영향 을 미치지 않지만 다른 블록이 필요합니다 dd. 그렇지 않으면 귀하에게 제공되는 모든 잘못된 정보는 무시해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.