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

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악몽을 피하십시오 .



답변

블록 크기가 작기 때문에 속도가 느립니다. 최신 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로 학사 매개 변수의 최적 값을 결정하는 방법이 있나요?


답변

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).


답변

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… 루프 장치.


답변

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

   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. 그렇지 않으면 귀하에게 제공되는 모든 잘못된 정보는 무시해야합니다.


답변