web-dev-qa-db-fra.com

Comment puis-je utiliser un Shebang dans un script PowerShell?

J'ai plusieurs scripts PowerShell que j'aimerais invoquer directement en tant que commande à partir d'un shell Bash dans Cygwin. Par exemple, si j'écris un script avec le nom de fichier Write-Foo.ps1, Je voudrais l'exécuter comme une commande à partir de n'importe quel répertoire de travail:

$ Write-Foo.ps1 arg1 arg2 ...

Pour ce faire, j'ajoute le script à mon PATH, le rend exécutable et j'inclus l'interpréteur Shebang/hashbang suivant au début du fichier:

#!/usr/bin/env powershell

Write-Host 'Foo'
...

C'est une utilisation (ab) courante du env , mais il dissocie les scripts du préfixe de chemin de Cygwin (/ cygdrive/c/...), au moins pour la déclaration de l'interprète.

Cela fonctionne pour démarrer PowerShell, mais le système transmet le fichier comme un chemin au format Cygwin, que PowerShell ne comprend pas, bien sûr:

Le terme "/cygdrive/c/path/to/Write-Foo.ps1" n'est pas reconnu comme le nom d'une applet de commande, d'une fonction, d'un fichier de script ou d'un programme exploitable.

MSYS (Git Bash) semble traduire correctement le chemin du script et le script s'exécute comme prévu, tant que le chemin d'accès au fichier ne contient aucun espace. Existe-t-il un moyen d'invoquer un script PowerShell directement en s'appuyant sur le Shebang dans Cygwin?

Idéalement, je voudrais également omettre .ps1 extension des noms de script si possible, mais je comprends que je devrai peut-être vivre avec cette limitation. Je veux éviter de créer un alias ou d'encapsuler manuellement les scripts si possible.


Une note rapide pour les utilisateurs Linux/macOS qui trouvent cela.

12
Cy Rossignol

Note rapide pour les utilisateurs Linux/macOS trouvant ceci:

  • Assurez-vous que la commande pwsh ou powershell est dans PATH
  • Utilisez cette directive d'interpréteur: #!/usr/bin/env pwsh
  • Assurez-vous que le script utilise des fins de ligne de style Unix (\n, pas\r\n)

Grâce aux commentaires de briantist , je comprends maintenant que ce n'est pas directement pris en charge pour les versions de PowerShell antérieures à 6.0 sans compromis:

... [ dans PowerShell Core 6. ] ils ont spécifiquement modifié le paramètre de position 0 de ‑Command à ‑File pour que ça marche. ... le message d'erreur que vous obtenez est parce qu'il passe un chemin vers ‑Command...

Un système de type Unix transmet le nom de fichier absolu du script PowerShell à l'interpréteur spécifié par le "Shebang" comme premier argument lorsque nous invoquons le script en tant que commande. En général, cela peut parfois fonctionner pour PowerShell 5 et versions antérieures car PowerShell, par défaut, interprète le nom de fichier du script comme la commande à exécuter.

Cependant, nous ne pouvons pas compter sur ce comportement car lorsque PowerShell gère -Command dans ce contexte, il re - interprète le nom de fichier comme s'il avait été tapé à l'invite , donc le chemin d'un script contenant des espaces ou certains symboles sera casser la "commande" que PowerShell considère comme l'argument. Nous perdons également un peu d'efficacité pour l'étape d'interprétation préliminaire.

Lorsque vous spécifiez le -File paramètre à la place, PowerShell charge le script directement, afin que nous puissions éviter les problèmes que nous rencontrons avec -Command. Malheureusement, pour utiliser cette option dans la ligne Shebang, nous devons sacrifier la portabilité que nous gagnons en utilisant le env utilitaire décrit dans la question car les chargeurs de programme du système d'exploitation n'autorisent généralement qu'un seul argument du programme déclaré dans le script de l'interpréteur.

Par exemple, la directive d'interpréteur suivante n'est pas valide car elle transmet deux arguments à la commande env (powershell et -File):

#!/usr/bin/env powershell -File

Dans un système MSYS (comme Git Bash), en revanche, un script PowerShell qui contient la directive suivante (avec le chemin absolu vers PowerShell) s'exécute comme prévu:

#!/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -File

... mais nous ne pouvons pas exécuter directement le script sur un autre système qui ne suit pas la même convention de système de fichiers.


Cela ne résout pas non plus le problème d'origine dans Cygwin. Comme décrit dans la question, le chemin d'accès à le script lui-même n'est pas traduit en un chemin de style Windows, donc PowerShell ne peut pas localiser le fichier (même dans la version 6). J'ai trouvé quelques solutions de contournement, mais aucune ne fournit une solution parfaite.

L'approche la plus simple exploite simplement le comportement par défaut du -Command paramètre. Après avoir ajouté le Write-Foo.ps1 script vers le chemin de recherche des commandes de l'environnement (PATH), nous pouvons invoquer PowerShell avec le nom du script, sans l'extension:

$ powershell Write-Foo arg1 arg2 ...

Tant que le fichier de script lui-même ne contient pas d'espaces dans le nom de fichier, cela nous permet d'exécuter le script à partir de n'importe quel répertoire de travail - aucun Shebang n'est nécessaire. PowerShell utilise une routine native pour résoudre la commande à partir de PATH, nous n'avons donc pas à nous soucier des espaces dans les répertoires parents. Nous perdons cependant la tabulation de Bash pour le nom de la commande.

Pour que le Shebang fonctionne dans Cygwin, j'avais besoin d'écrire un script proxy qui convertit le style de chemin d'accès du script appelé dans un format que PowerShell comprend. Je l'ai appelé pwsh (pour la portabilité avec PS 6) et l'a placé dans le PATH:

#!/bin/sh

if [ ! -f "$1" ]; then 
    exec "$(command -v pwsh.exe || command -v powershell.exe)" "$@"
    exit $?
fi

script="$(cygpath -w "$1")"
shift

if command -v pwsh.exe > /dev/null; then 
    exec pwsh.exe "$script" "$@"
else
    exec powershell.exe -File "$script" "$@"
fi

Le script commence par vérifier le premier argument. S'il ne s'agit pas d'un fichier, nous démarrons simplement PowerShell normalement. Sinon, le script traduit le nom de fichier en un chemin d'accès de style Windows. Cet exemple revient à powershell.exe si pwsh.exe à partir de la version 6 n'est pas disponible. Ensuite, nous pouvons utiliser la directive interprète suivante dans le script ...

#!/usr/bin/env pwsh

... et invoquez directement un script:

$ Write-Foo.ps1 arg1 arg2 ...

Pour les versions PowerShell antérieures à 6.0, le script peut être étendu pour créer un lien symbolique ou écrire un script PowerShell temporaire avec un .ps1 extension si nous voulons créer les originaux sans extension.

11
Cy Rossignol