CyberSDF

Reprenez la main sur votre numérique

bad interpreter : le retour chariot collé au nom de /bin/bash

un poste de travail sobre vu de trois quarts, ecran eteint, clavier mecanique, carnet ouvert

Un script écrit sous Windows refuse de partir, et le shell répond bad interpreter ou cannot execute. La cause tient dans un octet invisible : le retour chariot qui termine la première ligne reste collé au chemin de l’interpréteur, si bien que le noyau cherche un fichier nommé /bin/bash suivi d’un \r. Les sources du noyau Linux ne retirent que les espaces et les tabulations en fin de ligne avant cette recherche. Convertir le fichier en fins de ligne Unix suffit à remettre le script en marche, et la rubrique Linux couvre les autres pannes de lancement.

Sommaire
  1. Le noyau ouvre l’interpréteur retour chariot compris, et execve rend ENOENT
  2. Repérer le retour chariot puis convertir le fichier, en cinq commandes
  3. Le message dépend de la version de bash, la cause reste identique
  4. bash deploy.sh passe pendant que ./deploy.sh échoue
  5. Permission denied désigne deux autres pannes, que le retour chariot ne provoque pas
  6. Après la conversion, cinq cas décident de la suite

Le noyau ouvre l’interpréteur retour chariot compris, et execve rend ENOENT

La ligne #! est lue par le noyau, qui prend le nom de l’interpréteur au mot, retour chariot compris. La fonction load_script() de fs/binfmt_script.c parcourt cette ligne jusqu’au premier passage à la ligne, puis retire les espaces et les tabulations qui la terminent avant de confier le chemin à open_exec(). Un \r n’entre pas dans cette catégorie, il reste donc dans le nom, et /bin/bash devient un chemin qui n’existe pas. Le même fichier borne la lecture à sizeof(bprm->buf), soit BINPRM_BUF_SIZE, déclarée à 256 octets dans include/uapi/linux/binfmts.h, et le nom garde sa validité après la fusion de /bin dans /usr/bin, que décrivent les chemins de /bin et de bash. L’appel système rend alors ENOENT, ce que la page execve(2) du projet man-pages résume par « path or a script or ELF interpreter does not exist ».

bash: ./deploy.sh: /bin/bash^M: bad interpreter: No such file or directory

Cette première ligne vient de bash 5.1.4, lancé sur Ubuntu 24.04.4 LTS, et le ^M est la notation de cat -A pour l’octet \r.

bash: ./deploy.sh: cannot execute: required file not found

Le même fichier, lancé sous bash 5.2.21 livré par la même Ubuntu, produit cette seconde ligne, où le nom de l’interpréteur n’apparaît plus.

Un shebang passé par env donne un troisième texte, parce que le noyau trouve /usr/bin/env et que env cherche ensuite l’interpréteur manquant.

/usr/bin/env: ‘bash\r’: No such file or directory
/usr/bin/env: use -[v]S to pass options in shebang lines

Repérer le retour chariot puis convertir le fichier, en cinq commandes

Trois commandes prouvent le retour chariot, deux le retirent. Le fichier de travail s’appelle deploy.sh, il porte le bit d’exécution et se lance par ./deploy.sh, comme tout script qui réunit un shebang correct et un droit d’exécution, les deux bases de l’écriture d’un script shell. La détection commence par file, qui nomme le type réel du fichier, puis par od -c, qui affiche les octets de la première ligne. La page dos2unix(1) livrée par Debian 13 décrit ensuite le mode fichier ancien, celui qui convertit sur place en conservant propriétaire, groupe et droits de lecture et d’écriture. Chaque étape se termine par la sortie attendue, qui sert de critère d’arrêt.

  1. Nommer le type réel du fichier et lire les octets de sa première ligne.

    file deploy.sh
    head -1 deploy.sh | od -c
    

    Ce que vous devez voir : une ligne qui se termine par with CRLF line terminators, puis les octets # ! / b i n / b a s h \r \n. Un \n seul annonce un fichier déjà au format Unix.

  2. Compter les lignes terminées par un retour chariot, sans rien modifier.

    grep -c $'\r$' deploy.sh
    

    Ce que vous devez voir : un nombre supérieur à zéro. La valeur 1 désigne un fichier dont seule la ligne #! porte le retour chariot.

  3. Lister d’abord les fichiers du dossier qui seraient convertis.

    dos2unix -ic *.sh
    

    Ce que vous devez voir : uniquement les chemins des fichiers porteurs de retours chariot, comme l’annonce la page dos2unix(1) pour l’indicateur c. Un fichier absent de la liste n’a rien à convertir.

  4. Convertir le fichier, ou l’arborescence entière.

    dos2unix deploy.sh
    find /home/user/scripts -name '*.sh' -print0 | xargs -0 dos2unix
    

    Ce que vous devez voir : dos2unix: converting file deploy.sh to Unix format... Le manuel donne cette forme find -print0 | xargs -0 pour un arbre de fichiers et rappelle que les deux options vont ensemble sous peine de couper les noms contenant des espaces.

  5. Vérifier le résultat, puis relancer le script.

    file deploy.sh
    ls -l deploy.sh
    ./deploy.sh
    

    Ce que vous devez voir : la mention with CRLF a disparu, le mode commence toujours par -rwx, et le script s’exécute sans message d’erreur.

