Introducción

Cada sistema operativo tiene una forma distinta para indicar el fin de línea de un archivo. En sistemas Windows se usan los caracteres \r\n, en unix (aunque de ahora en adelante usaremos linux indistintamente) es el carácter \n y en macOS se usa \r. Cuando abrimos un archivo generado en un sistema operativo diferente al nuestro, es muy probable que el texto se vea escalonado o no presente la misma estructura (esto también depende del editor que usemos, ya que algunos identifican el final de línea del archivo y hacen la conversión al sistema actual antes de mostrarlo). Pero el problema no es únicamente visual, hay herramientas para manejo de texto que esperan que los archivos terminen con un carácter en particular; el comando grep de linux es una muestra de esto, ya que espera que los archivos terminen únicamente con el carácter \n y si no lo está no tendremos los resultados esperados.

Resumen de caracteres de fin de línea

Sistema Operativo Fin de Línea
Linux \n
MacOS \r
Windows \r\n

El problema al usar expresiones regulares

Siguiendo con el ejemplo de grep, trataremos de buscar la cadena Emacs rocks al final de la línea. Esto se hace con una expresión regular muy sencilla: Emacs rocks$. El símbolo $ al final indica fin de línea, por lo que podemos leer nuestra expresión regular como “encuentra la frase Emacs rocks seguido del fin de línea”. Supongamos que tenemos un archivo (editors.txt) con una lista de editores de texto, seguido de un eslogan:

Intellij, made with love from Rusia
Eclipse, always happy to consume your RAM
Emacs, simple put, Emacs rocks

Podemos usar nuestra expresión regular con grep de la siguiente manera: grep "Emacs rocks$" editors.txt solo para descubrir que no obtuvimos resultados. Pero al abrir el archivo vemos que Emacs rocks existe, y está al final de la línea, ¿Entonces por qué no aparece en los resultados de grep?. En este caso en particular el archivo editors.txt fue creado en windows, por lo que cada línea termina en \r\n y como ya comentamos grep espera que el fin de línea sea únicamente \n, y todo lo anterior a eso lo considera parte de la línea, entonces para grep el archivo se ve así:

Intellij, made with love from Rusia\r
Eclipse, always happy to consume your RAM\r
Emacs, simple put, Emacs rocks\r

En este caso, podemos incluir el carácter \r en nuestra expresión regular:

$> grep $'Emacs rocks\r$' editors.txt

Aquí el primer $ no es parte de la expresión regular, es una indicación para el shell de que tome un carriage return literalmente si lo encuentra dentro de los apóstrofes.

Convertir el archivo a nuestro formato de fin de línea

Lo primero que tenemos que hacer es identificar el tipo de archivo, para eso utilizaremos el comando file:

$> file editors.txt
editors.txt: ISO-8859 text, with CRLF line terminators

file no solo nos indica la codificación del archivo, también nos dice el tipo de fin de línea del mismo, en este caso CRLF (Carriage Return y Line Feed), o lo que es lo mismo \r\n. Veamos algunas formas para convertir el archivo a fin de línea de linux.

dos2unix

Este es un programa que tiene muchos años realizando esta tarea, es muy sencillo y efectivo. Para instalarlo desde debian o derivados:

$> sudo apt install dos2unix

Y la forma de ejecutarlo es muy simple:

$> dos2unix editors.txt
dos2unix: convirtiendo archivo editors.txt a formato Unix...

Para comprobar que la conversión tuvo éxito, ejecutamos nuevamente file:

$> file editors.txt
editors.txt: ISO:8859 text

Si esto lo comparamos con la salida anterior, veremos que la frase with CRLF terminators desapareció, lo que quiere decir que se convirtió exitosamente.

sed

dos2unix básicamente busca los caracteres \r\n y los reemplaza por \n, y esto lo podemos hacer nosotros mismos, usando sed:

$> sed $'s/\r\n/\n/g' -i editors.txt

Al igual que como hicimos en el ejemplo con grep tuvimos que usar $'...' para que \r sea interpretado correctamente.

Emacs

También podemos hacer la conversión desde nuestro editor favorito. Cuando abrimos un archivo creado en windows veremos al final de cada línea los símbolos ^M, este es el equivalente en Emacs para carriage return (y el equivalente para line feed es ^J). Para hacer el cambio de fin de línea tenemos varias opciones, y las veremos enseguida.

Reabrir el archivo con el formato correcto

Si ya tenemos abierto el archivo, la forma más sencilla de convertirlo es con la función revert-buffer-with-coding-system, que puede ser invocado con C-x C-m r dos.

Reemplazando el texto

Podemos usar el comando para reemplazar texto (query-replace), generalmente asigna a las teclas M-%:

M-x query-replace C-q C-m RET

usando elisp

Si queremos convertir automáticamente los archivos al abrirlos, la siguiente función hace el truco:

(defun no-junk-please-were-unixish ()
  (let ((coding-str (symbol-name buffer-file-coding-system)))
    (when (string-match "-\\(?:dos\\|mac\\)$" coding-str)
      (set-buffer-file-coding-system 'unix))))

(add-hook 'find-file-hooks 'no-junk-please-were-unixish)

O podemos indicar el tipo de codificación manualmente con set-buffer-coding-system, para hacer un poco más fácil de recordar se puede usar el siguiente código:

(defun dos2unix ()
      "Not exactly but it's easier to remember"
      (interactive)
      (set-buffer-file-coding-system 'unix 't) )

Conclusión

Existen muchas formas de convertir los finales de línea, las que yo compartí no son las únicas, pero tal vez sean las más prácticas.