bash - programacion - ¿Los scripts de shell son sensibles a la codificación y los finales de línea?
shell script linux (5)
Estoy creando una aplicación NW.js en Mac, y quiero ejecutar la aplicación en modo dev haciendo doble clic en un icono. Primer paso, estoy tratando de hacer que mi script de shell funcione.
Usando VSCode en Windows (quería ganar tiempo), he creado un archivo
run-nw
en la raíz de mi proyecto, que contiene esto:
#!/bin/bash
cd "src"
npm install
cd ..
./tools/nwjs-sdk-v0.17.3-osx-x64/nwjs.app/Contents/MacOS/nwjs "src" &
pero obtengo esta salida:
$ sh ./run-nw
: command not found
: No such file or directory
: command not found
: No such file or directory
Usage: npm <command>
where <command> is one of: (snip commands list)
(snip npm help)
[email protected] /usr/local/lib/node_modules/npm
: command not found
: No such file or directory
: command not found
Realmente no entiendo:
-
parece que toma líneas vacías como comandos.
En mi editor (VSCode) he intentado reemplazar
/r/n
con/n
(en caso de que/r
cree problemas) pero no cambia nada. -
parece que no encuentra las carpetas (con o sin la instrucción
dirname
), o tal vez no sabe sobre el comandocd
? -
parece que no entiende el argumento de
install
denpm
-
la parte que realmente me distingue es que todavía ejecuta la aplicación (si hice una
npm install
manualmente) ...
Al no poder hacer que funcione correctamente, y sospechando algo extraño con el archivo en sí, creé uno nuevo directamente en la Mac, usando vim esta vez.
Ingresé exactamente las mismas instrucciones y ... ahora funciona sin ningún problema.
Una diferencia en los dos archivos revela exactamente cero diferencia.
¿Cuál puede ser la diferencia? ¿Qué puede hacer que el primer script no funcione? ¿Cómo puedo averiguarlo?
Actualizar
Siguiendo las recomendaciones de la respuesta aceptada, después de que volvieron los finales de línea equivocados, verifiqué varias cosas.
Resulta que, dado que copié mi
~/.gitconfig
de mi máquina Windows, tenía
autocrlf=true
, así que cada vez que modificaba el archivo bash en Windows, restablecía los finales de línea a
/r/n
.
Entonces, además de ejecutar dos2unix (que tendrás que instalar usando Homebrew en mac), si estás usando Git, verifica tu configuración.
La forma más sencilla en MAC / Linux: cree un archivo con el comando ''táctil'', abra este archivo con el editor VI o VIM, pegue su código y guárdelo. Esto eliminaría automáticamente los caracteres de Windows.
Sí. Las secuencias de comandos Bash son sensibles a los finales de línea, tanto en la secuencia de comandos como en los datos que procesa. Deben tener terminaciones de línea de estilo Unix, es decir, cada línea termina con un carácter de avance de línea (decimal 10, hexadecimal 0A en ASCII).
Finalizaciones de línea de DOS / Windows en el script
Con las terminaciones de línea estilo Windows o DOS, cada línea termina con un retorno de carro seguido de un carácter de avance de línea. Si se guardó un archivo de script con terminaciones de línea de Windows, Bash ve el archivo como
#!/bin/bash^M
^M
cd "src"^M
npm install^M
^M
cd ..^M
./tools/nwjs-sdk-v0.17.3-osx-x64/nwjs.app/Contents/MacOS/nwjs "src" &^M
Nota: He usado la notación de intercalación para representar caracteres que no se imprimen, es decir,
^M
se usa para representar los caracteres de retorno de carro (representados como
/r
en otros contextos);
Esta es la misma técnica utilizada por
cat -v
y Vim.
En este caso, el retorno de carro (
^M
o
/r
) no se trata como un espacio en blanco.
Bash interpreta la primera línea después del shebang (que consiste en un solo carácter de retorno de carro) como el nombre de un comando / programa a ejecutar.
-
Como no hay un comando llamado
^M
, imprime: command not found
-
Como no hay un directorio llamado
"src"^M
(osrc^M
), imprime: No such file or directory
existe: No such file or directory
-
Pasa
install^M
lugar deinstall
como argumento paranpm
que hace quenpm
queje.
Terminaciones de línea de DOS / Windows en datos de entrada
Como en el caso anterior, si tiene un archivo de entrada con retornos de carro:
hello^M
world^M
entonces se verá completamente normal en los editores y al escribirlo en la pantalla, pero las herramientas pueden producir resultados extraños.
Por ejemplo,
grep
no podrá encontrar líneas que obviamente están allí:
$ grep ''hello$'' file.txt || grep -x "hello" file.txt
(no match because the line actually ends in ^M)
El texto agregado sobrescribirá en su lugar la línea porque el carro devuelve mueve el cursor al inicio de la línea:
$ sed -e ''s/$/!/'' file.txt
!ello
!orld
La comparación de cadenas parecerá fallar, a pesar de que las cadenas parecen ser las mismas al escribir en la pantalla:
$ a="hello"; read b < file.txt
$ if [[ "$a" = "$b" ]]
then echo "Variables are equal."
else echo "Sorry, $a is not equal to $b"
fi
Sorry, hello is not equal to hello
Soluciones
La solución es convertir el archivo para usar terminaciones de línea de estilo Unix. Hay varias maneras de lograr esto:
-
Esto se puede hacer usando el programa
dos2unix
:dos2unix filename
-
Abra el archivo en un editor de texto capaz (Sublime, Notepad ++, no Notepad) y configúrelo para guardar archivos con terminaciones de línea Unix, por ejemplo, con Vim, ejecute el siguiente comando antes de (re) guardar:
:set fileformat=unix
-
Si tiene una versión de la utilidad
sed
que admite la opción-i
o--in-place
, por ejemplo, GNUsed
, puede ejecutar el siguiente comando para eliminar los retornos del carro final:sed -i ''s//r$//'' filename
Con otras versiones de
sed
, puede usar la redirección de salida para escribir en un nuevo archivo. Asegúrese de utilizar un nombre de archivo diferente para el objetivo de redirección (se puede cambiar el nombre más adelante).sed ''s//r$//'' filename > filename.unix
-
Del mismo modo, el filtro de traducción
tr
se puede utilizar para eliminar caracteres no deseados de su entrada:tr -d ''/r'' <filename >filename.unix
Cygwin Bash
Con el puerto Bash para Cygwin, hay una opción de
igncr
personalizada que se puede configurar para ignorar el retorno de carro en las terminaciones de línea (presumiblemente porque muchos de sus usuarios usan programas nativos de Windows para editar sus archivos de texto).
Esto se puede habilitar para el shell
actual
ejecutando
set -o igncr
.
Establecer esta opción solo se aplica al proceso de shell
actual
, por lo que puede ser útil cuando se obtienen archivos con retornos de carro extraños.
Si regularmente encuentra scripts de shell con finales de línea de DOS y desea que esta opción se establezca permanentemente, puede establecer una variable de entorno llamada
SHELLOPTS
(todas las letras mayúsculas) para incluir
igncr
.
Bash utiliza esta variable de entorno para establecer las opciones de shell cuando se inicia (antes de leer los archivos de inicio).
Utilidades útiles
La utilidad de
file
es útil para ver rápidamente qué terminaciones de línea se utilizan en un archivo de texto.
Esto es lo que imprime para cada tipo de archivo:
-
Terminaciones de línea Unix:
Bourne-Again shell script, ASCII text executable
-
Terminaciones de línea Mac:
Bourne-Again shell script, ASCII text executable, with CR line terminators
-
Terminaciones de línea de DOS:
Bourne-Again shell script, ASCII text executable, with CRLF line terminators
La versión GNU de la utilidad
cat
tiene una opción
-v, --show-nonprinting
que muestra caracteres que no se imprimen.
La utilidad
dos2unix
está específicamente escrita para convertir archivos de texto entre finales de línea Unix, Mac y DOS.
Enlaces útiles
Wikipedia tiene un excelente artículo que cubre las diferentes formas de marcar el final de una línea de texto, el historial de tales codificaciones y cómo se tratan las nuevas líneas en diferentes sistemas operativos, lenguajes de programación y protocolos de Internet (por ejemplo, FTP).
Archivos con finales de línea clásicos de Mac OS
Con Classic Mac OS (pre-OS X), cada línea se terminó con un retorno de carro (decimal 13, hexadecimal 0D en ASCII). Si se guardara un archivo de script con tales finales de línea, Bash solo vería una línea larga así:
#!/bin/bash^M^Mcd "src"^Mnpm install^M^Mcd ..^M./tools/nwjs-sdk-v0.17.3-osx-x64/nwjs.app/Contents/MacOS/nwjs "src" &^M
Dado que esta única línea larga comienza con un octothorpe (
#
), Bash trata la línea (y todo el archivo) como un solo comentario.
Nota: En 2001, Apple lanzó Mac OS X que se basó en el NeXTSTEP operativo NeXTSTEP derivado de BSD. Como resultado, OS X también usa terminaciones de línea estilo LF de Unix y desde entonces, los archivos de texto terminados con un CR se han vuelto extremadamente raros. Sin embargo, creo que vale la pena mostrar cómo Bash intentaría interpretar dichos archivos.
Una forma más de deshacerse del carácter CR no deseado (''/ r'') es ejecutar el comando
tr
, por ejemplo:
$ tr -d ''/r'' < dosScript.py > nixScript.py
Viniendo de un duplicado, si el problema es que tiene archivos cuyos
nombres
contienen
^M
al final, puede cambiarles el nombre con
for f in *$''/r''; do
mv "$f" "${f%$''/r''}"
done
En primer lugar, desea corregir lo que causó que estos archivos tengan nombres rotos (probablemente, un script que los creó debería ser editado
dos2unix
y luego volver a ejecutarse), pero a veces esto no es factible.