
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 | wc
pesadelo.
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_bytes
e 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
fullblock
opçã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 dd
as 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 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. Ver -Existe uma maneira de determinar o valor ideal para o parâmetro bs para dd?
Responder2
bs=1
diz dd
para ler e escrever um byte por vez. Há uma sobrecarga para cada read
chamada 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 quecp
e cat
sã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 tail
para head
. Isso requer GNU coreutils ou alguma outra implementação que precise head -c
copiar um número especificado de bytes ( tail -c
está em POSIX, mas head -c
nã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 fullblock
sinalizador, 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 fullblock
flag, você precisa rezar para que dd
todos os dados sejam copiados. dd
retorna 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_right
ou tail_head
ou 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
... cat
o 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_size1
será 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 dd
o trabalho 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 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 dd
salta 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 dd
para 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 dd
fazer 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 dd
fluxo 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 dd
cópiastodosdos dados. No primeiro caso é possível(embora improvável com cat
)que alguns dos blocos de saída que são dd
copiados terão bits iguais a "$num" bytes porque dd
sã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 dd
as 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.