Une machine sans dos2unix se dépanne avec sed -i 's/\r$//' deploy.sh, qui suffit sur un fichier unique. La redirection tr -d '\r' < deploy.sh > deploy-new.sh convient aussi, à condition de rendre le bit d’exécution au nouveau fichier, créé en 644.

Le message dépend de la version de bash, la cause reste identique

La formulation change avec la version du shell, et les réponses trouvées en ligne citent souvent un texte que les versions récentes n’écrivent plus. La chaîne bad interpreter figure dans execute_cmd.c de bash 5.0 et de bash 5.1 sous la forme sys_error (_("%s: %s: bad interpreter"), command, interp). Bash 5.2 ajoute une seconde ligne, internal_error (_("%s: cannot execute: required file not found"), command), qui prend le relais dès que l’interpréteur est introuvable. Debian 13 livre bash 5.2.37-2+b9 et Ubuntu 24.04 livre bash 5.2.21-2ubuntu4, deux versions qui affichent donc la formulation récente.

Message affiché et code de sortie pour un script dont la ligne #! se termine par un retour chariot, mesurés sur Ubuntu 24.04.4 LTS.
Version du shell Distribution d’origine du paquet Message affiché Code de sortie
bash 5.0.3 paquet bash 5.0-4 de Debian 10 bash: ./deploy.sh: /bin/bash^M: bad interpreter: No such file or directory 126
bash 5.1.4 paquet bash 5.1-2+deb11u1 de Debian 11 même ligne que bash 5.0 126
bash 5.2.21 Ubuntu 24.04 LTS bash: ./deploy.sh: cannot execute: required file not found 127
dash 0.5.12 Ubuntu 24.04 LTS, cible du lien /bin/sh dash: 1: ./deploy.sh: not found 127

Les paquets bash de Debian 10 et 11 ont été extraits dans un dossier temporaire puis lancés tels quels sur Ubuntu 24.04.4 LTS. Le code de sortie compte pour une tâche planifiée ou pour un test sur $? : le manuel de référence de bash fixe 127 quand la commande est introuvable et 126 quand elle existe sans droit d’exécution. Une divergence entre sources mérite d’être connue ici, le même manuel rappelant que le découpage de la ligne #! varie selon les systèmes et que d’anciennes versions d’Unix limitaient le nom et l’argument à 32 caractères, là où le noyau Linux lit cette ligne dans 256 octets.

bash deploy.sh passe pendant que ./deploy.sh échoue

L’invocation explicite du shell masque le défaut, et c’est ce qui fait croire à un dépannage réussi. Avec la commande bash deploy.sh, le noyau exécute /bin/bash et lui remet le chemin du fichier : bash relit le fichier ligne par ligne, la ligne #! devient un commentaire, et le retour chariot ne gêne plus la recherche de l’interpréteur. Cet octet reste pourtant dans le texte et se colle au dernier mot de chaque commande. Un ls -l /tmp/ct reçoit ainsi un argument que personne n’a écrit, et l’erreur le montre avec un caractère d’échappement.

ls: cannot access '/tmp/ct'$'\r': No such file or directory

Cette ligne vient d’un script en CRLF lancé par bash, dont la commande était ls -l /tmp/ct : la suite du script continue et se termine par un code 0. Une affectation LOG=/tmp/ct place elle aussi l’octet dans la valeur, et od -c le retrouve juste avant le crochet fermant.

Permission denied désigne deux autres pannes, que le retour chariot ne provoque pas

