Leia o meio de um arquivo grande

Leia o meio de um arquivo grande

Eu tenho um arquivo de 1 TB. Gostaria de ler do byte 12345678901 ao byte 19876543212 e colocá-lo na saída padrão em uma máquina com 100 MB de RAM.

Posso escrever facilmente um script Perl que faça isso. O sysread oferece 700 MB/s (o que é bom), mas o syswrite oferece apenas 30 MB/s. Eu gostaria de algo mais eficiente, de preferência algo que esteja instalado em todo sistema Unix e que possa entregar na ordem de 1 GB/s.

Minha primeira ideia é:

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

Mas isso não é eficiente.

Editar:

Não tenho ideia de como medi o syswrite errado. Isso oferece 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

e evita o yes | dd bs=1024k count=10 | wcpesadelo.

Responder1

Isso é lento devido ao pequeno tamanho do bloco. Usando um GNU recente dd(coreutils v8.16 +), a maneira mais simples é usar as opções skip_bytese 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"

Atualizar

fullblockopção adicionada acima conformeResposta do @Gilles. A princípio pensei que poderia estar implícito em count_bytes, mas não é o caso.

Os problemas mencionados são um problema potencial abaixo. Se ddas chamadas de leitura/gravação forem interrompidas por qualquer motivo, os dados serão perdidos. Isso não é provável na maioria dos casos (as probabilidades são um pouco reduzidas, pois estamos lendo um arquivo e não um canal).


Usar a ddsem as opções skip_bytese count_bytesé mais 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"
}

Você também pode experimentar diferentes tamanhos de bloco, mas os ganhos não serão muito dramáticos. Ver -Existe uma maneira de determinar o valor ideal para o parâmetro bs para dd?

Responder2

bs=1diz ddpara ler e escrever um byte por vez. Há uma sobrecarga para cada readchamada write, o que torna isso lento. Use um tamanho de bloco maior para um desempenho decente.

Quando você copia um arquivo inteiro, pelo menos no Linux, descobri quecpe catsão mais rápidos do quedd, mesmo se você especificar um tamanho de bloco grande.

Para copiar apenas parte de um arquivo, você pode canalizar tailpara head. Isso requer GNU coreutils ou alguma outra implementação que precise head -ccopiar um número especificado de bytes ( tail -cestá em POSIX, mas head -cnão está). Um benchmark rápido no Linux mostra que isso é mais lento que o dd, provavelmente por causa do pipe.

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

O problema ddé quenão é confiável: pode copiar dados parciais. Pelo que eu sei, ddé seguro ler e gravar em um arquivo normal - consulteQuando o dd é adequado para copiar dados? (ou, quando read() e write() são parciais)- masapenas enquanto não for interrompido por um sinal. Com GNU coreutils, você pode usar o fullblocksinalizador, mas isso não é portátil.

Outro problema ddé que pode ser difícil encontrar uma contagem de blocos que funcione, porque tanto o número de bytes ignorados quanto o número de bytes transferidos precisam ser um múltiplo do tamanho do bloco. Você pode usar múltiplas chamadas para dd: uma para copiar o primeiro bloco parcial, uma para copiar a maior parte dos blocos alinhados e uma para copiar o último bloco parcial — vejaA resposta de Graemepara um trecho de shell. Mas não se esqueça que ao executar o script, a menos que esteja usando o fullblockflag, você precisa rezar para que ddtodos os dados sejam copiados. ddretorna um status diferente de zero se uma cópia for parcial, portanto é fácil detectar o erro, mas não há uma maneira prática de repará-lo.

O POSIX não tem nada melhor a oferecer no nível do shell. Meu conselho seria escrever um pequeno programa C para fins especiais (dependendo exatamente do que você implementa, você pode chamá-lo de dd_done_rightou tail_headou mini-busybox).

Responder3

Com dd:

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

Alternativamente com losetup:

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

E então dd... cato dispositivo de loop.

Responder4

É assim que você pode fazer isso:

   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

Isso é tudo o que é realmente necessário – não exige muito mais. Em primeiro lugar, a entrada regular de arquivos dd count=0 skip=1 bs=$block_size1será lseek()praticamente instantânea. Não há chance dedados perdidosou quaisquer outras inverdades que sejam ditas sobre isso, você pode simplesmente procurar diretamente a posição inicial desejada. Como o descritor de arquivo pertence ao shell e os dd's estão apenas herdando-o, eles afetarão a posição do cursor e, portanto, você poderá executá-lo em etapas. É realmente muito simples - e não existe ferramenta padrão mais adequada para a tarefa do que o dd.

Isso usa um tamanho de bloco de 64k, que geralmente é ideal. Ao contrário da crença popular, blocos maiores não tornam ddo trabalho mais rápido. Por outro lado, buffers minúsculos também não são bons. ddprecisa sincronizar seu tempo nas chamadas do sistema para que não precise esperar a cópia dos dados para a memória e para fora novamente, mas também para que não precise esperar pelas chamadas do sistema. Portanto, você deseja que leve tempo suficiente para que o próximo read()não precise esperar pelo último, mas não tanto a ponto de armazenar em buffer em tamanhos maiores do que o necessário.

Então o primeiro ddsalta para a posição inicial. Isso levazerotempo. Você poderia chamar qualquer outro programa de sua preferência para ler seu stdin e ele começaria a ler diretamente no deslocamento de bytes desejado. chamo outro ddpara ler((interval / blocksize) -1)conte blocos para stdout.

A última coisa necessária é copiar o módulo(caso existam)da operação de divisão anterior. E é isso.

A propósito, não acredite quando as pessoas declaram fatos sem evidências. Sim, é possível ddfazer uma breve leitura(embora tais coisas não sejam possíveis ao ler a partir de um dispositivo de bloco íntegro - daí o nome). Essas coisas só são possíveis se você não armazenar corretamente em buffer um ddfluxo que é lido de outro dispositivo que não seja um dispositivo de bloco. Por exemplo:

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

Em ambos os casos ddcópiastodosdos dados. No primeiro caso é possível(embora improvável com cat)que alguns dos blocos de saída que são ddcopiados terão bits iguais a "$num" bytes porque ddsão especificadosapenaspara armazenar qualquer coisa em buffer quando o buffer for solicitado especificamente em sua linha de comando. bs=representa ummáximotamanho do bloco porque opropósitoof ddé E/S em tempo real.

No segundo exemplo, especifico explicitamente o tamanho do bloco de saída e ddas leituras dos buffers até que gravações completas possam ser feitas. Isso não afeta count=o que é baseado em blocos de entrada, mas para isso você só precisa de outro arquivo dd. Qualquer desinformação que lhe seja fornecida de outra forma deve ser desconsiderada.

informação relacionada