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 \r
y \n
ya están escritos en el propio script, por lo que debe ser que Bash interprete los \r
aquí.
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\n
nuevamente 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\n
contaminan \n
el 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 autocrlf
indicador 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/63
algo similar en lugar del nombre del script original.
[1] Por supuesto, en lugar de usar binfmt_misc puedes simplemente crear un /bin/bash^M
enlace 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:
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 dos2unix
resultados y luego llamar bash
a la canalización con nombre.
Aquí tengo el original tmp.sh
con 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 cat
comando ha seguido su curso, el dos2unix <tmp.sh >ftmp.sh
comando que se bloqueó anteriormente se cerró.
Entonces, podemos configurar la dos2unix
escritura 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.sh
abrir 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.sh
en Linux ejecutará la última versión guardada.
El problema con esto es que comandos como read -p "Enter user: " user
ese 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 ; de hecho, tenía un 0>1&
o lo que sea al while
comando de bucle.sh
script wget
que también comenzó a repetirse de esa manera, y simplemente agregar un explícito exit
al final del .sh
script 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 -p
comandos 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" #