scripts script programacion pasar parametros operaciones manejo español ejemplos cadenas aritmeticas bash shell sh

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


En los productos JetBrains (PyCharm, PHPStorm, IDEA, etc.), deberá hacer clic en CRLF / LF para alternar entre los dos tipos de separadores de línea ( /r/n y /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 (o src^M ), imprime : No such file or directory existe : No such file or directory
  • Pasa install^M lugar de install como argumento para npm que hace que npm 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:

  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 .

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.