Leia o meio de um arquivo grande

19

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.

Eu posso escrever facilmente um script perl que faça isso. O sysread fornece 700 MB / s (o que é bom), mas o syswrite fornece apenas 30 MB / s. Gostaria de algo mais eficiente, de preferência algo instalado em todos os sistemas 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:

Eu não tenho idéia de como eu medi a syswrite errado. Isso fornece 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.

Ole Tange
fonte
seu comando combs=1M iflag=skip_bytes,count_bytes
frostschutz 27/03

Respostas:

21

Isso é lento devido ao tamanho pequeno 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, conforme resposta do @Gilles . A princípio, pensei que isso poderia estar implícito count_bytes, mas esse não é o caso.

Os problemas mencionados são um possível problema 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 de um arquivo e não de um pipe).


Usar um 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 drásticos. Consulte - Existe uma maneira de determinar o valor ideal para o parâmetro bs para dd?

Graeme
fonte
@ Graeme o segundo método falhará se bsnão for um fator de skip?
Steven Penny
@StevenPenny, não sabe ao certo o que está recebendo, mas skiphá vários blocos, não bytes. Talvez você esteja confuso, pois skip_bytesé usado no primeiro exemplo, o significado skip está em bytes lá?
Graeme
Seus bsé 4,096, o que significa que você não pode ignorar com mais precisão que 4,096bytes
Steven Penny
1
@StevenPenny, é por isso que existem três execuções diferentes, ddcom a primeira e a última utilização bs=1, a fim de copiar os dados que não iniciam ou terminam em um alinhamento de bloco.
Graeme
6

bs=1diz ddpara ler e escrever um byte de cada vez. Existe uma sobrecarga para cada chamada reade write, o que torna isso lento. Use um tamanho de bloco maior para obter um desempenho decente.

Quando você copia um arquivo inteiro, pelo menos no Linux, descobri isso cpe caté mais rápido quedd , mesmo se você especificar um tamanho de bloco grande.

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

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

O problema ddé que não é confiável: ele pode copiar dados parciais . Tanto quanto sei, ddé seguro ao ler e gravar em um arquivo comum - consulte Quando o dd é adequado para copiar dados? (ou, quando são lidos () e gravados () parciais) - mas apenas desde que não sejam interrompidos por um sinal . Com o GNU coreutils, você pode usar a fullblockflag, mas isso não é portátil.

Outro problema ddé que pode ser difícil encontrar uma contagem de blocos que funcione, porque o número de bytes ignorados e o número de bytes transferidos precisam ser múltiplos do tamanho do bloco. Você pode usar várias 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 - consulte a resposta de Graeme para obter um snippet de shell. Mas não se esqueça de que quando você executa o script, a menos que esteja usando a fullblockbandeira, você precisa rezar para que ddtodos os dados sejam copiados. ddretorna um status diferente de zero se uma cópia é parcial, por isso é fácil detectar o erro, mas não há maneira prática de repará-lo.

O POSIX não tem nada melhor para 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 dd_done_rightou tail_headou mini-busybox).

Gilles 'SO- parar de ser mau'
fonte
Uau, eu nunca conheci o yes | dd bs=1024k count=10 | wcproblema antes. Desagradável.
precisa
4

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, cat... o dispositivo de loop.

frostschutz
fonte
Isso parece muito centrado no Linux. Eu preciso do mesmo código para trabalhar no AIX, FreeBSD e Solaris também.
precisa
0

É 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 requer muito mais. Em primeiro lugar, dd count=0 skip=1 bs=$block_size1irá lseek()sobre a entrada regular de arquivos praticamente instantaneamente. Não há chance de dados perdidos ou quaisquer outras mentiras contadas a respeito, basta procurar diretamente a posição inicial desejada. Como o descritor de arquivo pertence ao shell e o ddmeramente o está herdando, eles afetarão sua posição do cursor e, portanto, você poderá segui-lo em etapas. É realmente muito simples - e não há ferramenta padrão mais adequada para a tarefa do que dd.

Isso usa um tamanho de bloco de 64k, o que geralmente é ideal. Ao contrário da crença popular, tamanhos maiores de blocos não tornam o ddtrabalho mais rápido. Por outro lado, tampões 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 na memória e para fora novamente, mas também para que não precise esperar nas chamadas do sistema. Portanto, você quer que demore tempo suficiente para que o próximo read()não precise esperar pelo último, mas não tanto que você esteja armazenando buffer em tamanhos maiores do que o necessário.

Então o primeiro ddpula para a posição inicial. Isso leva tempo zero . Você poderia ligar para qualquer outro programa que você gostasse naquele momento para ler o stdin e ele começaria a ler diretamente no deslocamento de bytes desejado. Eu chamo outro ddpara ler ((interval / blocksize) -1)blocos de contagem para stdout.

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

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

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

Nos dois casos, ddcopia todos os dados. No primeiro caso, é possível (embora improvável cat) que alguns dos blocos de saída copiados tenham ddbits iguais a "$ num" bytes porque ddsão especificados apenas para armazenar em buffer qualquer coisa quando o buffer for solicitado especificamente em seu comando. linha. bs=representa um tamanho máximo de bloco porque o objetivo de ddé a E / S em tempo real.

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

mikeserv
fonte