Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

« Avec PowerShell et WSL, nous pouvons intégrer des commandes Linux à Windows comme s'il s'agissait d'applications natives »,
Selon un ingénieur Microsoft

Le , par Stéphane le calme

40PARTAGES

10  0 
Avec WSL 2, Microsoft a apporté une nouvelle version de l’architecture qui permet au sous-système Windows pour Linux d’exécuter des fichiers binaires ELF64 Linux sous Windows. Cette nouvelle architecture, qui utilise un véritable noyau Linux, modifie la façon dont ces binaires Linux interagissent avec Windows et le matériel de votre ordinateur, tout en offrant la même expérience utilisateur que dans WSL 1 (la version actuellement disponible en version stable). WSL 2 offre des performances de système de fichiers beaucoup plus rapides et une compatibilité totale des appels système, ce qui vous permet d'exécuter davantage d'applications comme Docker!

WSL 2 inclut donc un véritable noyau Linux qui vous permet d’exécuter davantage de logiciels Linux sous Windows et offrant de meilleures performances que WSL 1.

Cette nouvelle version de WSL 2 utilise les fonctionnalités Hyper-V pour créer une machine virtuelle légère avec un noyau Linux minimal. Il faut s’attendre à une meilleure compatibilité avec les logiciels Linux, y compris la prise en charge de Docker, et « une augmentation spectaculaire des performances du système de fichiers ».

Mais Microsoft a voulu expliquer aux utilisateurs qu'ils pouvaient aller plus loin. Dans un billet de blog, Mike Battista, Senior Program Manager Windows Developer Platform, avance que :

« Une question récurrente chez les développeurs Windows est la suivante : "Pourquoi Windows n’a-t-il pas encore <INSERT FAVORITE LINUX COMMAND HERE>?". Qu'ils aspirent à utiliser un puissant pageur comme less ou à utiliser des commandes familières telles que grep ou sed, les développeurs Windows souhaitent pouvoir accéder facilement à ces commandes dans le cadre de leur flux de travail principal » .

Il a noté que le sous-système Windows pour Linux (WSL) constituait un énorme pas en avant en permettant aux développeurs d’appeler des commandes Linux à partir de Windows en les mettant en proxy par le biais de wsl.exe (par exemple, wsl ls). Cependant, il a relevé les limites de cette expérience à plusieurs niveaux, notamment :
  • Préfixer les commandes avec wsl est fastidieux et peu naturel
  • Les chemins Windows transmis en tant qu'arguments ne sont pas souvent résolus, car les barres obliques inverses sont interprétées comme des caractères d'échappement plutôt que comme des séparateurs de répertoire ;
  • Les chemins Windows transmis en tant qu'arguments ne sont pas souvent résolus, car ils ne sont pas traduits vers le point de montage approprié dans WSL ;
  • Les paramètres par défaut définis dans les profils de connexion WSL avec des alias et des variables d’environnement ne sont pas respectés ;
  • La complétion de chemin Linux n'est pas supportée ;
  • La complétion de la commande n'est pas supportée ;
  • La complétion de l'argument n'est pas prise en charge.

« Le résultat de ces lacunes est que les commandes Linux se sentent comme des citoyens de deuxième classe vis-à-vis de Windows et sont plus difficiles à utiliser qu’elles ne devraient l’être. Pour qu'une commande ressemble à une commande Windows native, nous devons résoudre ces problèmes ».


Encapsuleurs de fonctions PowerShell

Mike affirme que nous pouvons supprimer le besoin de préfixer les commandes par wsl, gérer la traduction des chemins Windows en chemins WSL et prendre en charge la complétion des commandes avec les wrappers de fonctions PowerShell. Les exigences de base des wrapper sont les suivantes :
  • Il devrait y avoir un wrapper de fonction par commande Linux portant le même nom que la commande ;
  • Le wrapper doit reconnaître les chemins Windows transmis en tant qu'arguments et les traduire en chemins WSL ;
  • Le wrapper doit appeler wsl avec la commande Linux correspondante, en canalisant toute entrée de pipeline et en transmettant tous les arguments de ligne de commande passés à la fonction.

