Lea el medio de un file grande

Tengo un file de 1 TB. Me gustaría leer desde byte 12345678901 hasta byte 19876543212 y poner eso en salida estándar en una máquina con 100 MB de RAM.

Puedo escribir fácilmente un script de Perl que hace esto. sysread entrega 700 MB / s (lo cual está bien), pero syswrite solo ofrece 30 MB / s. Me gustaría algo más eficiente, preferiblemente algo que está instalado en todos los sistemas Unix y que puede entregar en el order de 1 GB / s.

Mi primera idea es:

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

Pero eso no es eficiente.

Editar:

No tengo idea de cómo medí la syswrite mal. Esto entrega 3.5 GB / 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 

y evita el yes | dd bs=1024k count=10 | wc yes | dd bs=1024k count=10 | wc yes | dd bs=1024k count=10 | wc pesadilla.

Esto es lento debido al pequeño tamaño del bloque. Usando una dd GNU reciente ( coreutils v8.16 + ), la forma más simple es usar las opciones skip_bytes y count_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" 

Actualizar

opción de fullblock añadida arriba según @Gilles answer . Al principio, pensé que podría estar implícito en count_bytes , pero este no es el caso.

Los problemas mencionados a continuación son un posible problema. Si las llamadas de lectura / escritura de dd se interrumpen por algún motivo, se perderán los datos. Esto no es probable en la mayoría de los casos (las probabilidades se networkingucen un poco ya que estamos leyendo de un file y no de un conducto).


Usar un dd sin las opciones skip_bytes y count_bytes es más difícil:

 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" } 

También podría experimentar con diferentes tamaños de bloques, pero las ganancias no serán muy dramáticas. Ver – ¿Hay alguna manera de determinar el valor óptimo para el parámetro bs a dd?

bs=1 le dice a dd que lea y escriba un byte a la vez. Hay una sobrecarga para cada llamada de read y write , lo cual hace que esto sea lento. Use un tamaño de bloque más grande para un performance decente.

Cuando copys un file completo, al less en Linux, he descubierto que cp y cat son más rápidos que dd , incluso si especificas un tamaño de bloque grande.

Para copyr solo parte de un file, puede canalizar la tail hacia la head . Esto requiere GNU coreutils o alguna otra implementación que tenga head -c para copyr un número específico de bytes ( tail -c está en POSIX pero head -c no lo está). Un punto de reference rápido en Linux muestra que esto es más lento que dd , presumiblemente debido a la tubería.

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

El problema con dd es que no es confiable: puede copyr datos parciales . Por lo que yo sé, dd es seguro cuando lee y escribe en un file normal. Consulte ¿ Cuándo es adecuado dd para copyr datos? (o, cuando son leídos () y write () parciales) – pero solo mientras no sea interrumpido por una señal . Con coreutils de GNU, puede usar el indicador fullblock , pero esto no es portátil.

Otro problema con dd es que puede ser difícil encontrar un conteo de bloques que funcione, ya que tanto el número de bytes omitidos como el número de bytes transferidos deben ser un múltiplo del tamaño del bloque. Puede usar múltiples llamadas a dd : una para copyr el primer bloque parcial, una para copyr la mayor parte de bloques alineados y otra para copyr el último bloque parcial; consulte la respuesta de Graeme para un fragment de shell. Pero no te olvides de que cuando ejecutas el script, a less que fullblock flag de fullblock , debes orar para que dd copie todos los datos. dd devuelve un estado distinto de cero si una copy es parcial, por lo que es fácil detectar el error, pero no hay una forma práctica de repararlo.

POSIX no tiene nada mejor que ofrecer a nivel de shell. Mi consejo sería escribir un pequeño progtwig C de propósito especial (dependiendo de lo que implemente, puede llamarlo dd_done_right o tail_head o mini-busybox ).

Con dd :

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

Alternativamente con losetup :

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

Y luego dd , cat , … el dispositivo de bucle.

Así es como lo haces puedes hacer esto:

  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 

Eso es todo lo que es realmente necesario, no requiere mucho más. En primer lugar dd count=0 skip=1 bs=$block_size1 lseek() sobre la input de files regulares prácticamente instantáneamente. No existe la posibilidad de que se omitan datos o lo que se diga acerca de otras falsedades, simplemente puede search directamente en la position de inicio que desee. Debido a que el descriptor de file es propiedad del shell y los dd 's simplemente lo están henetworkingando, afectarán la position del cursor y, por lo tanto, puede simplemente seguirlo paso a paso. Realmente es muy simple, y no hay una herramienta estándar más adecuada para la tarea que dd .

Eso usa un tamaño de bloques de 64k que a menudo es ideal. Contrariamente a la creencia popular, los bloques grandes no hacen que dd funcione más rápido. Por otro lado, los pequeños amortiguadores tampoco son buenos. dd necesita sincronizar su hora en las llamadas al sistema para que no tenga que esperar para copyr los datos en la memory y fuera de ella, sino también para que no tenga que esperar en las llamadas al sistema. Por lo tanto, desea que se tome el time suficiente para que la próxima read() no tenga que esperar la última vez, pero no tanto que esté almacenando en un tamaño mayor que el necesario.

Entonces, el primer dd salta a la position de inicio. Eso toma cero time. Podrías llamar a cualquier otro progtwig que quisieras en ese momento para leer su código fuente y comenzaría a leer directamente en tu desplazamiento de bytes deseado. Llamo a otro dd para leer ((interval / blocksize) -1) contar bloques a stdout.

Lo último que es necesario es copyr el module (si lo hay) de la operación de split anterior. Y eso es eso.

Por cierto, no lo creo, cuando las personas expresan los hechos en su rostro sin testings. Sí, es posible que dd haga una lectura corta (aunque tales cosas no son posibles cuando se lee desde un dispositivo de bloque sano, por lo tanto, el nombre) . Tales cosas solo son posibles si no almacena correctamente en búfer un flujo dd que se lee de un dispositivo que no sea de bloque. Por ejemplo:

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

En ambos casos dd copy todos los datos. En el primer caso, es posible (aunque poco probable con cat ) que algunos de los bloques de salida que copyn dd bit equivalentes a bytes "$ num" porque dd se especifica solo para almacenar nada cuando el buffer se solicita específicamente en su línea de command. bs= representa un tamaño de bloque máximo porque el propósito de dd es i / o en time real.

En el segundo ejemplo, especifico explícitamente las lecturas de búfer de tamaño de bloque y dd salida hasta que se puedan realizar escrituras completas. Eso no afecta count= que se basa en bloques de input, pero para eso solo necesitas otro dd . Cualquier información errónea que se le dé de otra forma no debe tenerse en count.