web-dev-qa-db-fra.com

Les scripts Shell sont-ils sensibles au codage et aux fins de ligne?

Je crée une application NW.js sur Mac et je souhaite exécuter l'application en mode dev en double-cliquant sur une icône. Première étape, j'essaie de faire fonctionner mon script Shell.

En utilisant VSCode sous Windows (je voulais gagner du temps), j'ai créé un run-nw fichier à la racine de mon projet, contenant ceci:

#!/bin/bash

cd "src"
npm install

cd ..
./tools/nwjs-sdk-v0.17.3-osx-x64/nwjs.app/Contents/MacOS/nwjs "src" &

mais j'obtiens cette sortie:

$ 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

Je ne comprends vraiment pas:

  • il semble qu'il faut des lignes vides comme commandes. Dans mon éditeur (VSCode), j'ai essayé de remplacer \r\n avec \n (dans le cas où le \r crée des problèmes) mais cela ne change rien.
  • il semble qu'il ne trouve pas les dossiers (avec ou sans l'instruction dirname), ou peut-être qu'il ne connaît pas la commande cd?
  • il semble qu'il ne comprenne pas l'argument install à npm
  • la partie qui me fait vraiment bizarre, c'est qu'il exécute toujours l'application (si j'ai fait un npm install manuellement)...

Incapable de le faire fonctionner correctement, et soupçonnant quelque chose de bizarre avec le fichier lui-même, j'en ai créé un nouveau directement sur le Mac, en utilisant vim cette fois. J'ai entré exactement les mêmes instructions, et ... maintenant cela fonctionne sans aucun problème.
Un diff sur les deux fichiers révèle une différence exactement nulle.

Quelle peut être la différence? Qu'est-ce qui peut empêcher le premier script de fonctionner? Comment le savoir?

Mise à jour

En suivant les recommandations de la réponse acceptée, après le retour des mauvaises fins de ligne, j'ai vérifié plusieurs choses. Il s'avère que depuis que j'ai copié mon ~/.gitconfig depuis ma machine Windows, j'avais autocrlf=true, donc chaque fois que je modifiais le fichier bash sous Windows, il redéfinissait les fins de ligne sur \r\n.
Donc, en plus d'exécuter dos2unix (que vous devrez installer avec Homebrew sur mac), si vous utilisez Git, vérifiez votre configuration.

35
thomasb

Oui. Les scripts bash sont sensibles aux fins de ligne, à la fois dans le script lui-même et dans les données qu'il traite. Ils doivent avoir des fins de ligne de style Unix, c'est-à-dire que chaque ligne se termine par un caractère de saut de ligne (décimal 10, hex 0A en ASCII).

Terminaisons de ligne DOS/Windows dans le script

Avec les fins de ligne de type Windows ou DOS, chaque ligne se termine par un retour chariot suivi d'un caractère de saut de ligne. Si un fichier de script a été enregistré avec des fins de ligne Windows, Bash voit le fichier comme

#!/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