Ce modèle pouvant être appliqué à n’importe quelle commande, nous pouvons résumer la définition de ces wrappers et les générer de manière dynamique à partir d’une liste de commandes à importer.

Code PowerShell : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# The commands to import.
$commands = "awk", "emacs", "grep", "head", "less", "ls", "man", "sed", "seq", "ssh", "tail", "vim"
 
# Register a function for each command.
$commands | ForEach-Object { Invoke-Expression @"
Remove-Alias&nbsp;$_ -Force -ErrorAction Ignore
function global:$_() {
    for (`$i = 0; `$i -lt `$args.Count; `$i++) {
        # If a path is absolute with a qualifier (e.g. C:), run it through wslpath to map it to the appropriate mount point.
        if (Split-Path `$args[`$i] -IsAbsolute -ErrorAction Ignore) {
            `$args[`$i] = Format-WslArgument (wsl.exe wslpath (`$args[`$i] -replace "\\", "/"))
        # If a path is relative, the current working directory will be translated to an appropriate mount point, so just format it.
        } elseif (Test-Path `$args[`$i] -ErrorAction Ignore) {
            `$args[`$i] = Format-WslArgument (`$args[`$i] -replace "\\", "/")
        }
    }
 
    if (`$input.MoveNext()) {
        `$input.Reset()
        `$input | wsl.exe&nbsp;$_ (`$args -split ' ')
    } else {
        wsl.exe&nbsp;$_ (`$args -split ' ')
    }
}
"@
}

La liste $command définit les commandes à importer. Ensuite, nous générons dynamiquement l'encapsuleur de fonction pour chacune à l'aide de la commande Invoke-Expression (en supprimant tout d'abord les alias susceptibles d'entrer en conflit avec la fonction).

La fonction parcourt les arguments de ligne de commande, identifie les chemins Windows à l'aide des commandes Split-Path et Test-Path, puis convertit ces chemins en chemins WSL. Nous exécutons les chemins via une fonction d'assistance que nous définirons plus tard, appelée Format-WslArgument, qui permet d'échapper les caractères spéciaux tels que les espaces et les parenthèses qui seraient autrement mal interprétés.

Enfin, nous transmettons les entrées de pipeline et tous les arguments de ligne de commande à wsl.

Avec ces wrappers de fonctions en place, nous pouvons maintenant appeler nos commandes Linux préférées de manière plus naturelle sans avoir à les préfixer avec wsl ni à nous préoccuper de la façon dont les chemins Windows sont traduits en chemins WSL:
  • man bash
  • less -i $profile.CurrentUserAllHosts
  • ls -Al C:\Windows\ | less
  • grep -Ein error *.log
  • tail -f *.log

Un ensemble de commandes est présenté ici, mais vous pouvez générer un wrapper pour toute commande Linux simplement en l'ajoutant à la liste. Si vous ajoutez ce code à votre profil PowerShell, ces commandes seront disponibles dans chaque session PowerShell, tout comme les commandes natives!

Paramètres par défaut

Dans Linux, il est courant de définir des alias et/ou des variables d’environnement dans les profils de connexion pour définir les paramètres par défaut des commandes que vous utilisez fréquemment (par exemple, alias ls = ls -AFh ou export LESS = -i). L’un des inconvénients de la procuration par l’intermédiaire d’un shell non interactif via wsl.exe est que les profils de connexion ne sont pas chargés; ces paramètres par défaut ne sont donc pas disponibles (c’est-à-dire que ls dans WSL et wsl ls se comportaient différemment avec l’alias défini ci-dessus).

PowerShell fournit $PSDefaultParameterValues, un mécanisme standard permettant de définir les valeurs de paramètre par défaut, mais uniquement pour les applets de commande et les fonctions avancées. Convertir nos wrappers de fonctions en fonctions avancées est possible, mais introduit des complications (par exemple, PowerShell fait correspondre les noms de paramètres partiels - -a peut correspondre à -ArgumentList - ce qui entre en conflit avec les commandes Linux acceptant les noms partiels comme arguments), et la syntaxe permettant de définir les valeurs par défaut serait moins qu'idéal pour ce scénario (nécessitant le nom d'un paramètre dans la clé pour définir les arguments par défaut plutôt que le nom de la commande).

Avec une petite modification de nos wrappers de fonctions, nous pouvons introduire un modèle similaire à $PSDefaultParameterValues et activer les paramètres par défaut pour les commandes Linux!

Code PowerShell : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
function global:$_() {
    …
 
    `$defaultArgs = ((`$WslDefaultParameterValues.$_ -split ' '), "")[`$WslDefaultParameterValues.Disabled -eq `$true]
    if (`$input.MoveNext()) {
        `$input.Reset()
        `$input | wsl.exe&nbsp;$_ `$defaultArgs (`$args -split ' ')
    } else {
        wsl.exe&nbsp;$_ `$defaultArgs (`$args -split ' ')
    }
}

