Leer el medio de un archivo grande


19

Tengo un archivo de 1 TB. Me gustaría leer del byte 12345678901 al byte 19876543212 y ponerlo en la salida estándar en una máquina con 100 MB de RAM.

Puedo escribir fácilmente un script en perl que haga esto. sysread entrega 700 MB / s (lo cual está bien), pero syswrite solo entrega 30 MB / s. Me gustaría algo más eficiente, preferiblemente algo que esté instalado en todos los sistemas Unix y que pueda entregarse en el orden 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 midí syswrite mal. Esto ofrece 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 la yes | dd bs=1024k count=10 | wcpesadilla.


su comando conbs=1M iflag=skip_bytes,count_bytes
frostschutz

Respuestas:


21

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

fullblockopción agregada arriba según la respuesta de @Gilles . Al principio pensé que podría estar implícito count_bytes, pero este no es el caso.

Los problemas mencionados son un problema potencial a continuación, si ddlas llamadas de lectura / escritura se interrumpen por algún motivo, los datos se perderán. Esto no es probable en la mayoría de los casos (las probabilidades se reducen un poco ya que estamos leyendo desde un archivo y no desde una tubería).


Usar un ddsin las opciones skip_bytesy count_byteses 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 bloque, 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?


@Graeme no fallará el segundo método si bsno es un factor de skip?
Steven Penny

@StevenPenny, no estoy seguro de a qué te refieres, pero skiphay varios bloques, no bytes. ¿Quizás estás confundido ya que skip_bytesse usa en el primer ejemplo, el significado skip está en bytes allí?
Graeme

Su bses 4,096, lo que significa que no puede omitir con mayor precisión esos 4,096bytes
Steven Penny

1
@StevenPenny, esta es la razón por la cual hay tres ejecuciones diferentes ddcon el primer y el último uso bs=1para copiar los datos que no comienzan o terminan en una alineación de bloque.
Graeme

6

bs=1le dice ddque lea y escriba un byte a la vez. Hay una sobrecarga para cada uno ready writellamada, lo que hace que este lento. Use un tamaño de bloque más grande para un rendimiento decente.

Cuando copia un archivo completo, al menos en Linux, lo he encontrado cpy cates más rápido quedd , incluso si especifica un tamaño de bloque grande.

Para copiar sólo una parte de un archivo, se puede canalizar taila head. Esto requiere GNU coreutils o alguna otra implementación que tenga head -cque copiar un número específico de bytes ( tail -cestá en POSIX pero head -cno lo está). Un punto de referencia 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 ddes que no es confiable: puede copiar datos parciales . Hasta donde yo sé, ddes seguro al leer y escribir en un archivo normal. Consulte ¿ Cuándo es adecuado dd para copiar datos? (o, cuando se lee () y escribe () parcial) , pero solo mientras no sea interrumpido por una señal . Con GNU coreutils, puede usar la fullblockbandera, pero esto no es portátil.

Otro problema ddes que puede ser difícil encontrar un recuento de bloques que funcione, porque 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 varias llamadas a dd: una para copiar el primer bloque parcial, una para copiar la mayor parte de los bloques alineados y otra para copiar el último bloque parcial; consulte la respuesta de Graeme para obtener un fragmento de shell. Pero no olvides que cuando ejecutas el script, a menos que estés usando la fullblockbandera, debes rezar para que ddse copien todos los datos. dddevuelve un estado distinto de cero si una copia 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 programa en C para fines especiales pequeña (dependiendo exactamente lo que se implementa, se puede llamar así dd_done_righto tail_heado mini-busybox).


Wow, nunca supe el yes | dd bs=1024k count=10 | wcproblema antes. Asqueroso.
Ole Tange

4

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.


Esto parece muy centrado en Linux. También necesito el mismo código para trabajar en AIX, FreeBSD y Solaris.
Ole Tange

0

Así es como 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 entrada de archivos regulares prácticamente de forma instantánea. No hay posibilidad de que se pierdan datos o de cualquier otra falsedad que se les diga, solo puede buscar directamente a su posición de inicio deseada. Debido a que el descriptor de archivo es propiedad del shell y los dd's simplemente lo heredan, afectarán la posición del cursor y, por lo tanto, puede seguirlo paso a paso. Realmente es muy simple, y no existe una herramienta estándar más adecuada para la tarea que dd.

Utiliza un tamaño de bloque de 64k que a menudo es ideal. Contrariamente a la creencia popular, los bloques de mayor tamaño no hacen que el ddtrabajo sea más rápido. Por otro lado, los amortiguadores pequeños tampoco son buenos. ddnecesita sincronizar su hora en las llamadas al sistema para que no tenga que esperar para copiar datos en la memoria y volver a salir, sino también para que no tenga que esperar en las llamadas del sistema. Por lo tanto, desea que tome el tiempo suficiente para que el siguiente read()no tenga que esperar al último, pero no tanto como para almacenar en búfer en tamaños más grandes de lo necesario.

Entonces el primero ddsalta a la posición inicial. Eso lleva cero tiempo. Puede llamar a cualquier otro programa que le haya gustado en ese momento para leer su stdin y comenzaría a leer directamente en el desplazamiento de bytes deseado. Llamo a otro ddpara leer los ((interval / blocksize) -1)bloques de conteo para stdout.

Lo último que es necesario es copiar el módulo (si lo hay) de la operación de división anterior. Y eso es eso.

No lo creas, por cierto, cuando las personas declaran hechos sin evidencia. Sí, es posible ddhacer una lectura corta (aunque tales cosas no son posibles cuando se lee desde un dispositivo de bloque sano , de ahí el nombre) . Tales cosas solo son posibles si no almacena correctamente una ddsecuencia que se lee desde otro dispositivo que no sea un bloque. Por ejemplo:

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

En ambos casos, ddcopia todos los datos. En el primer caso, es posible (aunque improbable cat) que algunos de los bloques de salida que se ddcopian serán iguales a "$ num" bytes porque solodd se especifica para almacenar algo cuando el buffer se solicita específicamente en su comando. línea. representa un tamaño de bloque máximo porque el propósito de es E / S en tiempo real.bs=dd

En el segundo ejemplo, especifico explícitamente el tamaño de bloque de salida y las ddlecturas de almacenamiento intermedio hasta que se puedan realizar escrituras completas. Eso no afecta lo count=que se basa en bloques de entrada, pero para eso solo necesita otro dd. Cualquier información errónea que se le proporcione de otra manera no se tendrá en cuenta.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.