unix - ¿Por qué la salida de mi herramienta se sobrescribe y cómo la soluciono?
awk sed (3)
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.
Puede usar la
clase de caracteres abreviados
/R
en
PCRE
para archivos con finales de línea desconocidos.
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 ''final de línea'' y estandarizar en un carácter
/n
.
Dado:
$ printf "what/risgoingon/r/n" > file
$ od -c file
0000000 w h a t /r i s g o i n g o n /r /n
0000020
Perl y Ruby y la mayoría de los sabores de PCRE implementan
/R
combinados con la afirmación de final de cadena
$
(final de línea en modo multilínea):
$ perl -pe ''s//R$//n/'' file | od -c
0000000 w h a t /r i s g o i n g o n /n
0000017
$ ruby -pe ''$_.sub!(//R$/,"/n")'' file | od -c
0000000 w h a t /r i s g o i n g o n /n
0000017
(Tenga en cuenta que la
/r
entre las dos palabras se deja correctamente sola)
Si no tiene
/R
, puede usar el equivalente de
(?>/r/n|/v)
en PCRE.
Con herramientas POSIX rectas, es probable que su mejor apuesta sea así:
$ awk ''{sub(//r$/,"")} 1'' file | od -c
0000000 w h a t /r i s g o i n g o n /n
0000017
Cosas que funcionan (pero conoces tus limitaciones):
tr
elimina todo
/r
incluso si se usa en otro contexto (dado 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 w h a t i s g o i n g o n /n
0000016
GNU
sed
funciona, pero no POSIX
sed
ya que
/r
y
/x0D
no son compatibles con POSIX.
GNU sed solo:
$ sed ''s//x0D//'' file | od -c # also sed ''s//r//''
0000000 w h a t /r i s g o i n g o n /n
0000017
La Guía de expresión regular Unicode es probablemente la mejor apuesta de cuál es el tratamiento definitivo de lo que es una "nueva línea".