En transmettant $WslDefaultParameterValues à la ligne de commande que nous envoyons via wsl.exe, vous pouvez maintenant ajouter des instructions comme ci-dessous à votre profil PowerShell pour configurer les paramètres par défaut!

Code PowerShell : Sélectionner tout
1
2
3
$WslDefaultParameterValues["grep"] = "-E"
$WslDefaultParameterValues["less"] = "-i"
$WslDefaultParameterValues["ls"] = "-AFh --group-directories-first"

Étant donné que ceci est modélisé selon $PSDefaultParameterValues, vous pouvez les désactiver temporairement facilement en définissant la clé "Disabled" sur $true. Une table de hachage séparée présente l’avantage supplémentaire de pouvoir désactiver $WslDefaultParameterValues séparément de $PSDefaultParameterValues.

Complétion des arguments

PowerShell vous permet d'inscrire d'activer la complétion d'arguments à l'aide de la commande Register-ArgumentCompleter. Bash dispose de puissantes fonctions de complétion programmables. WSL vous permet d’appeler en bash depuis PowerShell. Si nous pouvons activer la complétion d'arguments pour nos wrappers de fonctions PowerShell et faire des appels bash pour générer les complétions, nous pouvons obtenir une complétion d'arguments enrichie avec la même fidélité que dans bash lui-même !

