¿Por qué la salida de mi herramienta se sobrescribe a sí misma y cómo la arreglo?

La intención de esta pregunta es proporcionar una respuesta a las preguntas diarias cuya respuesta es “usted tiene terminaciones de línea DOS”, así que simplemente podemos cerrarlas como duplicados de esta sin repetir las mismas respuestas ad nauseam .

NOTA: Esto NO es un duplicado de ninguna pregunta existente . El objective de esta sesión de preguntas y respuestas no es solo proporcionar una respuesta de “ejecutar esta herramienta” sino también explicar el problema de manera que podamos simplemente señalar a cualquiera con una pregunta relacionada y encontrarán una explicación clara de por qué fueron señalados aquí también. como herramienta para correr, resuelve su problema. Pasé horas leyendo todas las preguntas y respuestas existentes y todas carecen de la explicación del problema, las herramientas alternativas que se pueden usar para resolverlo y las ventajas y desventajas de las posibles soluciones. Además, algunos de ellos han aceptado respuestas que son simplemente peligrosas y nunca deben usarse.

Ahora volvamos a la pregunta típica que daría lugar a una referencia aquí:

Tengo un archivo que contiene 1 línea:

what isgoingon 

y cuando lo imprimo usando este script awk para invertir el orden de los campos:

 awk '{print $2, $1}' file 

en lugar de ver la salida que espero:

 isgoingon what 

Aparece el campo que debe estar al final de la línea al principio de la línea, sobrescribiendo texto al principio de la línea:

  whatngon 

o obtengo la salida dividida en 2 líneas:

 isgoingon what 

¿Cuál podría ser el problema y cómo lo soluciono?

El problema es que su archivo de entrada utiliza terminaciones de línea DOS de CRLF lugar de terminaciones de línea UNIX de solo LF y está ejecutando una herramienta UNIX en él para que la CR siga siendo parte de los datos que opera la herramienta UNIX. CR se denota comúnmente como \r y se puede ver como un control-M ( ^M ) cuando ejecuta cat -vE en el archivo mientras LF es \n y aparece como $ con cat -vE .

Entonces su archivo de entrada no era realmente justo:

 what isgoingon 

fue en realidad:

 what isgoingon\r\n 

como puedes ver con cat -v :

 $ cat -vE file what isgoingon^M$ 

y od -c :

 $ od -c file 0000000 whatisgoingon \r \n 0000020 

así que cuando ejecuta una herramienta UNIX como awk (que trata \n como el final de línea) en el archivo, el \n se consume al leer la línea, pero deja los 2 campos como:

   

Tenga en cuenta el \r al final del segundo campo. \r significa Carriage Return que es literalmente una instrucción para regresar el cursor al inicio de la línea así que cuando lo haga:

 print $2, $1 

awk imprimirá isgoingon y luego devolverá el cursor al inicio de la línea antes de imprimir what es por qué what parece sobrescribir el inicio de isgoingon .

Para solucionar el problema, haga alguno de estos:

 dos2unix file sed 's/\r$//' file awk '{sub(/\r$/,"")}1' file perl -pe 's/\r$//' file 

Aparentemente, dos2unix es conocido como frodos en algunas variantes de UNIX (por ejemplo, Ubuntu).

Tenga cuidado si decide usar tr -d '\r' como se sugiere a menudo, ya que eliminará todos los \r s en su archivo, no solo los que están al final de cada línea.

Tenga en cuenta que GNU awk le permitirá analizar archivos que tienen terminaciones de línea DOS simplemente configurando RS apropiadamente:

 gawk -v RS='\r\n' '...' file 

pero otras awks no permitirán eso ya que POSIX solo requiere awks para soportar un único carácter RS ​​y la mayoría de las otras awks truncarán silenciosamente RS='\r\n' a RS='\r' . Puede que necesites agregar -v BINMODE=3 para gawk incluso para ver los \r ya que las primitivas C subyacentes los despojarán en algunas plataformas, por ejemplo, cygwin.