Remarque: J'ai utilisé la notation caret pour représenter les caractères non imprimables, c'est-à-dire ^M est utilisé pour représenter les caractères de retour chariot (représentés par \r dans d'autres contextes); il s'agit de la même technique utilisée par cat -v et Vim.

Dans ce cas, le retour chariot (^M ou \r) n'est pas traité comme un espace. Bash interprète la première ligne après le Shebang (consistant en un seul caractère de retour chariot) comme le nom d'une commande/d'un programme à exécuter.

  • Puisqu'il n'y a pas de commande nommée ^M, il imprime : command not found
  • Puisqu'il n'y a pas de répertoire nommé "src"^M (ou src^M), il imprime : No such file or directory
  • Ça passe install^M au lieu de install comme argument de npm, ce qui fait que npm se plaint.

Terminaisons de ligne DOS/Windows dans les données d'entrée

Comme ci-dessus, si vous avez un fichier d'entrée avec des retours chariot:

hello^M
world^M

alors il semblera tout à fait normal dans les éditeurs et lors de l'écriture à l'écran, mais les outils peuvent produire des résultats étranges. Par exemple, grep ne trouvera pas de lignes qui sont évidemment là:

$ grep 'hello$' file.txt || grep -x "hello" file.txt
(no match because the line actually ends in ^M)

Le texte ajouté remplacera plutôt la ligne car le retour chariot déplace le curseur au début de la ligne:

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

La comparaison des chaînes semblera échouer, même si les chaînes semblent être les mêmes lors de l'écriture sur l'écran:

$ 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 solution consiste à convertir le fichier pour utiliser des fins de ligne de style Unix. Il existe plusieurs façons de procéder:

  1. Cela peut être fait en utilisant le dos2unix programme:

    dos2unix filename
    
  2. Ouvrez le fichier dans un éditeur de texte capable (Sublime, Notepad ++, not Notepad) et configurez-le pour enregistrer des fichiers avec des fins de ligne Unix, par exemple, avec Vim, exécutez la commande suivante avant de (re) sauvegarder:

    :set fileformat=unix
    
  3. Si vous disposez d'une version de l'utilitaire sed qui prend en charge -i ou --in-place option, par exemple, GNU sed, vous pouvez exécuter la commande suivante pour supprimer les retours chariot finaux:

    sed -i 's/\r$//' filename
    

    Avec d'autres versions de sed, vous pouvez utiliser la redirection de sortie pour écrire dans un nouveau fichier. Veillez à utiliser un nom de fichier différent pour la cible de redirection (il peut être renommé ultérieurement).

    sed 's/\r$//' filename > filename.unix
    
  4. De même, le filtre de traduction tr peut être utilisé pour supprimer les caractères indésirables de son entrée:

    tr -d '\r' <filename >filename.unix
    

Cygwin Bash

Avec le port Bash pour Cygwin, il existe une option igncr personnalisée qui peut être définie pour ignorer les retours de ligne en fin de ligne (probablement parce que beaucoup de ses utilisateurs utilisent des programmes Windows natifs pour modifier leurs fichiers texte). Ceci peut être activé pour le shell actuel en exécutant set -o igncr.

La définition de cette option s'applique uniquement au processus shell en cours , ce qui peut être utile lorsque sourcing fichiers avec retours de chariot superflus. Si vous rencontrez régulièrement des scripts Shell avec des fins de ligne DOS et que vous souhaitez que cette option soit définie de manière permanente, vous pouvez définir une variable d'environnement appelée SHELLOPTS (toutes les majuscules) pour inclure igncr. Cette variable d'environnement est utilisée par Bash pour définir les options du shell au démarrage (avant de lire les fichiers de démarrage).

Utilitaires utiles

L'utilitaire file est utile pour voir rapidement quelles fins de ligne sont utilisées dans un fichier texte. Voici ce qu'il imprime pour chaque type de fichier:

  • Fin de ligne Unix: Bourne-Again Shell script, ASCII text executable
  • Terminaisons de ligne Mac: Bourne-Again Shell script, ASCII text executable, with CR line terminators
  • Fin de ligne DOS: Bourne-Again Shell script, ASCII text executable, with CRLF line terminators

La version GNU de l'utilitaire cat a un -v, --show-nonprinting option qui affiche les caractères non imprimables.

Le dos2unix L'utilitaire est spécialement conçu pour convertir des fichiers texte entre les fins de ligne Unix, Mac et DOS.

Liens utiles

Wikipedia a un excellent article couvrant les nombreuses façons différentes de marquer la fin d'une ligne de texte, l'historique de ces encodages et la façon dont les nouvelles lignes sont traitées dans différents systèmes d'exploitation, langages de programmation et protocoles Internet (par exemple, FTP).

Fichiers avec des fins de ligne Mac OS classiques

Avec Mac OS classique (pré-OS X), chaque ligne se terminait par un retour chariot (décimal 13, hex 0D en ASCII). Si un fichier de script était enregistré avec de telles fins de ligne, Bash ne verrait qu'une seule longue ligne comme ceci:

#!/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

Puisque cette longue ligne unique commence par un octothorpe (#), Bash traite la ligne (et le fichier entier) comme un seul commentaire.

Remarque: En 2001, Apple a lancé Mac OS X qui était basé sur le système d'exploitation dérivé de BSD NeXTSTEP . Par conséquent, OS X utilise également LF de style Unix -seulement les fins de ligne et depuis lors, les fichiers texte terminés par un CR sont devenus extrêmement rares. Néanmoins, je pense qu'il vaut la peine de montrer comment Bash tenterait d'interpréter de tels fichiers.

55
Anthony Geoghegan

Sur les produits JetBrains (PyCharm, PHPStorm, IDEA, etc.), vous devrez click sur CRLF/LF pour basculer entre les deux types de séparateurs de ligne (\r\n et \n).

enter image description hereenter image description here

3
Pedro Lobito

Une autre façon de se débarrasser du caractère CR ('\ r') indésirable est d'exécuter la commande tr, par exemple:

$ tr -d '\r' < dosScript.py > nixScript.py
2

Venant d'un doublon, si le problème est que vous avez des fichiers dont names contiennent ^M à la fin, vous pouvez les renommer avec

for f in *$'\r'; do
    mv "$f" "${f%$'\r'}"
done

Vous voulez corriger correctement ce qui a causé la rupture de ces noms en premier lieu (probablement un script qui les a créés devrait être dos2unixed puis relancez?) mais parfois ce n'est pas possible.

1
tripleee

La manière la plus simple sur MAC/Linux - créez un fichier en utilisant la commande 'touch', ouvrez ce fichier avec VI ou VIM, collez votre code et enregistrez. Cela supprimerait automatiquement les caractères Windows.

0
danR