unix - ¿Por qué la salida de mi herramienta se sobrescribe y cómo la soluciono?




awk sed (2)

La intención de esta pregunta es proporcionar una respuesta a las preguntas diarias cuya respuesta es "tiene terminaciones de línea de DOS" para que podamos simplemente cerrarlas como duplicados de esta sin repetir las mismas respuestas hasta la saciedad .

NOTA: Esto NO es un duplicado de ninguna pregunta existente . La intención de estas preguntas y respuestas no es solo proporcionar una respuesta de "ejecutar esta herramienta", sino también explicar el problema de manera tal que podamos señalar a cualquier persona con una pregunta relacionada aquí y encontrarán una explicación clara de por qué se les señaló aquí también como la herramienta para ejecutar, así que resuelva su problema. Pasé horas leyendo todas las preguntas y respuestas existentes y todas carecen de la explicación del problema, herramientas alternativas que se pueden utilizar para resolverlo y / o los pros / contras / advertencias de las posibles soluciones. Además, algunos de ellos han aceptado respuestas que son simplemente peligrosas y que nunca deben usarse.

Ahora volvamos a la pregunta típica que resultaría en 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 el resultado que espero:

isgoingon what

Obtengo que el campo que debe estar al final de la línea aparece al comienzo de la línea, sobrescribiendo un texto al comienzo 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?


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

Si en un sistema Fedora, dnf install dos2unix colocará la herramienta dos2unix en su lugar (si no está instalada).

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

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

Esto significa que hay docenas de formas de convertir de DOS a Unix usando casi todas las herramientas imaginables. ¡Una manera simple es usar el comando tr donde simplemente reemplazas \r con nada!

tr -d '\r' < infile > outfile

El problema es que su archivo de entrada usa 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, por lo que el CR sigue siendo parte de los datos que opera la herramienta UNIX. CR se denota comúnmente por \r y se puede ver como un control-M ( ^M ) cuando ejecuta cat -vE en el archivo mientras que LF es \n y aparece como $ con cat -vE .

Entonces su archivo de entrada no era realmente solo:

what isgoingon

en realidad era:

what isgoingon\r\n

como puedes ver con cat -v :

$ cat -vE file
what isgoingon^M$

y od -c :

$ od -c file
0000000   w   h   a   t       i   s   g   o   i   n   g   o   n  \r  \n
0000020

así que cuando ejecuta una herramienta UNIX como awk (que trata \n como el final de la línea) en el archivo, el \n es consumido por el acto de leer la línea, pero eso deja los 2 campos como:

<what> <isgoingon\r>

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

print $2, $1

awk imprimirá isgoingon y luego regresará el cursor al inicio de la línea antes de imprimir el motivo por el cual what parece sobrescribir el inicio de isgoingon .

Para solucionar el problema, realice una de estas acciones:

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

Aparentemente, dos2unix es también frodos 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 de 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 de DOS simplemente configurando RS adecuadamente:

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

pero otros awks no permitirán eso, ya que POSIX solo requiere awks para admitir un solo carácter RS ​​y la mayoría de los otros awks truncarán silenciosamente RS='\r\n' a RS='\r' . Es posible que deba agregar -v BINMODE=3 para que gawk vea incluso los \r s, ya que las primitivas C subyacentes los eliminarán en algunas plataformas, por ejemplo, cygwin.

Una cosa a tener en cuenta es que los CSV creados por herramientas de Windows como Excel utilizarán CRLF como terminaciones de línea, pero pueden tener LF incrustados 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 solo convierte \r\n en \n , entonces ya no puede distinguir los avances de línea dentro de los campos de los avances de línea como terminaciones de línea, por lo que si desea hacerlo, le recomiendo convertir todos los avances de línea dentro del campo a otra cosa primero, por ejemplo, esto convertiría todos los LFs en pestañas y convertiría todos los CRLF finales de línea en LF s:

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

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





dos2unix