Como adicionar uma nova linha ao final de um arquivo?

165

Usando sistemas de controle de versão, fico irritado com o ruído quando o diff diz No newline at end of file .

Então, fiquei me perguntando: como adicionar uma nova linha no final de um arquivo para se livrar dessas mensagens?

    
por k0pernikus 17.02.2012 / 13:44

20 respostas

32

Para higienizar recursivamente um projeto, eu uso este oneliner:

git ls-files | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
    
por 13.10.2014 / 17:28
172

Aqui você vai :

sed -i -e '$a\' file

E, como alternativa, para o OS X sed :

sed -i '' -e '$a\' file

Isso adiciona \n no final do arquivo somente se ele ainda não tiver terminado com uma nova linha. Então, se você executá-lo duas vezes, ele não adicionará outra nova linha:

$ cd "$(mktemp -d)"
$ printf foo > test.txt
$ sed -e '$a\' test.txt > test-with-eol.txt
$ diff test*
1c1
< foo
\ No newline at end of file
---
> foo
$ echo $?
1
$ sed -e '$a\' test-with-eol.txt > test-still-with-one-eol.txt
$ diff test-with-eol.txt test-still-with-one-eol.txt
$ echo $?
0
    
por 17.02.2012 / 15:13
35

Dê uma olhada:

$ echo -n foo > foo 
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo

então echo "" >> noeol-file deve fazer o truque. (Ou você quis pedir para identificar esses arquivos e consertando-os?)

edit removeu o "" de echo "" >> foo (consulte o comentário do @yuyichao) edit2 adicionou novamente o "" ( mas veja o comentário do @Keith Thompson)

    
por 17.02.2012 / 13:58
14

Outra solução usando ed . Esta solução afeta somente a última linha e somente se \n estiver ausente:

ed -s file <<< w

Funciona essencialmente abrindo o arquivo para edição através de um script, o script é o único comando w , que grava o arquivo de volta no disco. É baseado nesta frase encontrada na página de manual ed(1) :

LIMITATIONS
       (...)

       If  a  text (non-binary) file is not terminated by a newline character,
       then ed appends one on reading/writing it.  In the  case  of  a  binary
       file, ed does not append a newline on reading/writing.
    
por 20.02.2012 / 18:43
11

Uma maneira simples, portátil e compatível com POSIX de adicionar uma nova linha final ausente a um arquivo de texto:

[ -n "$(tail -c1 file)" ] && echo >> file

Essa abordagem não precisa ler o arquivo inteiro; pode simplesmente procurar EOF e trabalhar a partir daí.

Essa abordagem também não precisa criar arquivos temporários nas suas costas (por exemplo, sed -i), por isso os hardlinks não são afetados.

echo acrescenta uma nova linha ao arquivo somente quando o resultado da substituição do comando é uma string não vazia. Note que isto só pode acontecer se o arquivo não estiver vazio e o último byte não for uma nova linha.

Se o último byte do arquivo é uma nova linha, tail retorna, então a substituição de comandos o desmembra; o resultado é uma string vazia. O teste -n falha e o eco não é executado.

Se o arquivo estiver vazio, o resultado da substituição do comando também será uma cadeia vazia e, novamente, o eco não será executado. Isso é desejável, porque um arquivo vazio não é um arquivo de texto inválido, nem é equivalente a um arquivo de texto não vazio com uma linha vazia.

    
por 18.02.2016 / 00:30
8

Adicione uma nova linha independentemente:

echo >> filename

Aqui está uma maneira de verificar se existe uma nova linha no final antes de adicionar uma, usando Python:

f=filename; python -c "import sys; sys.exit(open(\"$f\").read().endswith('\n'))" && echo >> $f
    
por 07.10.2013 / 17:18
6

A solução mais rápida é:

[ -n "$(tail -c1 file)" ] && printf '\n' >>file 
  1. É realmente rápido.
    Em um arquivo de tamanho médio seq 99999999 >file isso leva milisegundos.
    Outras soluções demoram muito tempo:

    [ -n "$(tail -c1 file)" ] && printf '\n' >>file  0.013 sec
    vi -ecwq file                                    2.544 sec
    paste file 1<> file                             31.943 sec
    ed -s file <<< w                             1m  4.422 sec
    sed -i -e '$a\' file                         3m 20.931 sec
    
  2. Funciona em ash, bash, lksh, mksh, ksh93, attsh e zsh, mas não em yash.

  3. Não altera o registro de data e hora do arquivo, se não houver necessidade de adicionar uma nova linha.
    Todas as outras soluções apresentadas aqui alteram o registro de data e hora do arquivo.
  4. Todas as soluções acima são POSIX válidas.

Se você precisar de uma solução portátil para fazer shells (e todas as outras shells listadas acima), ela poderá ficar um pouco mais complexa:

