¿Cómo probar si un archivo usa CRLF o LF sin modificarlo?


48

Necesito ejecutar periódicamente un comando que garantice que algunos archivos de texto se mantengan en modo Linux. Desafortunadamente, dos2unixsiempre modifica el archivo, lo que ensuciaría las marcas de tiempo del archivo y la carpeta y causaría escrituras innecesarias.

El guión que escribo está en Bash, por lo que preferiría respuestas basadas en Bash.

Respuestas:


41

Puede usarlo dos2unixcomo filtro y comparar su salida con el archivo original:

dos2unix < myfile.txt | cmp -s - myfile.txt

2
Muy inteligente y útil, porque prueba el archivo completo y no solo la primera o algunas líneas.
halloleo

2
Tal vez usted podría sustituir testpor myfile.txtdos veces en su ejemplo para evitar confusiones con /usr/bin/test.
Peterino

1
Nota: necesitará eliminar la -sbandera para ver la salida. De las páginas man: -s, --quiet, --silent suppress all normal output
tobalr

24

Si el objetivo es simplemente evitar afectar la marca de tiempo, dos2unixtiene una opción -ku --keepdateopción que mantendrá la marca de tiempo igual. Todavía tendrá que escribir para crear el archivo temporal y cambiarle el nombre, pero sus marcas de tiempo no se verán afectadas.

Si alguna modificación del archivo es inaceptable, puede usar la siguiente solución de esta respuesta .

find . -not -type d -exec file "{}" ";" | grep CRLF

1
¿Quiere decir que literalmente escribe CRLF como 4 caracteres C, R, L y F?
bodacydo

77
¿También quiere decir que grep puede tomar CR y LF así como así?
bodacydo

@bodacydo Se explica en la respuesta a la que se vincula, y ahora también en la edición de Scott de la respuesta de BertS aquí unix.stackexchange.com/a/79708/59699 .
dave_thompson_085

@ dave_thompson_085 No veo explicación. Solo menciona CRLF pero no explica de qué se trata.
bodacydo

1
@bodacydo stackoverflow.com/questions/73833/… dice que find ... -exec file ... | grep CRLFpara un archivo con terminaciones de línea de DOS (es decir, bytes 0D 0A) "obtendrá algo como: ./1/dos1.txt: ASCII text, with CRLF line terminators Como puede ver, esto contiene la cadena CRLF real y, por lo tanto, se grepbusca buscando la cadena simple CRLF.
dave_thompson_085

22

Puede intentar el grepcódigo CRLF, octal:

grep -U $'\015' myfile.txt

o hexadecimal:

grep -U $'\x0D' myfile.txt

Por supuesto, la suposición es que este es un archivo de texto.
mdpc

2
Me gusta este grepuso porque me permite enumerar fácilmente todos esos archivos en el directorio grep -lU $'\x0D' *y pasarle la salida xargs.
Melebius

¿Cuál es el significado de $ antes del patrón de búsqueda? @don_crissti
fersarr



13

Primer método ( grep):

Cuente las líneas que contienen un retorno de carro:

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

Cuente las líneas que terminan con un retorno de carro:

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

Estos serán típicamente equivalentes; un retorno de carro en el interior de una línea (es decir, no al final) es raro.

Más eficiente:

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

Esto es mas eficiente

  1. porque no necesita convertir el recuento en una cadena ASCII, y luego convertir esa cadena a un número entero, y compararlo con cero, y
  2. porque grep -cnecesita leer todo el archivo, para contar todas las apariciones del patrón, mientras que grep -qpuede salir al ver la primera aparición del patrón.

Notas:

  • En todo lo anterior, es posible que deba agregar la -Uopción (es decir, usar -cUo -qU), porque GNU grepadivina si el archivo es un archivo de texto. Si cree que el archivo es texto, ignora los retornos de carro al final de las líneas, en un intento de hacer que $las expresiones regulares funcionen "correctamente", ¡incluso si la expresión regular lo es \r$! La especificación -U(o --binary) anula esta conjetura, lo grepque hace que los archivos se traten como binarios y pasen los datos al mecanismo de coincidencia textualmente, con terminaciones CR intactas.
  • No lo hagas grep … $'\r\n' myfile.txt, porque se greptrata \ncomo un delimitador de patrón. Así como grep -E 'foo|'busca líneas que contienen fooo una cadena nula, grep $'\r\n'busca líneas que contienen \ro una cadena nula, y cada línea coincide con una cadena nula.