Le message Permission denied accompagne le retour chariot dans les recherches, alors qu’il vient d’ailleurs dans la plupart des cas. Le bit d’exécution manquant produit bash: ./deploy.sh: Permission denied, qu’un chmod 755 corrige, et sa notation relève des permissions des utilisateurs et des groupes. Un interpréteur nommé sans ce bit donne bad interpreter: Permission denied, seule occurrence des deux termes sur une même ligne. La page execve(2) du projet man-pages classe ces refus sous EACCES et cite trois situations : le droit d’exécution absent sur le fichier ou sur l’interpréteur, un nom qui ne désigne pas un fichier ordinaire, et une partition montée noexec.

Une conversion par redirection crée un fichier neuf en 644, et la tentative de lancement échoue avec le code 126.

tr -d '\r' < deploy.sh > deploy-new.sh
./deploy-new.sh
chmod 755 deploy-new.sh

Ce que vous devez voir : bash: ./deploy-new.sh: Permission denied avant le chmod, puis l’exécution normale. Le contrôle d’une partition se fait par findmnt -no OPTIONS -T deploy.sh, dont la sortie ne doit pas contenir noexec.

Après la conversion, cinq cas décident de la suite

Le retour chariot provoque une seule panne, la recherche d’un interpréteur qui n’existe pas. Les questions qui reviennent ensuite portent sur l’outil à garder, le traitement d’un dossier entier, les fichiers qui refusent encore de partir et les conversions partielles. Chacune se règle par une commande, et les valeurs citées viennent des sources officielles ou des mesures faites sur Ubuntu 24.04.4 LTS.

Pourquoi ./deploy.sh échoue-t-il alors que bash deploy.sh démarre ?

Le chemin direct passe par execve(2) et par la ligne #!, que le noyau lit avec le retour chariot ; la forme bash deploy.sh remet le fichier au shell, qui n’ouvre aucun interpréteur. Le script démarre donc dans le second cas alors que chaque ligne garde son octet, lequel se retrouve dans le dernier argument des commandes et dans la valeur des variables. Un script fautif peut ainsi se terminer par un code 0 en ayant échoué à l’intérieur, et le contrôle décisif reste file deploy.sh.

dos2unix mérite-t-il l’installation, ou sed -i suffit-il ?

Sur un fichier unique, sed -i 's/\r$//' deploy.sh fait le travail sans rien installer. La page dos2unix(1) livrée par Debian 13 apporte deux différences : les fichiers binaires sont écartés par défaut grâce à l’option -s, et le mode fichier ancien conserve propriétaire, groupe et droits, là où une redirection crée un fichier neuf. Ce mode abandonne la conversion s’il ne peut pas préserver ces valeurs, ce que l’option --allow-chown lève.

Comment convertir tous les .sh d’un dossier ?

La page dos2unix(1) donne la forme find /home/user/scripts -name '*.sh' -print0 | xargs -0 dos2unix et précise que -print0 avec -0 évite de couper les noms qui contiennent un espace. L’option -ic liste d’abord les fichiers concernés sans rien modifier, sous la forme dos2unix -ic *.sh. Le code de retour vaut 0 en cas de succès, et l’option -q le force à 0 sauf lorsqu’une option de ligne de commande est refusée.

Le retour chariot peut-il produire Permission denied ?

Dans les cas mesurés sur Ubuntu 24.04.4 LTS, il produit ENOENT et jamais EACCES. Le refus d’exécution vient du bit absent, d’un interpréteur non exécutable ou d’une partition montée noexec, trois cas que la page execve(2) range sous EACCES. Le contrôle tient en deux commandes, ls -l deploy.sh pour le mode et findmnt -no OPTIONS -T deploy.sh pour les options de montage.

Un fichier mi-CRLF mi-LF, signalé « with CRLF, LF line terminators », part-il quand même ?

Oui si le mélange épargne la première ligne, parce que le noyau n’interprète que celle-là. La mention with CRLF, LF line terminators signale un fichier mixte, où certaines lignes finissent en CRLF et d’autres en LF, tandis qu’un fichier entièrement converti par Windows porte with CRLF line terminators. La commande head -1 deploy.sh | od -c tranche pour la ligne #! : un \r \n en fin de sortie impose la conversion.

Sources : dos2unix(1) livré par Debian 13, options -i, -ic, -s, -q et mode fichier ancien, fs/binfmt_script.c du noyau Linux, lecture de la ligne #! et retrait des espaces de fin, sources officielles de bash 5.1 publiées par le projet GNU, fichier execute_cmd.c, execve(2) du projet man-pages, erreurs ENOENT et EACCES, manuel de référence de bash, découpage de la ligne #! et codes 126 et 127.