Comment tester si un fichier utilise CRLF ou LF sans le modifier?

48

Je dois exécuter périodiquement une commande qui garantit que certains fichiers texte sont conservés en mode Linux. Malheureusement, dos2unixmodifie toujours le fichier, ce qui perturberait les horodatages des fichiers et des dossiers et provoquerait des écritures inutiles.

Le script que j'écris est en Bash, je préférerais donc des réponses basées sur Bash.

Adam Ryczkowski
la source

Réponses:

41

Vous pouvez utiliser dos2unixcomme filtre et comparer sa sortie au fichier d'origine:

dos2unix < myfile.txt | cmp -s - myfile.txt
Samuel Edwin Ward
la source
2
Très intelligent et utile, car il teste le fichier complet et pas seulement la première ou quelques lignes.
halloleo
2
Peut - être que vous pouvez remplacer testpar myfile.txtdeux fois dans votre exemple pour éviter toute confusion avec /usr/bin/test.
Peterino
1
NB: vous devrez supprimer le -sdrapeau pour voir le résultat. Des pages de manuel: -s, --quiet, --silent suppress all normal output
tobalr le
24

Si l’objectif est simplement d’éviter d’affecter l’horodatage, dos2unixune option -kou --keepdatepermet de conserver l’horodatage. Il faudra toujours une écriture pour créer le fichier temporaire et le renommer, mais votre horodatage ne sera pas affecté.

Si une modification du fichier est inacceptable, vous pouvez utiliser la solution suivante à partir de cette réponse .

find . -not -type d -exec file "{}" ";" | grep CRLF
j883376
la source
1
Voulez-vous dire que vous écrivez littéralement CRLF avec 4 caractères C, R, L et F?
bodacydo
7
Voulez-vous dire également que grep peut prendre CR et LF juste comme ça?
bodacydo
@bodacydo C'est expliqué dans la réponse à laquelle il renvoie, et maintenant également dans l'édition de Scott de la réponse de BertS ici unix.stackexchange.com/a/79708/59699 .
dave_thompson_085
@ dave_thompson_085 Je ne vois pas d'explication. Il ne mentionne que CRLF mais n'explique pas ce que c'est.
bodacydo
1
@bodacydo stackoverflow.com/questions/73833/… indique que find ... -exec file ... | grep CRLFpour un fichier avec des fins de ligne DOS (octets 0D 0A) "vous obtiendrez quelque chose comme: ./1/dos1.txt: ASCII text, with CRLF line terminators Comme vous pouvez le voir, cela contient la chaîne réelle CRLF et est donc comparé en greprecherchant la chaîne simple CRLF
dave_thompson_085
22

Vous pouvez essayer greppour le code CRLF, octal:

grep -U $'\015' myfile.txt

ou hex:

grep -U $'\x0D' myfile.txt
don_crissti
la source
Bien entendu, l’hypothèse est qu’il s’agit d’un fichier texte.
Mdpc
2
J'aime cet grepusage, car il me permet de répertorier facilement tous ces fichiers dans le répertoire grep -lU $'\x0D' *et de transmettre le résultat xargs.
Melebius
Quel est le sens du $ avant le motif de recherche? @don_crissti
fersarr
1
@fersarr - unix.stackexchange.com/a/401451/22142
don_crissti
21

Depuis la version 7.1dos2unix a une -i, --infooption pour obtenir des informations sur les sauts de ligne. Vous pouvez utiliser dos2unix lui-même pour tester les fichiers nécessitant une conversion.

Exemple:

dos2unix -ic *.txt | xargs dos2unix
Erwin Waterlander
la source
Voici un lien vers le journal des modifications
Adam Ryczkowski
13

Première méthode ( grep):

Comptez les lignes qui contiennent un retour chariot:

[[ $(grep -c $'\r' myfile.txt) -gt 0 ]] && echo dos

Comptez les lignes qui se terminent par un retour chariot:

[[ $(grep -c $'\r$' myfile.txt) -gt 0 ]] && echo dos

