¿Manejando el script Bash con CRLF (retorno de carro) en Linux como en MSYS2?

¿Manejando el script Bash con CRLF (retorno de carro) en Linux como en MSYS2?

Digamos que tengo el siguiente script trivial tmp.sh:

echo "testing"
stat .
echo "testing again"

Por trivial que sea, tiene \r\n(es decir, CRLF, es decir, retorno de carro+avance de línea) como finales de línea. Dado que la página web no conservará los finales de línea, aquí hay un volcado hexadecimal:

$ hexdump -C tmp.sh 
00000000  65 63 68 6f 20 22 74 65  73 74 69 6e 67 22 0d 0a  |echo "testing"..|
00000010  73 74 61 74 20 2e 0d 0a  65 63 68 6f 20 22 74 65  |stat ...echo "te|
00000020  73 74 69 6e 67 20 61 67  61 69 6e 22 0d 0a        |sting again"..|
0000002e

Ahora tiene finales de línea CRLF, porque el script se inició y desarrolló en Windows, bajo MSYS2. Entonces, cuando lo ejecuto en Windows 10 en MSYS2, obtengo lo esperado:

$ bash tmp.sh
testing
  File: .
  Size: 0               Blocks: 40         IO Block: 65536  directory
Device: 8e8b98b6h/2391513270d   Inode: 281474976761067  Links: 1
Access: (0755/drwxr-xr-x)  Uid: (197609/      USER)   Gid: (197121/    None)
Access: 2020-04-03 10:42:53.210292000 +0200
Modify: 2020-04-03 10:42:53.210292000 +0200
Change: 2020-04-03 10:42:53.210292000 +0200
 Birth: 2019-02-07 13:22:11.496069300 +0100
testing again

Sin embargo, si copio este script en una máquina Ubuntu 18.04 y lo ejecuto allí, obtengo algo más:

$ bash tmp.sh
testing
stat: cannot stat '.'$'\r': No such file or directory
testing again

En otros scripts con los mismos finales de línea, también recibí este error en Ubuntu bash:

line 6: $'\r': command not found

... probablemente de una línea vacía.

Entonces, claramente, algo en Ubuntu se ahoga en los retornos de carro. He vistoBASH y comportamiento de retorno de carro:

no tiene nada que ver con Bash: \r y \n son interpretados por la terminal, no por Bash

... sin embargo, supongo que eso es sólo para cosas escritas palabra por palabra en la línea de comando; aquí los \ry \nya están escritos en el propio script, por lo que debe ser que Bash interprete los \raquí.

Aquí está la versión de Bash en Ubuntu:

$ bash --version
GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)

... y aquí la versión de Bash en MSYS2:

$ bash --version
GNU bash, version 4.4.23(2)-release (x86_64-pc-msys)

(no parecen muy separados...)

De todos modos, mi pregunta es: ¿hay alguna manera de persuadir a Bash en Ubuntu/Linux para que ignore el \r, en lugar de intentar interpretarlo como un (por así decirlo) "carácter imprimible" (en este caso, es decir, un carácter que podría ser un parte de un comando válido, ¿qué bash interpreta como tal)? EDITAR:sintener que convertir el script en sí (para que siga siendo el mismo, con finales de línea CRLF, si se marca de esa manera, digamos, en git)

EDITAR2: Lo preferiría de esta manera, porque otras personas con las que trabajo podrían volver a abrir el script en el editor de texto de Windows, potencialmente reintroducirlo \r\nnuevamente en el script y confirmarlo; y luego podríamos terminar con un flujo interminable de confirmaciones que podrían no ser más que conversiones que \r\ncontaminan \nel repositorio.

EDITAR2: @Kusalananda en los comentarios mencionados dos2unix( sudo apt install dos2unix); tenga en cuenta que simplemente escribiendo esto:

$ dos2unix tmp.sh 
dos2unix: converting file tmp.sh to Unix format...

... convertirá el archivo en el lugar; Para que salga a la salida estándar, se debe configurar la redirección estándar:

$ dos2unix <tmp.sh | hexdump -C
00000000  65 63 68 6f 20 22 74 65  73 74 69 6e 67 22 0a 73  |echo "testing".s|
00000010  74 61 74 20 2e 0a 65 63  68 6f 20 22 74 65 73 74  |tat ..echo "test|
00000020  69 6e 67 20 61 67 61 69  6e 22 0a                 |ing again".|
0000002b

... y luego, en principio, se podría ejecutar esto en Ubuntu, lo que parece funcionar en este caso:

$ dos2unix <tmp.sh | bash
testing
  File: .
  Size: 20480       Blocks: 40         IO Block: 4096   directory
Device: 816h/2070d  Inode: 1572865     Links: 27
Access: (1777/drwxrwxrwt)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2020-04-03 11:11:00.309160050 +0200
Modify: 2020-04-03 11:10:58.349139481 +0200
Change: 2020-04-03 11:10:58.349139481 +0200
 Birth: -
testing again

Sin embargo, aparte del comando ligeramente complicado de recordar, esto también cambia la semántica de bash, ya que stdin ya no es una terminal; Esto puede haber funcionado con este ejemplo trivial, pero vea, por ejemplohttps://stackoverflow.com/questions/23257247/pipe-a-script-into-bashpor ejemplo de problemas mayores.

Respuesta1

Hasta donde yo sé, no hay forma de decirle a Bash que acepte finales de línea al estilo de Windows.

En situaciones que involucran a Windows, la práctica común es confiar en la capacidad de Git para convertir automáticamente los finales de línea al confirmar, utilizando el autocrlfindicador de configuración. ver por ejemploDocumentación de GitHub sobre finales de línea, que no es específico de GitHub. De esa manera, los archivos se confirman con finales de línea de estilo Unix en el repositorio y se convierten según corresponda para cada plataforma de cliente.

(El problema opuesto no es un problema: MSYS2 funciona bien con finales de línea de estilo Unix, en Windows).

Respuesta2

Deberías usarbinfmt_miscpor eso [1].

Primero, defina un magic que maneje archivos que comiencen con #! /bin/bash<CR><LF>y luego cree un intérprete ejecutable para él. El intérprete puede ser otro guión:

INTERP=/path/to/bash-crlf

echo ",bash-crlf,M,,#! /bin/bash\x0d\x0a,,$INTERP," > /proc/sys/fs/binfmt_misc/register
cat > "$INTERP" <<'EOT'; chmod 755 "$INTERP"
#! /bin/bash
script=$1; shift; exec bash <(sed 's/\r$//' "$script") "$@"
EOT

Pruébalo:

$ printf '%s\r\n' '#! /bin/bash' pwd >/tmp/foo; chmod 755 /tmp/foo
$ cat -v /tmp/foo
#! /bin/bash^M
pwd^M
$ /tmp/foo
/tmp

El intérprete de muestra tiene dos problemas:1.dado que pasa el script a través de un archivo no buscable (una tubería), bash lo leerá byte a byte, de manera muy ineficiente, y2.cualquier mensaje de error hará referencia a /dev/fd/63algo similar en lugar del nombre del script original.

[1] Por supuesto, en lugar de usar binfmt_misc puedes simplemente crear un /bin/bash^Menlace simbólico al intérprete, que también funcionaría en otros sistemas como OpenBSD:

ln -s /path/to/bash-crlf $'/bin/bash\r'

Pero en Linux, los ejecutables shebanged no tienen ninguna ventaja sobre binfmt_misc, y poner basura dentro de los directorios del sistema no es la estrategia correcta y dejará a cualquier administrador de sistemas sacudiendo la cabeza ;-)

Respuesta3

Ok, encontré una solución alternativa a través de:

Enlaces simbólicos "unidos"

Los sistemas Unix modernos tienen una manera de hacer que los datos arbitrarios aparezcan como un archivo, independientemente de cómo estén almacenados:FUSIBLE. Con FUSE, cada operación en un archivo (crear, abrir, leer, escribir, listar directorio, etc.) invoca algún código en un programa, y ​​ese código puede hacer lo que quieras. VerCrea un archivo virtual que en realidad es un comando. Podrías probarguionesofusible, o si te sientes ambicioso, haz el tuyo propio.