Segundo método ( file):

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

porque fileinforma algo como:

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

Variante más segura:

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

dónde

Tenga en cuenta que la comprobación de la salida file puede no funcionar en una configuración regional que no esté en inglés.


1
Puede reemplazarlo "$(echo -e '\r')"por uno mucho más simple $'\r', aunque personalmente lo usaría $'\r\n'para reducir la cantidad de falsos positivos.
rici

@rici grep $'\r\n'parece coincidir con todos los archivos en mi sistema ...
depquid

@rici: buena captura. Edité mi respuesta de acuerdo a su sugerencia. - depquid: ¿Quizás estás en Windows? :-) La sugerencia de rici funciona aquí.
BertS

@depquid (y BertS): En realidad, creo que la invocación correcta es grep -U $'\r$', para evitar greptratar de adivinar las terminaciones de línea.
rici

Además, puede usar -qsimplemente establecer el código de retorno si se encuentra una coincidencia, en lugar de lo -ccual requiere una verificación adicional. Personalmente, me gusta su segunda solución, aunque depende en gran medida de los caprichos filey podría no funcionar en un entorno no inglés.
rici

11

Utilizar cat -A

$ cat file
hello
hello

Ahora, si este archivo se hizo en sistemas * NIX, se mostrará

$ cat -A file
hello$
hello$

Pero si este archivo se hizo en Windows, se mostrará

$ cat -A file
hello^M$
hello

^Mrepresenta CRy $representa LF. Observe que Windows no guardó la última línea conCRLF

Esto tampoco cambia el contenido del archivo.


¡La mejor y más simple solución! necesita más votos.
user648026

1
+1 Con mucho, la mejor respuesta. Sin dependencias, sin scripts de bash complicados. Solo -Apara el gato. Un consejo, aunque sería utilizar cat -A file | lesssi el archivo es demasiado grande. Estoy seguro de que no es raro tener que verificar las terminaciones de archivos para un archivo particularmente largo. (Presione qpara dejar menos)
Nicholas Pipitone

4

una función bash para ti:

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

Entonces puedes hacer cosas como

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

streamFile /tmp/foo.txt | process_lines_without_CR

3
Usted no tiene que utilizar isDosFile()en su ejemplo: streamFile() { sed 's/\r$//' "$1" ; }.

1
Creo que esta es la solución más elegante; no lee todo el archivo, solo la primera línea.
Adam Ryczkowski

4

Si un archivo tiene terminaciones de línea CR-LF estilo DOS / Windows, entonces si lo mira usando una herramienta basada en Unix, verá caracteres CR ('\ r') al final de cada línea.

Este comando:

grep -l '^M$' filename

se imprimirá filenamesi el archivo contiene una o más líneas con terminaciones de línea estilo Windows, y no imprimirá nada si no lo tiene. Excepto que ^Mtiene que ser un carácter de retorno de carro literal, típicamente ingresado en la terminal escribiendo Ctrl+ Vseguido de Enter (o Ctrl+ Vy luego Ctrl+ M). El shell bash le permite escribir un retorno de carro literal como $'\r'( documentado aquí ), para que pueda escribir:

grep -l $'\r$' filename

Otros proyectiles pueden proporcionar una característica similar.

Puede usar otra herramienta en su lugar:

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

Esto saldrá con un estado de 1(configuración $?a 1) si el archivo contiene cualquier final de línea al estilo de Windows, y con un estado de 0si no lo hace, lo que lo hace útil en una ifdeclaración de shell (tenga en cuenta la falta de [corchetes ]):

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 archivo puede contener una mezcla de terminaciones de línea estilo Unix y estilo Windows. Supongo aquí que desea detectar archivos que tengan cualquier final de línea estilo Windows.


1
Puede codificar un retorno de carro en la línea de comando en bash (y algunos otros shells) escribiendo $'\r', como se menciona en otras respuestas a esta pregunta.
Scott

2

Uso 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

Esta idea se ha discutido mucho más a fondo en dos respuestas anteriores.
G-Man dice 'Restablecer a Monica'

1

he estado usando

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

que parece funcionar Encuentro la salida un poco más fácil de leer que

dos2unix < filename.txt | diff - filename.txt

También es útil si no puede instalar dos2unixpor alguna razón.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.