Adicione linhas ao início e ao fim do arquivo enorme

23

Eu tenho o cenário em que as linhas a serem adicionadas no início e no final dos arquivos enormes.

Eu tentei como mostrado abaixo.

  • para a primeira linha:

    sed -i '1i\'"$FirstLine" $Filename
  • para a última linha:

    sed -i '$ a\'"$Lastline" $Filename  

Mas o problema com este comando é que ele está anexando a primeira linha do arquivo e atravessando o arquivo inteiro. Para a última linha, é novamente percorrendo o arquivo inteiro e anexando uma última linha. Como seu arquivo muito grande (14 GB) está demorando muito tempo.

Como posso adicionar uma linha ao início e outra ao final de um arquivo enquanto apenas o leio uma vez?

UNIXbest
fonte

Respostas:

20

sed -iusa tempfiles como um detalhe de implementação, que é o que você está enfrentando; no entanto, anexar dados ao início de um fluxo de dados sem substituir o conteúdo existente exige a reescrita do arquivo; não há como contornar isso, mesmo evitando sed -i.

Se a reescrita do arquivo não for uma opção, considere manipulá-lo quando for lido, por exemplo:

{ echo some prepended text ; cat file ; } | command

Além disso, sed é para editar fluxos - um arquivo não é um fluxo. Use um programa destinado a esse fim, como ed ou ex. A -iopção sed não é apenas portátil, mas também quebra os links simbólicos para o seu arquivo, pois ele o exclui e recria, o que é inútil.

Você pode fazer isso em um único comando com edo seguinte:

ed -s file << 'EOF'
0a
prepend these lines
to the beginning
.
$a
append these lines
to the end
.
w
EOF

Observe que, dependendo da sua implementação do ed, ele pode usar um arquivo de paginação, exigindo que você tenha pelo menos esse espaço disponível.

Chris Down
fonte
Oi, o comando ed que você forneceu está funcionando muito bem para arquivos enormes. Mas eu tenho 3 arquivos enormes como Teste, Teste1, Teste 2. Eu dei o comando como ed -s Tes * << 'EOF' 0a que antecede essas linhas no início. $ a acrescenta essas linhas ao final. w EOF Mas é só pegar o arquivo de teste e adicionar as primeiras / últimas linhas. Como podemos fazer alterações no mesmo comando para que ele adicione a primeira e a última linha em todos os arquivos.
usar o seguinte comando
@UNIXbest - Use um forloop:for file in Tes*; do [command]; done
Chris Down
Olá, Eu usei o comando abaixo para arquivo no Tes *; do ed -s Tes * << 'EOF' 0a HEllO HDR. Olá, TLR. w EOF feito Mas ainda está gravando no primeiro arquivo.
usar o seguinte comando
Certo, porque você precisa usar "$file", não Tes*como argumento ed.
Chris Baixo
2
@UNIXbest Se o seu problema foi resolvido por esta resposta, considere aceitá-lo.
Joseph R.
9

Observe que, se você quiser evitar a alocação de uma cópia inteira do arquivo no disco, poderá:

sed '
1i\
begin
$a\
end' < file 1<> file

Isso usa o fato de que, quando seu stdin / stdout é um arquivo, sed lê e grava em bloco. Portanto, aqui, não há problema em substituir o arquivo que está lendo, desde que a primeira linha que você está adicionando seja menor que sedo tamanho do bloco (deve ser algo como 4k ou 8k).

Observe que, se por algum motivo sedfalhar (morte, falha na máquina ...), você terminará com o arquivo meio processado, o que significa que alguns dados do tamanho da primeira linha estão ausentes em algum lugar no meio.

Observe também que, a menos que você sedseja o GNU sed, isso não funcionará para dados binários (mas como você está usando -i, você está usando o GNU sed).

Stéphane Chazelas
fonte
esses erros para mim no Ubuntu 16.04
Csaba Toth 9/16
4

Aqui estão algumas opções (que criarão uma nova cópia do arquivo, verifique se você tem espaço suficiente para isso):

  • eco simples / gato

    echo "first" > new_file; cat $File >> new_file; \
      echo "last" >> new_file; 
  • awk / gawk etc

    gawk 'BEGIN{print "first\n"}{print}END{print "last\n"}' $File > NewFile 

    awke seus arquivos de leitura ilk linha por linha. O BEGIN{}bloco é executado antes da primeira linha e o END{}bloco após a última linha. Então, o comando acima significa print "first" at the beginning, then print every line in the file and print "last" at the end.

  • Perl

    perl -ne 'BEGIN{print "first\n"} print;END{print "last\n"}' $File > NewFile

    Isso é essencialmente a mesma coisa que o gawk acima, escrito em Perl.

terdon
fonte
1
Observe que em todos esses casos, você precisará de pelo menos 14 GB mais espaço para o novo arquivo.
Chris Baixo
@ Chrishrown bom ponto, eu editei minha resposta para deixar isso claro. Eu assumi que isso não era um problema, pois o OP estava usando, o sed -ique cria arquivos temporários.
terdon
3

Eu prefiro o muito mais simples:

gsed -i '1s/^/foo\n/gm; $s/$/\nbar/gm' filename.txt

Isso transforma o arquivo:

asdf
qwer

para o arquivo:

foo
asdf
qwer
bar
CommaToast
fonte
2

Você pode usar o Vim no modo Ex:

ex -sc '1i|ALFA' -c '$a|BRAVO' -cx file
  1. 1 selecione a primeira linha

  2. i inserir texto e nova linha

  3. $ selecione a última linha

  4. a acrescentar texto e nova linha

  5. x salvar e fechar

Steven Penny
fonte
e se quiséssemos fazer isso em vários arquivos?
geoyws
1
@geoyws que não é realmente no escopo para esta pergunta
Steven Penny
você tem certeza de que é $ a e não% a?
Carlos Robles
2

Não há como inserir dados no início de um arquivo¹, tudo o que você pode fazer é criar um novo arquivo, gravar os dados adicionais e anexar os dados antigos. Portanto, você terá que reescrever o arquivo inteiro pelo menos uma vez para inserir a primeira linha. Você pode acrescentar a última linha sem reescrever o arquivo.

sed -i '1i\'"$FirstLine" $Filename
echo "$LastLine" >>$Filename

Como alternativa, você pode combinar os dois comandos em uma corrida de sed.

sed -i -e '1i\'"$FirstLine" -e '$ a\'"$Lastline" $Filename

sed -icria um novo arquivo de saída e o move sobre o arquivo antigo. Isso significa que, enquanto o sed estiver trabalhando, há uma segunda cópia do arquivo usando espaço. Você pode evitar isso substituindo o arquivo no local , mas com restrições importantes: a linha que você está adicionando deve ser menor que o buffer do sed e, se o sistema travar, você terminará com um arquivo danificado e com algum conteúdo perdido no meio, então eu recomendo fortemente contra isso.

¹ O Linux tem uma maneira de inserir dados em um arquivo, mas só pode inserir um número inteiro de blocos do sistema de arquivos, não pode inserir seqüências de caracteres de comprimentos arbitrários. É útil para alguns aplicativos, como bancos de dados e máquinas virtuais, mas é inútil para arquivos de texto.

Gilles 'SO- parar de ser mau'
fonte
Não é verdade. Veja fallocate()com FALLOC_FL_INSERT_RANGEdisponível no XFS e ext4 nos kernels modernos (4.xx) man7.org/linux/man-pages/man2/fallocate.2.html
Eric
@ Eric Você só pode inserir blocos inteiros, mas não comprimentos de bytes arbitrários, pelo menos a partir do Linux 4.15.0 com ext4. Existe um sistema de arquivos que pode inserir comprimentos de bytes arbitrários?
Gilles 'SO- stop be evil'
Certo, mas ainda não faz sua declaração correta. Você escreveu: "Não há como inserir dados no início de um arquivo". Isso ainda não é verdade: existe um mecanismo para inserir extensões no início de um arquivo. Ele vem com advertências, com certeza, mas vale a pena mencionar, porque alguns usuários podem não se importar com as restrições de tamanho de bloco preenchendo espaços ou retornos de carro.
Eric
0
$ (echo "Some Text" ; cat file1) > file2
Koushik Karmakar
fonte
4
Única resposta código não são aceitáveis, por favor melhorar a sua resposta
Networker
Considere expandir sua resposta para incluir uma explicação de sua sugestão ou links para a documentação que suporta sua solução.
precisa saber é o seguinte
-1

Os modernos kernels do Linux (superiores a 4.1 ou 4.2) suportam a inserção de dados no início de um arquivo através da fallocate()chamada do sistema FALLOC_FL_INSERT_RANGEnos sistemas de arquivos ext4 e xfs. Em essência, esta é uma operação de mudança lógica: os dados são realocados logicamente com um deslocamento mais alto.

Existe uma restrição em relação à granularidade do intervalo que você deseja inserir no início do arquivo. Mas para arquivos de texto, você provavelmente pode alocar um pouco mais do que o necessário (até o limite da granularidade) e preencher com espaços ou retornos de carro, mas isso depende do seu aplicativo

Não conheço nenhum utilitário linux prontamente disponível que manipule extensões de arquivo, mas não é difícil escrever: obtenha um descritor de arquivo e chame fallocate()com os argumentos apropriados. Para mais detalhes, consulte a página de manual da fallocatechamada do sistema: http://man7.org/linux/man-pages/man2/fallocate.2.html

Eric
fonte
Um utilitário não é o problema (assumindo um Linux não incorporado): o fallocateutilitário -linux contém um utilitário. O problema é que uma granularidade de blocos inteiros torna isso inútil para a maioria dos arquivos de texto. Outro problema é que a alocação do intervalo e a modificação subsequente não são atômicas. Portanto, isso realmente não resolve o problema aqui.
Gilles 'SO- stop be evil'
A granularidade é uma advertência que já mencionei e não, não a torna inútil, depende da aplicação. Onde você viu na pergunta que a atomicidade é importante? Eu só consigo ver o problema das performances. Mesmo assim, este syscall parece ser atômico: elixir.bootlin.com/linux/latest/source/fs/open.c#L228 e se a atomicidade se tornar importante (não é, mas diga que é por uma questão de argumento), então basta usar o bloqueio de arquivos. (aponte-me para o local no código do kernel onde a fallocateatomicidade é interrompida, por favor, estou curioso)
Eric