Code PowerShell : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# Register an ArgumentCompleter that shims bash's programmable completion.
Register-ArgumentCompleter -CommandName $commands -ScriptBlock {
    param($wordToComplete, $commandAst, $cursorPosition)
 
    # Map the command to the appropriate bash completion function.
    $F = switch ($commandAst.CommandElements[0].Value) {
        {$_ -in "awk", "grep", "head", "less", "ls", "sed", "seq", "tail"} {
            "_longopt"
            break
        }
 
        "man" {
            "_man"
            break
        }
 
        "ssh" {
            "_ssh"
            break
        }
 
        Default {
            "_minimal"
            break
        }
    }
 
    # Populate bash programmable completion variables.
    $COMP_LINE = "`"$commandAst`""
    $COMP_WORDS = "('$($commandAst.CommandElements.Extent.Text -join "' '")')" -replace "''", "'"
    for ($i = 1; $i -lt $commandAst.CommandElements.Count; $i++) {
        $extent = $commandAst.CommandElements[$i].Extent
        if ($cursorPosition -lt $extent.EndColumnNumber) {
            # The cursor is in the middle of a word to complete.
            $previousWord = $commandAst.CommandElements[$i - 1].Extent.Text
            $COMP_CWORD = $i
            break
        } elseif ($cursorPosition -eq $extent.EndColumnNumber) {
            # The cursor is immediately after the current word.
            $previousWord = $extent.Text
            $COMP_CWORD = $i + 1
            break
        } elseif ($cursorPosition -lt $extent.StartColumnNumber) {
            # The cursor is within whitespace between the previous and current words.
            $previousWord = $commandAst.CommandElements[$i - 1].Extent.Text
            $COMP_CWORD = $i
            break
        } elseif ($i -eq $commandAst.CommandElements.Count - 1 -and $cursorPosition -gt $extent.EndColumnNumber) {
            # The cursor is within whitespace at the end of the line.
            $previousWord = $extent.Text
            $COMP_CWORD = $i + 1
            break
        }
    }
 
    # Repopulate bash programmable completion variables for scenarios like '/mnt/c/Program Files'/<TAB> where <TAB> should continue completing the quoted path.
    $currentExtent = $commandAst.CommandElements[$COMP_CWORD].Extent
    $previousExtent = $commandAst.CommandElements[$COMP_CWORD - 1].Extent
    if ($currentExtent.Text -like "/*" -and $currentExtent.StartColumnNumber -eq $previousExtent.EndColumnNumber) {
        $COMP_LINE = $COMP_LINE -replace "$($previousExtent.Text)$($currentExtent.Text)", $wordToComplete
        $COMP_WORDS = $COMP_WORDS -replace "$($previousExtent.Text) '$($currentExtent.Text)'", $wordToComplete
        $previousWord = $commandAst.CommandElements[$COMP_CWORD - 2].Extent.Text
        $COMP_CWORD -= 1
    }
 
    # Build the command to pass to WSL.
    $command = $commandAst.CommandElements[0].Value
    $bashCompletion = ". /usr/share/bash-completion/bash_completion 2> /dev/null"
    $commandCompletion = ". /usr/share/bash-completion/completions/$command 2> /dev/null"
    $COMPINPUT = "COMP_LINE=$COMP_LINE; COMP_WORDS=$COMP_WORDS; COMP_CWORD=$COMP_CWORD; COMP_POINT=$cursorPosition"
    $COMPGEN = "bind `"set completion-ignore-case on`" 2> /dev/null; $F `"$command`" `"$wordToComplete`" `"$previousWord`" 2> /dev/null"
    $COMPREPLY = "IFS=`$'\n'; echo `"`${COMPREPLY[*]}`""
    $commandLine = "$bashCompletion; $commandCompletion; $COMPINPUT; $COMPGEN; $COMPREPLY" -split ' '
 
    # Invoke bash completion and return CompletionResults.
    $previousCompletionText = ""
    (wsl.exe $commandLine) -split '\n' |
    Sort-Object -Unique -CaseSensitive |
    ForEach-Object {
        if ($wordToComplete -match "(.*=).*") {
            $completionText = Format-WslArgument ($Matches[1] + $_) $true
            $listItemText = $_
        } else {
            $completionText = Format-WslArgument $_ $true
            $listItemText = $completionText
        }
 
        if ($completionText -eq $previousCompletionText) {
            # Differentiate completions that differ only by case otherwise PowerShell will view them as duplicate.
            $listItemText += ' '
        }
 
        $previousCompletionText = $completionText
        [System.Management.Automation.CompletionResult]::new($completionText, $listItemText, 'ParameterName', $completionText)
    }
}
 
# Helper function to escape characters in arguments passed to WSL that would otherwise be misinterpreted.
function global:Format-WslArgument([string]$arg, [bool]$interactive) {
    if ($interactive -and $arg.Contains(" ")) {
        return "'$arg'"
    } else {
        return ($arg -replace " ", "\ ") -replace "([()|])", ('\$1', '`$1')[$interactive]
    }
}

