Leia o meio de um arquivo grande

19

Eu tenho um arquivo de 1 TB. Eu gostaria de ler do byte 12345678901 para o byte 19876543212 e colocar isso na saída padrão em uma máquina com 100 MB de RAM.

Eu posso escrever facilmente um script perl que faz isso. O sysread fornece 700 MB / s (o que é bom), mas o syswrite fornece apenas 30 MB / s. Eu gostaria de algo mais eficiente, de preferência algo que é instalado em cada sistema Unix e que pode 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 pesadelo yes | dd bs=1024k count=10 | wc .

    
por Ole Tange 27.03.2014 / 16:22

4 respostas

20

Isso é lento devido ao tamanho pequeno do bloco. Usando um GNU dd recente ( coreutils v8.16 + ), a maneira mais simples é usar o skip_bytes e count_bytes opções:

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

fullblock opção adicionada acima conforme @Gilles answer . No começo eu pensei que poderia estar implícito em count_bytes , mas este não é o caso.

Os problemas mencionados são um possível problema abaixo, se dd s 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 chances são reduzidas um pouco desde que estamos lendo de um arquivo e não de um pipe).

Usar uma dd sem as opções skip_bytes e 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. Veja - Existe uma maneira de determinar o valor ideal para o parâmetro bs para dd?

    
por 27.03.2014 / 16:49
6

bs=1 informa dd para ler e escrever um byte de cada vez. Existe uma sobrecarga para cada chamada de read e 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 que cp e cat são mais rápidos que dd , mesmo se você especificar um tamanho de bloco grande .

Para copiar somente parte de um arquivo, você pode canalizar tail para head . Isto requer o kernelutils GNU ou alguma outra implementação que tenha head -c para copiar um número especificado de bytes ( tail -c é em POSIX mas head -c não é). Uma referência rápida no Linux mostra que isso é mais lento que dd , presumivelmente por causa do pipe.

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

O problema com dd é que não é confiável: pode copiar dados parciais . Tanto quanto eu sei, dd é seguro ao ler e gravar em um arquivo normal - veja Quando o dd é adequado para copiar dados? (ou, quando são lidos () e write () parciais) - mas somente enquanto não for interrompido por um sinal . Com o GNU coreutils, você pode usar o sinalizador fullblock , mas isso não é portátil.

Outro problema com 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 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 outra para copiar o último bloco parcial - consulte A resposta de Graeme para um fragmento de shell. Mas não esqueça que quando você executar o script, a menos que esteja usando o sinalizador fullblock , você precisa orar para que dd copie todos os dados. dd retorna um status diferente de zero se uma cópia for parcial, por isso é fácil detectar o erro, mas não há nenhuma maneira prática de repará-lo.

POSIX não tem nada melhor para oferecer no nível do shell. Meu conselho seria escrever um pequeno programa C com finalidade especial (dependendo exatamente do que você implementa, você pode chamá-lo de dd_done_right ou tail_head ou mini-busybox ).

    
por 28.03.2014 / 03:50
4

com dd :

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

Como alternativa, com losetup :

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

E, em seguida, dd , cat , ... o dispositivo de loop.

    
por 27.03.2014 / 21:42
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_size1 irá lseek() sobre a entrada regular de arquivos praticamente instantaneamente. Não há chance de dados perdidos ou qualquer outra inverdade sobre isso, você pode apenas procurar diretamente a sua posição inicial desejada. Como o descritor de arquivo é de propriedade do shell e os dd estão apenas herdando-o, eles afetarão a posição do cursor e, portanto, você pode simplesmente seguir as etapas. É realmente muito simples - e não há uma ferramenta padrão mais adequada à tarefa do que dd .

Isso usa um tamanho de blocos de 64k, o que geralmente é ideal. Ao contrário da crença popular, blocos maiores não tornam o dd mais rápido. Por outro lado, buffers minúsculos também não são bons. dd precisa sincronizar seu tempo nas chamadas do sistema para que não precise esperar para copiar dados na memória e sair novamente, mas também para não precisar aguardar chamadas do sistema. Então você quer levar tempo suficiente para que o próximo read() não tenha que esperar no último, mas não tanto que você esteja armazenando em buffer em tamanhos maiores do que o necessário.

Portanto, o primeiro dd pula para a posição inicial. Isso leva tempo zero . Você poderia chamar qualquer outro programa que você gostou naquele momento para ler seu stdin e ele começaria a ler diretamente no seu deslocamento de byte desejado. Eu chamo outro dd para ler os blocos de contagem ((interval / blocksize) -1) para stdout.

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

Não acredite, a propósito, quando as pessoas declaram fatos em suas faces sem evidências. Sim, é possível para dd fazer uma leitura curta (embora tais coisas não sejam possíveis ao ler de um dispositivo de bloco saudável - daí o nome) . Essas coisas só são possíveis se você não armazenar corretamente em buffer um fluxo dd que seja lido de outro 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 dd copia todos dos dados. No primeiro caso é possível (apesar de improvável com cat ) que alguns dos blocos de saída que dd copia serão bit igual a "$ num" bytes porque dd é spec'd somente para armazenar qualquer coisa quando o buffer é especificamente solicitado em sua linha de comando. bs= representa um tamanho de bloco máximo porque a finalidade de dd é a E / S em tempo real.

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

    
por 13.01.2016 / 06:00

Tags