f=file
if       [ "$(tail -c1 "$f"; echo x)" != "$(printf '\nx')" ]
then     printf '\n' >>"$f"
fi
    
por 02.05.2018 / 05:12
4

É melhor corrigir o editor do usuário que editou o arquivo pela última vez. Se você é a última pessoa a ter editado o arquivo - qual editor você está usando, acho que textmate ..?

    
por 17.02.2012 / 14:02
4

A maneira mais rápida de testar se o último byte de um arquivo é uma nova linha é ler apenas esse último byte. Isso pode ser feito com tail -c1 file . No entanto, a maneira simplista de testar se o valor de byte é uma nova linha, dependendo da remoção usual de uma nova linha no início de uma expansão de comando falha (por exemplo) em yash, quando o último caractere no arquivo é um UTF- 8 valor.

A forma correta, compatível com POSIX, toda (razoável) das shells para descobrir se o último byte de um arquivo é uma nova linha é usar xxd ou hexdump:

tail -c1 file | xxd -u -p
tail -c1 file | hexdump -v -e '/1 "%02X"'

Em seguida, comparar a saída acima para 0A fornecerá um teste robusto.
É útil evitar adicionar uma nova linha a um arquivo vazio.
Arquivo que não fornecerá um último caractere de 0A , é claro:

f=file
a=$(tail -c1 "$f" | hexdump -v -e '/1 "%02X"')
[ -s "$f" -a "$a" != "0A" ] && echo >> "$f"

Curto e doce. Isso leva muito pouco tempo, pois apenas lê o último byte (procurar EOF). Não importa se o arquivo é grande. Em seguida, adicione apenas um byte, se necessário.

Nenhum arquivo temporário é necessário nem usado. Nenhum link físico é afetado.

Se esse teste for executado duas vezes, ele não adicionará outra nova linha.

    
por 13.11.2016 / 02:43
2

Se você quiser adicionar rapidamente uma nova linha ao processar algum pipeline, use:

outputting_program | { cat ; echo ; }

é também compatível com POSIX.

Depois, é claro, você pode redirecioná-lo para um arquivo.

    
por 27.06.2015 / 18:50
2

Contanto que não haja nulos na entrada:

paste - <>infile >&0

... seria suficiente para sempre anexar apenas uma nova linha ao final de um arquivo se ele já não tivesse um. E só precisa ler o arquivo de entrada pela única vez para acertar.

    
por 17.02.2016 / 23:39
1

Embora não responda diretamente à pergunta, aqui está um script relacionado que escrevi para detectar arquivos que não terminam em nova linha. É muito rápido.

find . -type f | # sort |        # sort file names if you like
/usr/bin/perl -lne '
   open FH, "<", $_ or do { print " error: $_"; next };
   $pos = sysseek FH, 0, 2;                     # seek to EOF
   if (!defined $pos)     { print " error: $_"; next }
   if ($pos == 0)         { print " empty: $_"; next }
   $pos = sysseek FH, -1, 1;                    # seek to last char
   if (!defined $pos)     { print " error: $_"; next }
   $cnt = sysread FH, $c, 1;
   if (!$cnt)             { print " error: $_"; next }
   if ($c eq "\n")        { print "   EOL: $_"; next }
   else                   { print "no EOL: $_"; next }
'

O script perl lê uma lista de nomes de arquivos (opcionalmente classificados) de stdin e para cada arquivo lê o último caractere para determinar se o arquivo termina em uma nova linha ou não. É muito rápido porque evita a leitura de todo o conteúdo de cada arquivo. Ele gera uma linha para cada arquivo que lê, prefixado com "error:" se algum tipo de erro ocorrer, "empty:" se o arquivo estiver vazio (não terminar com newline!), "EOL:" ("end of line ") se o arquivo terminar com nova linha e" sem EOL: "se o arquivo não terminar com nova linha.

Nota: o script não lida com nomes de arquivos que contenham novas linhas. Se você está rodando no linux, você poderia manipular todos os nomes de arquivos possíveis adicionando -print0 para find, -z para sortear e -0 para perl, assim:

find . -type f -print0 | sort -z |
/usr/bin/perl -ln0e '
   open FH, "<", $_ or do { print " error: $_"; next };
   $pos = sysseek FH, 0, 2;                     # seek to EOF
   if (!defined $pos)     { print " error: $_"; next }
   if ($pos == 0)         { print " empty: $_"; next }
   $pos = sysseek FH, -1, 1;                    # seek to last char
   if (!defined $pos)     { print " error: $_"; next }
   $cnt = sysread FH, $c, 1;
   if (!$cnt)             { print " error: $_"; next }
   if ($c eq "\n")        { print "   EOL: $_"; next }
   else                   { print "no EOL: $_"; next }
'

Claro, você ainda teria que criar uma maneira de codificar os nomes de arquivos com novas linhas na saída (deixada como um exercício para o leitor).

