Comment trouver des fichiers qui n'ont pas de ligne vide à la fin?

9

J'ai des fichiers dans les sous-répertoires du répertoire courant qui peuvent ou non avoir de nouvelles lignes à la fin; comment puis-je trouver des fichiers qui n'ont pas de nouvelle ligne à la fin?

J'ai essayé ça:

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

mais ça ne marche pas. awk 'END{print}' $fileimprime la ligne avant une nouvelle ligne vide, identique à tail -n 1 $file.

jcubic
la source
@don_crissti J'ai besoin de fichiers sans ligne vide de fin.
jcubic
2
Puis-je vous demander la raison pour laquelle vous avez besoin de trouver ces fichiers? Je suppose que cela a à voir avec le fait que les fichiers texte sous Unix sont censés se terminer par une nouvelle ligne (vi en ajoutera "presque silencieusement" un lorsque vous enregistrez, par exemple), et plusieurs commandes (orientées texte) ignoreront le dernière ligne si elle n'est pas terminée par une nouvelle ligne (wc, iirc .... mais il y en a d'autres). Et cela peut aider
Olivier Dulac
awk 'END{print}' $file : ceci ignore totalement le contenu de $ file, et après avoir terminé l'analyse de tous les fichiers contenus dans "$ file", il ajoute une nouvelle ligne. Comme c'est la seule chose que la commande awk imprime, elle pourrait être remplacée par: printf '\n'(sans aucun mentino de $ file) et faire la même chose. Je pense que ce n'est PAS ce que vous visiez (c'est-à-dire: imprimer la dernière ligne du fichier?)
Olivier Dulac
@don_crissti: si le dernier caractère d'un fichier n'est pas une nouvelle ligne, alors ce fichier n'est pas strictement un fichier TEXTE unix. voir: unix.stackexchange.com/a/263919/27616 . notez que de nombreuses commandes de texte (wc, par exemple) ignorent simplement cette dernière "ligne" si elle n'est pas terminée par une nouvelle ligne
Olivier Dulac
1
@OlivierDulac: gawk s'imprime cet FreeBSD aussi, mais je n'avais pas remarqué qu'il était documenté comme dépendant de l'implémentation: gnu.org/software/gawk/manual/… . Donc , il ne se produit mais pas toujours.
dave_thompson_085

Réponses:

14

Pour clarifier, le caractère LF (aka \nou newline) est le délimiteur de ligne , ce n'est pas le séparateur de ligne. Une ligne n'est terminée que si elle se termine par un caractère de nouvelle ligne. Un fichier qui contient uniquement a\nbn'est pas un fichier texte valide car il contient des caractères après la dernière ligne. Idem pour un fichier qui ne contient que a. Un fichier qui contient a\ncontient une ligne non vide.

Ainsi, un fichier qui se termine par au moins une ligne vide se termine par deux caractères de nouvelle ligne ou contient un seul caractère de nouvelle ligne.

Si:

 tail -c 2 file | od -An -vtc

Sorties \nou \n \n, alors le fichier contient au moins une ligne vide de fin. S'il ne sort rien, alors c'est un fichier vide, s'il sort <anything-but-\0> \n, alors il se termine par une ligne non vide. Rien d'autre, ce n'est pas un fichier texte.

Maintenant, pour utiliser cela pour trouver des fichiers qui se terminent par une ligne vide, OK c'est efficace (en particulier pour les fichiers volumineux) en ce sens qu'il ne lit que les deux derniers octets des fichiers, mais d'abord la sortie n'est pas facilement analysable par programme, d'autant plus que c'est pas cohérente d'une implémentation odà l'autre, et nous aurions besoin d'en exécuter tailune odpar fichier.

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

(pour trouver les fichiers se terminant par une ligne vide) exécuterait le moins de commandes possible mais signifierait lire le contenu complet de tous les fichiers.

Idéalement, vous auriez besoin d'un shell capable de lire lui-même la fin d'un fichier.

Avec 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
la source
un moyen d'utiliser la méthode de cette réponse pour savoir si un fichier (s) sont des fichiers texte: 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 ; }. Utiliser comme:if ( are_textfiles this that otherthing ) ; then echo all are text files ; else echo "are_textfiles returned : $?" ; fi
Olivier Dulac
6

Avec gnu sedet un shell comme zsh(ou bashavec shopt -s globstar):

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

cela vérifie si la dernière ligne de chaque fichier n'est pas vide, si c'est le cas, elle imprime le nom du fichier.
Si vous voulez l'inverse (imprimer les noms de fichiers si la dernière ligne est vide) remplacez simplement /./par/^$/

don_crissti
la source
1
Jamais vu -sen action auparavant. Merci GNU!
glenn jackman
Remarque: L'option F existe à partir de la version 4.2.2 de sed (22 décembre 2012)
Isaac
3

Un fichier texte correctement terminé avec une dernière ligne vide se termine par deux \n.

Ensuite, nous nous attendons à ce que ce tail -c2soit égal à $'\n\n'.

Malheureusement, les extensions de commande suppriment les nouvelles lignes à la fin. Nous aurons besoin d'un peu de peaufinage.

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"

Nous pourrions même développer un peu pour vérifier quels fichiers n'ont pas de nouvelle ligne de fin:

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

Notez que la nouvelle ligne pourrait être changée en quelque chose comme $'\r\nsi nécessaire.
Dans ce cas, changez également tail -c2en tail -c4.

Isaac
la source
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
la source
1
cela ne fonctionne pas avec des fichiers vides mais je peux vivre avec ça.
jcubic
Il pourrait y avoir d'autres erreurs car la comparaison de chaînes ne semble pas fonctionner comme je m'y attendais. J'ai ajouté une vérification pour les fichiers vides.
Oskar Skog du
Ah, il ignore les caractères de nouvelle ligne.
Oskar Skog du
Tenez compte plus lisible cat $file 2>&1 /dev/null, ou si cela est Bash seule, cat $file &> /dev/null.
chat
1
Aussi, pensez à citer $filepartout où il est utilisé - et s'il vous plaît, utilisez $(commands ...)au lieu de `backticks`...
cat