Para higienizar recursivamente um projeto, eu uso este oneliner:
git ls-files | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
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?
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
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)
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.
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.
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
A solução mais rápida é:
[ -n "$(tail -c1 file)" ] && printf '\n' >>file
É 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
Funciona em ash, bash, lksh, mksh, ksh93, attsh e zsh, mas não em yash.
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
É 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 ..?
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.
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.
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.
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 ...
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 .
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).
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.
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
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.
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.
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.
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á.
Tags bash text-processing shell newlines