¿Son los scripts de shell sensibles a la codificación y a las terminaciones de línea

bash shell sh


Estoy haciendo una aplicación de NW.js en Mac,y quiero ejecutar la aplicación en modo de desarrollo 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 tengo 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)

npm@3.10.3 /usr/local/lib/node_modules/npm  
: command not found  
: No such file or directory  
: command not found

Realmente no lo 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 comando cd ?
  • parece que no entiende el argumento de install de npm
  • la parte que realmente me distingue es que todavía ejecuta la aplicación (si hice una npm install manualmente) ...

Al no poder hacerlo funcionar correctamente,y sospechando algo raro con el propio archivo,creé uno nuevo directamente en el Mac,usando vim esta vez.Introduje exactamente las mismas instrucciones,y...ahora funciona sin ningún problema.
Una diferencia en los dos archivos revela exactamente cero diferencias.

¿Cuál puede ser la diferencia? ¿Qué puede hacer que el primer guión no funcione? ¿Cómo puedo averiguarlo?

Update

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 .
Así que,además de ejecutar dos2unix (que tendrás que instalar usando Homebrew en mac),si estás usando Git,comprueba tu configuración.




Answer 1 Anthony Geoghegan


Si. 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).

Las líneas de DOS/Windows terminan en el guión

Con finales de línea al estilo Windows o DOS,cada línea se termina con un Retorno de Carro seguido de un carácter de Alimentación de Línea.Si un archivo de guión fue guardado 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 (o src^M ), imprime : No such file or directory existe tal archivo o directorio
  • Pasa install^M lugar de install como argumento para npm ,lo que hace que npm se queje.

Los finales de línea de DOS/Windows en los datos de entrada

Como arriba,si tienes 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 añadido sobreescribirá en cambio la línea porque el retorno del carro mueve el cursor al principio de la línea:

$ sed -e 's/$/!/' file.txt
!ello
!orld

La comparación de cuerdas parecerá fallar,aunque las cuerdas 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

Solutions

La solución es convertir el archivo para usar terminaciones de línea de estilo Unix.Hay varias maneras de lograr esto:

  1. Esto se puede hacer usando el programa dos2unix :

    dos2unix filename
  2. 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
  3. Si tiene una versión de la utilidad sed que admite la opción -i o --in-place , por ejemplo, GNU sed , 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
  4. 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 .

Al establecer esta opción sólo se aplica a la actual proceso de shell para que pueda ser útil al abastecimiento de archivos con retornos de carro extraños. Si regularmente encuentra scripts de shell con terminaciones de línea de DOS y desea que esta opción se establezca de forma permanente, 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 sistema operativo NeXTSTEP derivado de BSD . Como resultado, OS X también utiliza 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.




Answer 2 CONvid19


En los productos de JetBrains (PyCharm, PhpStorm, idea, etc), que necesita para click el CRLF / LF para alternar entre los dos tipos de separadores de línea ( \r\n y \n ).

enter image description here enter image description here




Answer 3 Igor Soudakevitch


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



Answer 4 tripleee


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.

La sintaxis $'\r' es específica de Bash; Si tiene un shell diferente, tal vez necesite usar alguna otra notación. Quizás vea también la diferencia entre sh y bash




Answer 5 danR


La forma más simple en MAC/Linux-crear un archivo usando el comando 'touch',abrir este archivo con el editor VI o VIM,pegar el código y guardar.Esto eliminará automáticamente los caracteres de Windows.