Melhor maneira de remover bytes desde o início de um arquivo?

62

Hoje eu tive que remover os primeiros 1131 bytes de um arquivo de texto / binário de 800 MB, um despejo de subversão filtrado que estou procurando por um novo repositório. Qual é a melhor forma de fazer isso?

Para começar, tentei

dd bs=1 skip=1131 if=filtered.dump of=trimmed.dump

mas após o salto, ele copia o restante do arquivo, um byte de cada vez, ou seja, muito lentamente. No final, resolvi que precisava de 405 bytes para arredondar até três blocos de 512 que eu poderia pular

dd if=/dev/zero of=405zeros bs=1 count=405
cat 405zeros filtered.dump | dd bs=512 skip=3 of=trimmed.dump

que foi concluído rapidamente, mas deve ter havido uma maneira mais simples / melhor? Existe outra ferramenta que eu esqueci? Obrigado!

Rup
fonte
ddé a ferramenta certa para o trabalho - parece que você encontrou uma solução agradável e elegante para o seu problema.
Justin Ethier

Respostas:

64

Você pode alternar as opções bs e pular:

dd bs=1131 skip=1 if=filtered.dump of=trimmed.dump

Dessa forma, a operação pode se beneficiar de um bloco maior.

Caso contrário, você pode tentar com tail (embora não seja seguro usá-lo com arquivos binários):

tail -c +1132 filtered.dump >trimmed.dump

Finalmente, você pode usar 3 instâncias dd para escrever algo como isto:

dd if=filtered.dump bs=512k | { dd bs=1131 count=1 of=/dev/null; dd bs=512k of=trimmed.dump; }

onde o primeiro dd imprime sua saída padrão filtrada.dump; o segundo apenas lê 1131 bytes e os joga fora; então, o último lê da entrada padrão os bytes restantes de filter.dump e grava-os em trimmed.dump.

marco
fonte
6
Obrigado! Eu não sabia que a entrada canalizada era transferida para um segundo processo como esse - isso é muito interessante. Porém, não acredito que pensei bs=1131 skip=1: - /
Rup
2
A maioria das implementações modernas de utilitários de shell funcionam corretamente com arquivos binários (ou seja, não têm problemas com caracteres nulos e não inserem uma nova linha extra no final do arquivo). Certamente as implementações GNU e * BSD são seguras.
Gilles 'SO- stop be evil'
O que significa "não é seguro usá-lo com arquivos binários"?
Scott
18

Não tem certeza de quando skip_bytesfoi adicionado, mas para pular os 11 primeiros bytes que você possui:

# echo {123456789}-abcdefgh- | 
                              dd bs=4096 skip=11 iflag=skip_bytes
-abcdefgh-
0+1 records in
0+1 records out
11 bytes (11 B) copied, 6.963e-05 s, 158 kB/s

Onde iflag=skip_bytesdiz ao dd para interpretar o valor da skipopção como bytes em vez de blocos, tornando-o simples.

Ярослав Рахматуллин
fonte
Certamente uma vantagem de velocidade para arquivos grandes e uma pequena quantidade de dados a serem removidos.
sstn
Esta é a melhor resposta, pois funciona para todos os tamanhos de bloco, por exemploiflag=skip_bytes skip=1234 bs=1M
phiresky 15/01
15

Você pode usar um sub-shell e duas ddchamadas como esta:

$ ( dd bs=1131 count=1 of=dev_null && dd bs=4K of=out.mp3 ) < 100827_MR029_LobbyControl.mp3
1+0 records in
1+0 records out
1131 bytes (1.1 kB) copied, 7.9691e-05 s, 14.2 MB/s
22433+1 records in
22433+1 records out
91886130 bytes (92 MB) copied, 0.329823 s, 279 MB/s
$ ls -l *
-rw------- 1 max users 91887261 2011-02-03 22:59 100827_MR029_LobbyControl.mp3
-rw-r--r-- 1 max users     1131 2011-02-03 23:04 dev_null
-rw-r--r-- 1 max users 91886130 2011-02-03 23:04 out.mp3
$ cat dev_null out.mp3 > orig
$ cmp 100827_MR029_LobbyControl.mp3 orig
maxschlepzig
fonte
11
Obrigado - eu não sabia que a entrada canalizada continuava em um segundo processo como esse, acho que esse é o sub shell? Definitivamente vou me lembrar disso! Eu dei a marca a Marco porque ele chegou aqui primeiro, mas +1 e obrigado pela resposta!
Rup
11
@Rup, sim, o sub-shell - criado entre parênteses - fornece um descritor de arquivo stdin e as duas chamadas dd consomem sucessivamente a entrada dele. Sim - Marco me bater por 29 segundos :)
maxschlepzig
6

Se o sistema de arquivos e o kernel Linux o suportarem, você pode tentar fallocatese deseja fazer as alterações no local: na melhor das hipóteses, não há IO de dados:

$ fallocate <magic> -o 0 -l 1131 inplace.dump

onde <magic>depende do sistema de arquivos, da versão Linux e do tipo de arquivo ( FALLOC_FL_COLLAPSE_RANGEou FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZEpode ser usado internamente ).

jfs
fonte
11
Este é o meu método preferido, mas a execução em um contêiner tem seus problemas. stackoverflow.com/questions/31155591/…
michaelcurry
3

Você deve usar count=0- isso é simples lseek()sempre que possível.

Como isso:

{  dd bs=1131 skip=1 count=0; cat; } <filtered.dump >trimmed.dump

ddfará lseek()o descritor do arquivo de entrada para um deslocamento de 1131 bytes e, em seguida cat, simplesmente copiará o que restar na saída.

mikeserv
fonte
2

Ainda outra maneira de remover os bytes iniciais de um arquivo (sem usar ddnada) é usar xxde sedou tailrespectivamente.

bytes=$((1131*2))

xxd -p -c 256 filtered.dump | tr -d '\n' | sed "s/^.\{0,${bytes}\}//" | xxd -r -p > trimmed.dump

bytes=$((bytes + 1)) 
xxd -p -c 256 filtered.dump | tr -d '\n' | tail -c +${bytes} | xxd -r -p > trimmed.dump
wop
fonte
Isso é legal, mas acho que prefiro apenas trabalhar com o arquivo em binário do que convertê-lo para e de hex.
Rup
2

@maxschlepzig pede um forro online. Aqui está um em perl. É preciso 2 argumentos: de byte e comprimento. O arquivo de entrada deve ser fornecido por '<' e a saída estará no stdout:

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 < bigfile > outfile

Se o comprimento for maior que o arquivo, o restante do arquivo será copiado.

No meu sistema, isso oferece 3,5 GB / s.

Ole Tange
fonte
Acho que o desafio de uma linha dele foi fazer com que você provasse que a solução da linguagem de script era melhor do que a solução shell de uma linha. E eu prefiro o dele: é mais curto e claro para mim. Se o seu tiver um desempenho melhor, é porque você está usando um tamanho de bloco maior do que ele, o que também é facilmente aprimorado na versão dele.
Rup
@Rup Alas, mas não. Você parece esquecer que ddnão garante uma leitura completa. Tente: sim | dd bs = contagem de 1024k = 10 | wc unix.stackexchange.com/questions/17295/…
Ole Tange
Além disso, minha solução não lerá os bytes que você não precisa (que pode ter vários terabytes de comprimento).
Ole Tange