Como encontrar arquivos que não possuem linha vazia no final?

9

Eu tenho arquivos em subdiretórios do diretório atual que podem ou não ter novas linhas no final; como posso encontrar arquivos que não possuem uma nova linha no final?

Eu tentei isso:

find . -name '*.styl' | while read file; do
    awk 'END{print}' $file | grep -E '^$' > /dev/null || echo $file;
done

mas não funciona. awk 'END{print}' $fileimprime a linha antes de uma nova linha vazia, igual a tail -n 1 $file.

jcubic
fonte
@don_crissti Preciso de arquivos que não tenham uma linha vazia à direita.
jcubic
2
Posso perguntar o motivo pelo qual você precisa encontrar esses arquivos? Eu acho que isso tem a ver com o fato de que os arquivos de texto no unix devem terminar com uma nova linha (o vi "quase silenciosamente" adiciona um quando você salva, por exemplo), e vários comandos (orientados a texto) ignoram o última linha se não for finalizada por uma nova linha (wc, iirc .... mas existem outras). E isso pode ajudar #
1144 Olivier Dulac
awk 'END{print}' $file : isso ignora totalmente o conteúdo do arquivo $ e, após terminar de analisar todos os arquivos contidos em "$ arquivo", ele adiciona uma nova linha. Como é a única coisa que o comando awk imprime, ele pode ser substituído por: printf '\n'(sem nenhum mentino de $ file) e fazer a mesma coisa. Eu acho que não é isso que você estava com o objetivo de: (isto é imprimir a última linha do arquivo?)
Olivier Dulac
@don_crissti: se o último caractere de um arquivo não for uma nova linha, esse arquivo não será estritamente posixly, um arquivo TEXT unix. consulte: unix.stackexchange.com/a/263919/27616 . nota que muitos comandos de texto (wc, por exemplo) simplesmente ignorar essa última "linha" se não for denunciado por uma nova linha
Olivier Dulac
1
@OlivierDulac: o gawk imprime ce o FreeBSD também, mas eu não percebi que ele está documentado como dependente da implementação: gnu.org/software/gawk/manual/… . Por isso não acontecer, mas nem sempre.
David_thompson_085 15/16

Respostas:

14

Para esclarecer, o caractere LF (aka \nou nova linha) é o delimitador de linhas , não é o separador de linhas. Uma linha não é finalizada, a menos que seja finalizada por um caractere de nova linha. Um arquivo que contém apenas a\nbnão é um arquivo de texto válido porque contém caracteres após a última linha. O mesmo para um arquivo que contém apenas a. Um arquivo que contém a\ncontém uma linha não vazia.

Portanto, um arquivo que termina com pelo menos uma linha vazia termina com dois caracteres de nova linha ou contém um único caractere de nova linha.

E se:

 tail -c 2 file | od -An -vtc

Saídas \nou \n \n, em seguida, o arquivo contém pelo menos uma linha vazia à direita. Se ele não produzir nada, então esse é um arquivo vazio, se gerar <anything-but-\0> \n, então termina em uma linha não vazia. Além disso, não é um arquivo de texto.

Agora, para usar isso para encontrar arquivos que terminam em uma linha vazia, OK é eficiente (especialmente para arquivos grandes), pois apenas lê os últimos dois bytes dos arquivos, mas primeiro a saída não é facilmente analisável programaticamente, especialmente considerando que é não é consistente de uma implementação odpara a seguinte e precisamos executar uma taile uma odpor arquivo.

find . -type f -size +0 -exec gawk '
  ENDFILE{if ($0 == "") print FILENAME}' {} +

(para localizar arquivos que terminam em uma linha vazia) executaria o menor número possível de comandos, mas significaria ler o conteúdo completo de todos os arquivos.

Idealmente, você precisaria de um shell que possa ler o final de um arquivo sozinho.

Com zsh:

zmodload zsh/system
for f (**/*(D.L+0)) {
  {
    sysseek -w end -2
    sysread
    [[ $REPLY = $'\n' || $REPLY = $'\n\n' ]] && print -r -- $f
  } < $f
}
Stéphane Chazelas
fonte
uma maneira de usar o método desta resposta para saber se algum arquivo (s) são arquivos de texto: are_textfiles () { nontext=0; rem="return 0 if all args are files with terminating newline, or n [=number of non-textfiles]" ; for f in "$@" ; do [ -f "$f" ] && { tail -c 1 "$f" | od -An -vtc | grep "\\n" ;} >/dev/null 2>&1 || ((nontext++)) ; done ; return $nontext ; }. Use como:if ( are_textfiles this that otherthing ) ; then echo all are text files ; else echo "are_textfiles returned : $?" ; fi
Olivier Dulac 14/10
6

Com gnu sede um shell como zsh(ou bashcom shopt -s globstar):

sed -ns '${/./F}' ./**/*.styl

isso verifica se a última linha de cada arquivo não está vazia; caso contrário, imprime o nome do arquivo.
Se desejar o contrário (imprima os nomes dos arquivos se a última linha estiver vazia), substitua-os /./por/^$/

don_crissti
fonte
1
Nunca visto -sem ação antes. Obrigado GNU!
Glenn Jackman
Nota: A opção F existe da versão sed 4.2.2 (22 de dezembro de 2012)
Isaac
3

Um arquivo de texto finalizado corretamente com uma última linha vazia termina em duas \n.

Então, esperamos que isso tail -c2seja igual a $'\n\n'.

Infelizmente, as expansões de comando removem as novas linhas à direita. Vamos precisar de alguns ajustes.

f=filename
nl='
'
t=$(tail -c2 $f; printf x)  # capture the last two characters.
r="${nl}${nl}$"                 # regex for: "ends in two newlines".
[[ ${t%x} =~ $r ]] &&  echo "file $f ends in an empty line"

Poderíamos até expandir um pouco para verificar quais arquivos não possuem uma nova linha à direita:

nl='
'
nl=$'\n'
find . -type f -name '*.styl' | while read f; do
    t=$(tail -c2 $f; printf x); r1="${nl}$"; r2="${nl}${r1}"
    [[ ${t%x} =~ $r1 ]] || echo "file $f is missing a trailing newline"
    [[ ${t%x} =~ $r2 ]] && echo "$f"
done

Observe que a nova linha pode ser alterada para algo como $'\r\nse necessário.
Nesse caso, mude também tail -c2para tail -c4.

Isaac
fonte
0
for file in *; do
    # Check if the file is readable to avoid clutter
    if cat "./$file" 2&>1 /dev/null; then
        # Compare the last character with a single newline character.
        if [ -n "$(tail -c 1 -- "./$file")" ]; then
            echo "$file"
        fi
        # Also report empty files.
        if [ $(wc -c  < "./$file") -eq 0 ]; then
            echo "$file"
        fi
    fi
done
Oskar Skog
fonte
1
isso não funciona com arquivos vazios, mas eu posso viver com isso.
jcubic
Pode haver mais alguns erros porque a comparação de cadeias parece não funcionar da maneira que eu esperava. Eu adicionei uma verificação para arquivos vazios.
Oskar Skog
Ah, ele ignora os caracteres da nova linha.
Oskar Skog
Considere o mais legível cat $file 2>&1 /dev/null, ou se for somente Bash cat $file &> /dev/null,.
gato
1
Além disso, considere citando $filetodos os lugares ele é utilizado - e, por favor, use $(commands ...)em vez de `backticks`...
cat