desenv-web-rp.com

Como adicionar uma nova linha ao final de um arquivo?

Usando sistemas de controle de versão, fico aborrecido com o barulho quando o diff diz No newline at end of file.

Então, eu estava pensando: como adicionar uma nova linha no final de um arquivo para se livrar dessas mensagens?

208
k0pernikus

Para higienizar recursivamente um projeto, eu uso este oneliner:

git ls-files -z | while IFS= read -rd '' f; do tail -c1 < "$f" | read -r _ || echo >> "$f"; done

Explicação:

  • git ls-files -z lista os arquivos no repositório. É necessário um padrão opcional como parâmetro adicional, que pode ser útil em alguns casos, se você quiser restringir a operação a determinados arquivos/diretórios. Como alternativa, você pode usar find -print0 ... ou programas similares para listar os arquivos afetados - apenas certifique-se de que emite NUL - entradas delimitadas.

  • while IFS= read -rd '' f; do ... done itera pelas entradas, manipulando com segurança os nomes de arquivos que incluem espaços em branco e/ou novas linhas.

  • tail -c1 < "$f" lê o último caractere de um arquivo.

  • read -r _ sai com um status de saída diferente de zero se uma nova linha à direita estiver ausente.

  • || echo >> "$f" anexa uma nova linha ao arquivo se o status de saída do comando anterior for diferente de zero.

46
Patrick Oscity

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 apenas se ele ainda não terminar com uma nova linha. Portanto, 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
215
l0b0

Dar uma olhada:

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

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

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

41
sr_

Outra solução usando ed. Esta solução afeta apenas 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 do manual ed(1) :

 LIMITAÇÕES 
 (...) 
 
 Se um arquivo de texto (não binário) não for finalizado por um caractere de nova linha, 
 Ed acrescenta um na leitura/gravação. No caso de um arquivo binário 
, Ed não acrescenta uma nova linha na leitura/gravação. 
16
enzotib

Adicione 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
15
Alexander

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

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

Essa abordagem não precisa ler o arquivo inteiro; ele 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), para que os hardlinks não sejam afetados.

echo acrescenta uma nova linha ao arquivo somente quando o resultado da substituição do comando for uma sequência não vazia. Observe que isso 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 for uma nova linha, tail retorna-o e a substituição de comando o remove; o resultado é uma sequência 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.

12
Barefoot IO

A solução mais rápida é:

[ -n "$(tail -c1 file)" ] && printf '\n' >>file 

  1. É muito rápido.
    Em um arquivo de tamanho médio seq 99999999 >file isso leva milissegundos.
    Outras soluções levam 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 carimbo de data/hora do arquivo se não for necessário 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 yash (e todos os outros shells listados acima), ela poderá ser um pouco mais complexa:

f=file
if       [ "$(tail -c1 "$f"; echo x)" != "$(printf '\nx')" ]
then     printf '\n' >>"$f"
fi
8
Isaac

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

A maneira correta, compatível com POSIX, de todos os shells (razoáveis) 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"'

Então, comparando a saída acima com 0A fornecerá um teste robusto.
É útil evitar adicionar uma nova linha a um arquivo vazio.
Arquivo que não fornecerá o ú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 grosso. Isso leva muito pouco tempo, pois apenas lê o último byte (procure 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. Não há hardlinks afetados.

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

7
sorontar

É melhor corrigir o editor do usuário que editou o arquivo pela última vez. Se você é a última pessoa a editar o arquivo - que editor você está usando, eu acho que ele tem um colega de texto ..?

4
AD7six

Desde que não haja nulos na entrada:

paste - <>infile >&0

... seria suficiente sempre acrescentar apenas uma nova linha no final de um infile se ele ainda não tivesse um. E ele só precisa ler o arquivo de entrada uma vez para acertar.

3
user157018

Se você deseja adicionar rapidamente uma nova linha ao processar algum pipeline, use o seguinte:

outputting_program | { cat ; echo ; }

também é compatível com POSIX.

Então, é claro, você pode redirecioná-lo para um arquivo.

3
MichalH

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 arquivo (opcionalmente classificados) do stdin e para cada arquivo lê o último byte 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 ocorrer algum tipo de erro, "empty:" se o arquivo estiver vazio (não termina com nova linha!), "EOL:" ("final de linha ") se o arquivo terminar com nova linha e" sem EOL: "se o arquivo não terminar com nova linha.

Nota: o script não trata nomes de arquivos que contêm novas linhas. Se você estiver em um sistema GNU ou BSD, poderá lidar com todos os nomes de arquivos possíveis adicionando -print0 para encontrar, -z para classificar e -0 para Perl, desta forma:

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 }
'