En gros :
  • Nous enregistrons l'argument compléteur pour tous nos wrappers de fonction en passant la liste $commands au paramètre -CommandName de Register-ArgumentCompleter ;
  • Nous mappons chaque commande à la fonction shell utilisée par bash pour la compléter ($F qui porte le nom de complete -F <FUNCTION> utilisée pour définir les spécifications de complétion dans bash) ;
  • Nous convertissons les arguments $wordToComplete, $commandAst et $cursorPosition de PowerShell au format attendu par les fonctions de complétion bash conformément à la spécification de complétion programmable bash ;
  • Nous construisons une ligne de commande que nous pouvons transmettre à wsl.exe afin de nous assurer que l'environnement d'achèvement est correctement configuré, d'appeler la fonction de complétion appropriée, puis de générer une chaîne contenant les résultats d'achèvement séparés par de nouvelles lignes ;
  • Nous appelons ensuite wsl avec la ligne de commande, divisons la chaîne de sortie sur le nouveau séparateur de ligne, puis générons CompletionResults pour chacun, en les triant, ainsi que des caractères d'échappement tels que des espaces et des parenthèses qui seraient autrement mal interprétés.

Le résultat final de ceci est maintenant que nos wrappers de commandes Linux utiliseront exactement la même finition que celle utilisée par bash ! Par exemple :

Code Powershell : Sélectionner tout
ssh -c <TAB> -J <TAB> -m <TAB> -O <TAB> -o <TAB> -Q <TAB> -w <TAB> -b <TAB>

Chaque complétion va fournir des valeurs spécifiques à l'argument précédent, lisant des données de configuration comme des hôtes connus à partir de WSL!

<TAB> va parcourir les options. <Ctrl + Espace> affichera toutes les options disponibles.

De plus, étant donné que la complétion bash est désormais supportée, vous pouvez résoudre les chemins Linux directement dans PowerShell !
  • less /etc/<TAB>
  • ls /usr/share/<TAB>
  • vim ~/.bash<TAB>

Dans les cas où la complétion bash ne renvoie aucun résultat, PowerShell reprend sa complétion par défaut, ce qui résoudra les chemins Windows, ce qui vous permettra effectivement de résoudre à la fois les chemins Linux et Windows.

Mike conclut en disant que : « avec PowerShell et WSL, nous pouvons intégrer des commandes Linux à Windows comme s'il s'agissait d'applications natives. Nul besoin de chercher des versions Win32 d'utilitaires Linux ni d'être obligé d'interrompre votre flux de travail pour accéder à un shell Linux. Installez simplement WSL, configurez votre profil PowerShell et répertoriez les commandes que vous souhaitez importer! La complétion riche des arguments sur les options de commande et les chemins de fichiers Linux et Windows présentée ici est une expérience que même les commandes Windows natives ne fournissent pas aujourd’hui ».

code source complet de ce qui a été décrit ici ainsi qu'un guide pour vous en servir dans votre workflow

Source : Microsoft

Et vous ?

Avez-vous déjà souhaité intégrer des commandes Linux à Windows ?
Que pensez-vous de l'approche de cet ingénieur ?
Allez-vous l'essayer ou préférez-vous attendre la disponibilité d'un utilitaire spécifique qui va faire ce travail ?

Voir aussi :

Comment profiter de Linux sur Windows 10 sans WSL sur le Microsoft Store avec Dark Moon ?
Des ingénieurs demandent à ce que soient ajoutés les opérateurs de type Bash && et || à PowerShell dans une RFC
Microsoft publie le code source du noyau Linux léger utilisé dans WSL2 sous licence GPL version 2
Docker Desktop pour Windows 10 va bientôt passer à WSL2, quels sont les avantages pour les développeurs ?
Microsoft avance que la prochaine itération de PowerShell Core sera baptisée PowerShell 7, l'éditeur explique pourquoi il envisage de supprimer "Core"

Une erreur dans cette actualité ? Signalez-le nous !

Avatar de Sodium
Membre extrêmement actif https://www.developpez.com
Le 29/09/2019 à 7:51
Je confirme pour l'amélioration spectaculaire du système de fichier. Sur une application Symfony qui charge beaucoup trop de dépendances à mon goût, je suis passée de 12 secondes de chargement à... 800ms.
0  0 
Avatar de bk417
Membre régulier https://www.developpez.com
Le 29/09/2019 à 13:38
C'est bien.
On attend la sortie publique de Windows Terminal et de WSL2.
2020 peut-être ?
0  0