Ceux-ci seront généralement équivalents; un retour de chariot à l'intérieur d'une ligne (c'est-à-dire pas à la fin) est rare.

Plus efficace:

grep -q $'\r' myfile.txt && echo dos

C'est plus efficace

  1. car il n'est pas nécessaire de convertir le compte en chaîne ASCII, puis de reconvertir cette chaîne en entier et de la comparer à zéro, et
  2. car grep -cdoit lire tout le fichier, compter toutes les occurrences du motif, tout en grep -qpouvant sortir dès la première apparition du motif.

Remarques:

  • Tout au long de ce qui précède, vous devrez peut-être ajouter l’ -Uoption (c’est-à-dire utiliser -cUou -qU), car GNU grepdevine si le fichier est un fichier texte. S'il pense que le fichier est du texte, il ignore les retours à la ligne à la fin des lignes, pour que $les expressions régulières fonctionnent "correctement" - même si l'expression régulière l'est \r$! La spécification -U(ou --binary) annule cette approximation, ce grepqui entraîne le traitement du ou des fichiers comme étant binaire et la transmission complète des données au mécanisme de correspondance, avec les terminaisons CR intactes.
  • Ne faites pas grep … $'\r\n' myfile.txt, car greptraite \ncomme un délimiteur de motif. Tout comme grep -E 'foo|'recherche des lignes contenant fooou une chaîne NULL, grep $'\r\n'recherche des lignes contenant \rou une chaîne NULL, et chaque ligne correspond à une chaîne NULL.

Deuxième méthode ( file):

[[ $(file myfile.txt) =~ CRLF ]] && echo dos

parce que filerapporte quelque chose comme:

myfile.txt: UTF-8 Unicode text, with CRLF line terminators

Variante plus sûre:

[[ $(file -b - < myfile.txt) =~ CRLF ]] && echo dos

  • file -baffiche uniquement le type de fichier et non le nom du fichier. Sans cela, un fichier dont le nomCRLF inclurait les caractères déclencherait un faux positif.
  • file - < filenamefonctionne même si filenamecommence par -Voir Script Bash: vérifiez si un fichier est un fichier texte .

Notez que la vérification de la sortie file peut ne pas fonctionner dans un environnement local non anglais.

BertS
la source
1
Vous pouvez remplacer "$(echo -e '\r')"par le beaucoup plus simple $'\r', bien que personnellement, je $'\r\n'réduirais le nombre de faux positifs.
Rici
@rici grep $'\r\n'semble correspondre à tous les fichiers de mon système ...
jeudi
@rici: bonne prise. J'ai édité ma réponse selon votre suggestion. - depquid: Peut-être que vous êtes sur Windows? :-) Le conseil de Rici fonctionne ici.
BertS
@depquid (et BertS): En fait, je pense que l'invocation correcte consiste grep -U $'\r$'à empêcher d' grepessayer de deviner les fins de ligne.
Rici
En outre, vous pouvez -qsimplement définir le code de retour si une correspondance est trouvée, -cce qui nécessite une vérification supplémentaire. Personnellement, j’aime bien votre deuxième solution, bien qu’elle dépende beaucoup des caprices de fileet puisse ne pas fonctionner dans un environnement local autre que l’anglais.
Rici
11

Utilisation cat -A

$ cat file
hello
hello

Si ce fichier était créé sur des systèmes * NIX, il afficherait

$ cat -A file
hello$
hello$

Mais si ce fichier était créé sous Windows, il afficherait

$ cat -A file
hello^M$
hello

^Mreprésente CRet $représente LF. Notez que Windows n’a pas enregistré la dernière ligne avecCRLF

Cela ne change pas non plus le contenu du fichier.

GypsyCosmonaut
la source
La solution la meilleure et la plus simple! a besoin de plus de votes.
user648026
1
+1 De loin la meilleure réponse. Pas de dépendances, pas de scripts bash compliqués. Juste -Apour chat. Un conseil cependant serait d'utiliser cat -A file | lesssi le fichier est trop gros. Je suis sûr qu'il n'est pas rare de devoir vérifier la fin d'un fichier pour un fichier particulièrement long. (Appuyez qpour quitter moins)
Nicholas Pipitone
4

une fonction bash pour vous:

# return 0 (true) if first line ends in CR
isDosFile() {
    [[ $(head -1 "$1") == *$'\r' ]]  
}

Ensuite, vous pouvez faire des choses comme

streamFile () {
    if isDosFile /tmp/foo.txt; then
        sed 's/\r$//' "$1"
    else
        cat "$1"
    fi
}

streamFile /tmp/foo.txt | process_lines_without_CR
Glenn Jackman
la source
3
Vous ne devez pas utiliser isDosFile()dans votre exemple: streamFile() { sed 's/\r$//' "$1" ; }.
1
Je pense que c'est la solution la plus élégante. il ne lit pas le fichier entier, juste la première ligne.
Adam Ryczkowski
4

Si un fichier comporte des fins de ligne CR-LF de style DOS / Windows, il affiche des caractères CR ("\ r") à la fin de chaque ligne, à l'aide d'un outil basé sur Unix.

Cette commande:

grep -l '^M$' filename

imprimera filenamesi le fichier contient une ou plusieurs lignes avec des fins de ligne de style Windows et n’imprimera rien s’il ne l’est pas. Sauf que le ^Mdoit être un caractère de retour chariot littéral, généralement entré dans le terminal en tapant Ctrl+ Vsuivi de Enter (ou Ctrl+ Vpuis Ctrl+ + M). Le shell bash vous permet d’écrire un retour chariot littéral comme $'\r'( documenté ici ), vous pouvez donc écrire:

grep -l $'\r$' filename

D'autres coquilles peuvent fournir une fonctionnalité similaire.

Vous pouvez utiliser un autre outil à la place:

awk '/\r$/ { exit(1) }' filename

Cela se terminera avec le statut 1(paramétrant $?sur 1) si le fichier contient des fins de ligne de style Windows et avec le statut 0s'il ne le contient pas, ce qui le rend utile dans une ifinstruction shell (notez le manque de [crochets ]):

if awk '/\r$/ { exit(1) }' filename ; then
    echo filename has Unix-style line endings
else
    echo filename has at least one Windows-style line ending
fi

Un fichier peut contenir un mélange de fins de ligne de style Unix et Windows. Je suppose ici que vous voulez détecter les fichiers qui ont des fins de ligne de type Windows.

Keith Thompson
la source
1
Vous pouvez coder un retour chariot sur la ligne de commande dans bash (et quelques autres shells) en tapant $'\r', comme indiqué dans d'autres réponses à cette question.
Scott
2

Utiliser file:

$ file README.md
README.md: ASCII text, with CRLF line terminators

$ dos2unix README.md
dos2unix: converting file README.md to Unix format...

$ file README.md
README.md: ASCII text
Dan Sorak
la source
Cette idée a été discutée beaucoup plus à fond dans deux réponses précédentes.
G-Man dit 'Réintégrez Monica'
1

J'ai utilisé

cat -v filename.txt | diff - filename.txt

qui semble fonctionner. Je trouve la sortie un peu plus facile à lire que

dos2unix < filename.txt | diff - filename.txt

C'est également utile si vous ne pouvez pas installer dos2unixpour une raison quelconque.

Alex028502
la source