Obviamente, você ainda precisará criar uma maneira de codificar os nomes dos arquivos com novas linhas na saída (deixadas 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 uma, simplesmente com

 echo >> "$filename"

A falta de uma nova linha final pode causar erros nos scripts, pois algumas versões do Shell e outros utilitários não tratam adequadamente uma nova linha final ausente ao ler esse 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 ausente ao editar um arquivo, embora ele relate esses arquivos.

Por fim, existem scripts muito mais curtos (mas mais lentos) que podem fazer um loop sobre as 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 ...
2
jrw32982 supports Monica

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

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 'Nenhuma nova linha no final do arquivo' para muitos arquivos? no SO

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

1
kenorb

Se seu arquivo for finalizado com Windows finais de linha \r\n e você está no Linux, pode usar este comando sed. Ele apenas adiciona \r\n até a última linha, se ainda não estiver lá:

sed -i -e '$s/\([^\r]\)$/\1\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
\1\r\n    replace it with itself and add \r\n

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

0
masgo

Para aplicar a resposta aceita a todos os arquivos no diretório atual (mais subdiretórios):

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

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

0
friederbluemle

Você poderia escrever um fix-non-delimited-line script como:

#! /bin/zsh -
zmodload zsh/system || exit
ret=0
for file do
  (){
    sysseek -w end -1 || {
      syserror -p "Can't seek before the last byte: "
      return 1
    }
    read -r x || print -u0
  } <> $file || ret=$?
done
exit $ret

Ao contrário de algumas das soluções fornecidas aqui,

  • deve ser eficiente, pois não bifurca nenhum processo, apenas lê um byte para cada arquivo e não reescreve o arquivo (apenas acrescenta uma nova linha)
  • não quebrará links simbólicos/hardlinks ou afetará metadados (também, o ctime/mtime é atualizado apenas quando uma nova linha é adicionada)
  • deve funcionar bem, mesmo que o último byte seja um NUL ou faça parte de um caractere de vários bytes.
  • deve funcionar bem, independentemente de quais caracteres ou não-caracteres os nomes de arquivo podem conter
  • Deve manipular corretamente arquivos ilegíveis, graváveis ​​ou inaceitáveis ​​(e relatar erros de acordo)
  • Não deve adicionar uma nova linha aos arquivos vazios (nesse caso, relata um erro sobre uma busca inválida)

Você pode usá-lo, por exemplo, como:

that-script *.txt

ou:

git ls-files -z | xargs -0 that-script
0
Stéphane Chazelas

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

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

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

0
cipher

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.

0
user247137

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 muito grande para ser lida de qualquer maneira?) 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 bem em um arquivo que você está fazendo check-in porque o modificou) e perde links físicos, permissões não padrão e ACLs, a menos que você seja ainda mais cuidadoso.

0
dave_thompson_085

Muitas ótimas sugestões aqui, mas uma idéia seria remover uma nova linha e adicionar uma para que você saiba que não as está adicionando 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 uma nova linha, se não contiver, adicione uma.

  grep -q "[^0-9a-z-]" <<< $(tail -1 ./foo) && echo " " >> ./foo
0
Mike Q

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 escrevemos uma nova linha.

0
Daemoncan