... yCrea un archivo virtual que en realidad es un comando

Quizás estés buscando untubería con nombre.

Entonces, este es el enfoque: crear una canalización con nombre, generarle dos2unixresultados y luego llamar basha la canalización con nombre.

Aquí tengo el original tmp.shcon terminaciones de línea CRLF en /tmp; Primero, creemos la tubería con nombre:

tmp$ mkfifo ftmp.sh

Ahora, si ejecuta este comando:

tmp$ dos2unix <tmp.sh >ftmp.sh

... notarás que se bloquea; entonces si lo haces, di:

~$ cat /tmp/ftmp.sh | hexdump -C
00000000  65 63 68 6f 20 22 74 65  73 74 69 6e 67 22 0a 73  |echo "testing".s|
00000010  74 61 74 20 2e 0a 65 63  68 6f 20 22 74 65 73 74  |tat ..echo "test|
00000020  69 6e 67 20 61 67 61 69  6e 22 0a                 |ing again".|
0000002b

... notará que la conversión se ha realizado y, una vez que el catcomando ha seguido su curso, el dos2unix <tmp.sh >ftmp.shcomando que se bloqueó anteriormente se cerró.

Entonces, podemos configurar la dos2unixescritura en la tubería con nombre en un bucle while "interminable":

tmp$ while [ 1 ] ; do dos2unix <tmp.sh >ftmp.sh ; done

... e incluso si es un bucle "estrecho", no debería ser un problema, ya que la mayoría de las veces el comando dentro del bucle while está bloqueando.

Entonces puedo hacer:

~$ bash /tmp/ftmp.sh
testing
  File: .
  Size: 4096        Blocks: 8          IO Block: 4096   directory
Device: 801h/2049d  Inode: 5276132     Links: 7
...
testing again
$

... y claramente, el script funciona bien.

Lo bueno de este enfoque es que puedo tmp.shabrir el original en un editor de texto; y escriba un código nuevo, con terminaciones CRLF, y luego guárdelo tmp.sh; y ejecutarlo bash /tmp/ftmp.shen Linux ejecutará la última versión guardada.

El problema con esto es que comandos como read -p "Enter user: " userese que dependen de la entrada estándar del terminal real fallarán; o mejor dicho no fallar, pero si lo intentas, di esto como/tmp/tmp.sh

echo "testing"
stat .
echo "testing again"
read -p "Enter user: " user
echo "user is: $user"

... entonces esto será el resultado:

$ bash /tmp/ftmp.sh
testing
  File: .
  Size: 4096        Blocks: 8          IO Block: 4096   directory
...
 Birth: -
testing again
Enter user: tyutyu
user is: tyutyu
testing
  File: .
  Size: 4096        Blocks: 8          IO Block: 4096   directory
...
 Birth: -
testing again
Enter user: asd
user is: asd
testing
...

... y así sucesivamente, es decir, la entrada estándar del teclado en la terminal se interpreta correctamente, pero por alguna razón el script comienza a repetirse y se ejecuta desde el principio una y otra vez (lo que no sucede si no tenemos un read -p ...comando en el original tmp.sh). Tal vez haya algunas cosas de redirección (por ejemplo, agregar algo 0>1&o lo que sea al whilecomando de bucle ; de ​​hecho, tenía un .shscript wgetque también comenzó a repetirse de esa manera, y simplemente agregar un explícito exital final del .shscript pareció funcionar para detener el bucle del script) eso también podría manejar esto, pero hasta ahora, el script que necesito usar no tiene read -pcomandos similares, por lo que este enfoque podría funcionar para mí.

Respuesta4

Puede insertar un hash (#) justo al final de cada línea en sus scripts bash. De tal manera, los shells en Unix considerarán el CR solo como un comentario y no les importará.

"Hablando en hexadecimal", cualquier línea debe terminar con

0x23 0x0D 0x0A

Ejemplo:

echo "testing" #
stat . #
echo "testing again" #

información relacionada