A saída pode ser filtrada, se desejado, para acrescentar uma nova linha aos arquivos que não possuem um, simplesmente com

 echo >> $filename

A falta de uma nova linha final pode causar erros nos scripts, já que algumas versões do shell e de outros utilitários não manipularão corretamente uma nova linha final perdida ao ler tal arquivo.

Na minha experiência, a falta de uma nova linha final é causada pelo uso de vários utilitários do Windows para editar arquivos. Eu nunca vi o vim causar uma nova linha final perdida ao editar um arquivo, embora ele relate isso em arquivos.

Finalmente, há scripts muito mais curtos (mas mais lentos) que podem fazer um loop sobre suas entradas de nome de arquivo para imprimir os arquivos que não terminam em nova linha, como:

/usr/bin/perl -ne 'print "$ARGV\n" if /.\z/' -- FILE1 FILE2 ...
    
por 26.12.2013 / 19:27
1

Os editores vi / vim / ex adicionam automaticamente <EOL> na EOF, a menos que o arquivo já tenha.

Então, tente:

vi -ecwq foo.txt

que é equivalente a:

ex -cwq foo.txt

Teste:

$ printf foo > foo.txt && wc foo.txt
0 1 3 foo.txt
$ ex -scwq foo.txt && wc foo.txt
1 1 4 foo.txt

Para corrigir vários arquivos, verifique: Como corrigir 'Não há nova linha no final do arquivo' para muitos arquivos? na SO

Por que isso é tão importante? Para manter nossos arquivos compatível com POSIX .

    
por 16.05.2015 / 15:31
1

Para aplicar a resposta aceita a todos os arquivos no diretório atual (além de subdiretórios):

$ find . -type f -exec sed -i -e '$a\' {} \;

Isso funciona no Linux (Ubuntu). No OS X, você provavelmente terá que usar -i '' (não testado).

    
por 14.07.2015 / 09:53
0

Isso funciona no AIX ksh:

lastchar='tail -c 1 *filename*'
if [ 'echo "$lastchar" | wc -c' -gt "1" ]
then
    echo "/n" >> *filename*
fi

No meu caso, se o arquivo estiver faltando a nova linha, o comando wc retornará um valor de 2 e nós escreveremos uma nova linha.

    
por 11.02.2015 / 22:37
0

Muitas ótimas sugestões aqui, mas uma ideia seria remover uma nova linha e adicionar uma, para que você saiba que não as adiciona continuamente:

Pegue o arquivo "foo":

Remova uma nova linha, se houver uma:

  truncate -s $(($(stat -c '%s' foo)-1)) foo

Em seguida, adicione um:

  sed -i -e '$a\' foo

Portanto, foo sempre conterá pelo menos uma nova linha.

ou siga o arquivo e procure por uma nova linha, se ela não contiver, adicione uma ...

  grep -q "[^0-9a-z-]" <<< $(tail -1 ./foo) && echo " " >> ./foo
    
por 17.02.2016 / 21:51
0

Pelo menos nas versões GNU, simplesmente grep '' ou awk 1 canoniza sua entrada, adicionando uma nova linha final, se ainda não estiver presente. Eles copiam o arquivo no processo, o que leva tempo se for grande (mas a fonte não deve ser grande demais para ler mesmo assim?) E atualiza o modtime a menos que você faça algo como

 mv file old; grep '' <old >file; touch -r old file

(embora isso possa estar certo em um arquivo que você está fazendo o check-in porque você o modificou) e perde hardlinks, permissões não padrão e ACLs, etc., a menos que você seja ainda mais cuidadoso.

    
por 12.11.2016 / 16:28
0

Adicionando resposta de Patrick Oscity , se você quiser aplicá-lo em um diretório específico, também use:

find -type f | while read f; do tail -n1 $f | read -r _ || echo >> $f; done

Execute isso dentro do diretório no qual você gostaria de adicionar novas linhas.

    
por 25.07.2017 / 04:55
0

echo $'' >> <FILE_NAME> adicionará uma linha em branco ao final do arquivo.

echo $'\n\n' >> <FILE_NAME> adicionará 3 linhas em branco ao final do arquivo.

    
por 18.08.2017 / 15:26
0

Se seu arquivo for finalizado com terminações de linha Windows \r\n e você estiver no Linux, poderá usar este comando sed . Ele só adiciona \r\n à última linha, se ainda não estiver lá:

sed -i -e '$s/\([^\r]\)$/\r\n/'

Explicação:

-i    replace in place
-e    script to run
$     matches last line of a file
s     substitute
\([^\r]\)$    search the last character in the line which is not a \r
\r\n    replace it with itself and add \r\n

Se a última linha já contiver um \r\n , a regexp de pesquisa não corresponderá, portanto, nada acontecerá.

    
por 15.05.2018 / 16:48