Una cosa a tener en cuenta es que los archivos CSV creados por herramientas de Windows como Excel usarán CRLF como terminaciones de línea, pero pueden tener LF insertados dentro de un campo específico del CSV, por ejemplo:

 "field1","field2.1 field2.2","field3" 

realmente es:

 "field1","field2.1\nfield2.2","field3"\r\n 

así que si simplemente convierte \r\n a \n ns, ya no podrá distinguir los avances de línea de los campos de avances de línea, por lo que si desea hacerlo, le recomiendo primero convertir todos los avances de línea dentro de un campo en algo diferente, por ejemplo, esto convertiría todos los LFs dentro del campo en tabs y convertiría todos los CRLF final de línea en LF s:

 gawk -v RS='\r\n' '{gsub(/\n/,"\t")}1' file 

Hacer lo mismo sin GNU awk queda como un ejercicio, pero con otros awks implica combinar líneas que no terminan en CR medida que se leen.

Ejecute dos2unix . Si bien puede manipular los finales de línea con el código que usted mismo escribió, existen utilidades que existen en el mundo de Linux / Unix que ya lo hacen por usted.

Si en un sistema Fedora dnf install dos2unix pondrá la herramienta dos2unix en su lugar (en caso de que no esté instalada).

Hay un paquete similar de dos2unix deb disponible para sistemas basados ​​en Debian.

Desde el punto de vista de la progtwigción, la conversión es simple. Busque todos los caracteres en un archivo para la secuencia \r\n y reemplácelo con \n .

Esto significa que hay docenas de maneras de convertir de DOS a Unix usando casi todas las herramientas imaginables. ¡Una forma simple es usar el comando tr donde simplemente lo reemplaza con nada!

 tr -d '\r' < infile > outfile 

Puede usar la clase de caracteres taquigrafía \R en PCRE para archivos con terminaciones de línea desconocidas. Hay incluso más final de línea para considerar con Unicode u otras plataformas. El formulario \R es una clase de caracteres recomendada del consorcio Unicode para representar todas las formas de una nueva línea genérica.

Entonces, si tiene un ‘extra’ puede encontrarlo y eliminarlo con la expresión regular s/\R$/\n/ normalizará cualquier combinación de terminaciones de línea en \n . Alternativamente, puede usar s/\R/\n/g para capturar cualquier noción de ‘terminación de línea’ y estandarizar en un carácter \n .

Dado:

 $ printf "what\risgoingon\r\n" > file $ od -c file 0000000 what \risgoingon \r \n 0000020 

Perl y Ruby y la mayoría de los sabores de PCRE implementan \R combinado con el final de la aserción de cadena $ (fin de línea en modo multilínea):

 $ perl -pe 's/\R$/\n/' file | od -c 0000000 what \risgoingon \n 0000017 $ ruby -pe '$_.sub!(/\R$/,"\n")' file | od -c 0000000 what \risgoingon \n 0000017 

(Tenga en cuenta que el \r entre las dos palabras se deja correctamente solo)

Si no tiene \R , puede usar el equivalente de (?>\r\n|\v) en PCRE.

Con las herramientas POSIX directas, es probable que su mejor apuesta sea así:

 $ awk '{sub(/\r$/,"")} 1' file | od -c 0000000 what \risgoingon \n 0000017 

Cosas que funcionan un poco (pero conoce tus limitaciones):

tr borra todo \r incluso si se usa en otro contexto (se concede que el uso de \r es raro, y el procesamiento XML requiere que \r se elimine, por lo que tr es una gran solución):

 $ tr -d "\r" < file | od -c 0000000 whatisgoingon \n 0000016 

GNU sed funciona, pero no POSIX sed ya que \r \x0D no son compatibles con POSIX.

GNU sed solo:

 $ sed 's/\x0D//' file | od -c # also sed 's/\r//' 0000000 what \risgoingon \n 0000017 

La Guía de expresión regular de Unicode es probablemente la mejor opción para definir el tratamiento definitivo de lo que es una "nueva línea".