Une question typique des développeurs Windows: « Pourquoi n'y a-t-il encore rien ici ? » <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?. Qu'il s'agisse d'un coup puissant less ou des outils familiers grep ou sed, les développeurs sous Windows souhaitent un accès facile à ces commandes dans leur travail quotidien.
a fait un grand pas en avant à cet égard. Il vous permet d'appeler des commandes Linux de Windows, en les faisant passer par un intermédiaire wsl.exe (par exemple, wsl ls). Bien qu’il s’agisse d’une amélioration significative, cette option présente un certain nombre d’inconvénients.
- Ajout omniprésent
wslfastidieux et contre nature. - Façons Windows Les arguments ne fonctionnent pas toujours car les barres obliques inverses sont interprétées comme des caractères d'échappement et non comme des séparateurs de répertoires.
- Façons Windows Les arguments ne sont pas traduits en point de montage correspondant dans WSL.
- Les paramètres par défaut ne sont pas respectés dans les profils WSL avec alias et variables d'environnement.
- La complétion automatique du chemin n'est pas prise en charge. Linux.
- L'achèvement des commandes n'est pas pris en charge.
- La complétion des arguments n’est pas prise en charge.
Grâce à l'équipe Linux sont perçus sous Windows Ils sont traités comme des citoyens de seconde zone, et plus difficiles à utiliser que leurs équipes locales. Pour rétablir l'égalité de leurs droits, il est indispensable de s'attaquer à ces problèmes.
Wrappers de fonctions PowerShell
Avec les wrappers de fonctions PowerShell, nous pouvons ajouter la complétion des commandes et éliminer le besoin de préfixes wsl, voies de diffusion Windows Sur le chemin WSL. Configuration requise pour les shells :
- Pour chaque équipe Linux Il doit exister une fonction wrapper portant le même nom.
- Le shell doit reconnaître les chemins Windows, passés en arguments, et les convertir en chemins WSL.
- Le shell devrait appeler
wslavec la commande appropriée Linux à toute entrée de pipeline et en passant tous les arguments de ligne de commande transmis à la fonction.
Puisque ce modèle peut être appliqué à n’importe quelle commande, nous pouvons faire abstraction de la définition de ces wrappers et les générer dynamiquement à partir d’une liste de commandes à importer.
# 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 $_ -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 $_ (`$args -split ' ')
} else {
wsl.exe $_ (`$args -split ' ')
}
}
"@
} Liste $command définit les commandes d'importation. Nous générons ensuite dynamiquement un wrapper de fonction pour chacun d'eux à l'aide de la commande Invoke-Expression (en supprimant d'abord tous les alias qui pourraient entrer en conflit avec la fonction).
La fonction parcourt les arguments de la ligne de commande et détermine les chemins d'accès. Windows utiliser des commandes Split-Path и Test-Pathpuis convertit ces chemins en chemins WSL. Nous parcourons les chemins via une fonction d'assistance Format-WslArgument, que nous définirons plus tard. Il échappe aux caractères spéciaux tels que les espaces et les parenthèses qui autrement seraient mal interprétés.
Enfin, nous transmettons wsl entrée du pipeline et tous les arguments de ligne de commande.
Grâce à ces wrappers, vous pouvez appeler vos commandes préférées. Linux de manière plus naturelle, sans ajouter de préfixe wsl et sans se soucier de la façon dont les chemins sont convertis :
man bashless -i $profile.CurrentUserAllHostsls -Al C:Windows | lessgrep -Ein error *.logtail -f *.log
Un ensemble de commandes de base est présenté ici, mais vous pouvez créer un wrapper pour n'importe quelle commande. Linux, simplement en l'ajoutant à la liste. Si vous ajoutez ce code à votre PowerShell, ces commandes seront disponibles à chaque session PowerShell, tout comme les commandes natives !
Paramètres par défaut
В Linux Il est courant de définir des alias et/ou des variables d'environnement dans les profils (profil de connexion), en définissant des paramètres par défaut pour les commandes fréquemment utilisées (par exemple, alias ls=ls -AFh ou export LESS=-i). L'un des inconvénients du proxy via un shell non interactif wsl.exe - que les profils ne sont pas chargés, donc ces options ne sont pas disponibles par défaut (c'est à dire ls en WSL et wsl ls se comportera différemment avec l'alias défini ci-dessus).
PowerShell fournit , un mécanisme standard pour définir les paramètres par défaut, mais uniquement pour les applets de commande et les fonctions avancées. Bien sûr, nous pouvons créer des fonctions avancées à partir de nos shells, mais cela introduit des complications inutiles (par exemple, PowerShell corrèle les noms de paramètres partiels (par exemple, -a est en corrélation avec -ArgumentList), ce qui entrera en conflit avec les commandes Linux, en prenant des noms partiels comme arguments), et la syntaxe pour définir les valeurs par défaut ne sera pas la plus appropriée (la définition des arguments par défaut nécessite le nom du paramètre dans le commutateur, et non seulement le nom de la commande).
Cependant, avec une légère modification de nos shells, nous pouvons implémenter un modèle similaire à $PSDefaultParameterValueset activer les options par défaut pour les commandes Linux!
function global:$_() {
…
`$defaultArgs = ((`$WslDefaultParameterValues.$_ -split ' '), "")[`$WslDefaultParameterValues.Disabled -eq `$true]
if (`$input.MoveNext()) {
`$input.Reset()
`$input | wsl.exe $_ `$defaultArgs (`$args -split ' ')
} else {
wsl.exe $_ `$defaultArgs (`$args -split ' ')
}
} Qui passe $WslDefaultParameterValues à la ligne de commande, nous envoyons des paramètres via wsl.exe. Ce qui suit montre comment ajouter des instructions à votre profil PowerShell pour configurer les paramètres par défaut. Maintenant, nous pouvons le faire !
$WslDefaultParameterValues["grep"] = "-E"
$WslDefaultParameterValues["less"] = "-i"
$WslDefaultParameterValues["ls"] = "-AFh --group-directories-first" Puisque les paramètres sont modélisés d’après $PSDefaultParameterValues, Vous pouvez temporairement en installant la clé "Disabled" dans le sens $true. Un avantage supplémentaire d'une table de hachage séparée est la possibilité de désactiver $WslDefaultParameterValues en dehors de $PSDefaultParameterValues.
Achèvement des arguments
PowerShell vous permet d'enregistrer les fins d'arguments à l'aide de la commande Register-ArgumentCompleter. Bash a un puissant . WSL vous permet d'appeler bash depuis PowerShell. Si nous pouvons enregistrer les complétions d’arguments pour nos wrappers de fonctions PowerShell et appeler bash pour générer les complétions, nous obtiendrons la complétion complète des arguments avec la même précision que bash lui-même !
# 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]
}
}Le code est un peu dense sans comprendre certaines fonctions internes de bash, mais en gros ce que nous faisons est ceci :
- Enregistrer un complément d'argument pour tous nos wrappers de fonctions en passant une liste
$commandsen paramètre-CommandNamepourRegister-ArgumentCompleter. - Nous mappons chaque commande à la fonction shell que bash utilise pour l'auto-complétion (pour définir les spécifications d'auto-complétion, bash utilise
$F, abréviation decomplete -F <FUNCTION>). - Conversion d'arguments PowerShell
$wordToComplete,$commandAstи$cursorPositiondans le format attendu par les fonctions d'auto-complétion de bash selon les spécifications frapper. - Nous composons une ligne de commande vers laquelle transférer
wsl.exe, qui garantit que l'environnement est correctement configuré, appelle la fonction d'auto-complétion appropriée et affiche les résultats ligne par ligne. - Ensuite, nous appelons
wslavec la ligne de commande, nous séparons la sortie par des séparateurs de ligne et générons pour chaqueCompletionResults, en les triant et en échappant aux caractères tels que les espaces et les parenthèses qui autrement seraient mal interprétés.
Par conséquent, nos interpréteurs de commandes Linux Utilise exactement la même fonction d'autocomplétion que dans bash ! Par exemple :
ssh -c <TAB> -J <TAB> -m <TAB> -O <TAB> -o <TAB> -Q <TAB> -w <TAB> -b <TAB>
Chaque auto-complétion fournit des valeurs spécifiques à l'argument précédent, en lisant les données de configuration telles que les hôtes connus de WSL !
<TAB> fera défiler les paramètres. <Ctrl + пробел> affichera toutes les options disponibles.
De plus, puisque l'autocomplétion de bash fonctionne désormais, vous pouvez compléter automatiquement les chemins. Linux directement dans PowerShell !
less /etc/<TAB>ls /usr/share/<TAB>vim ~/.bash<TAB>
Dans les cas où la saisie automatique de bash ne donne aucun résultat, PowerShell utilise les chemins par défaut du système. WindowsAinsi, en pratique, vous pouvez utiliser les deux voies simultanément, à votre discrétion.
Conclusion
Grâce à PowerShell et WSL, nous pouvons intégrer des commandes. Linux в Windows comme les applications natives. Plus besoin de chercher des versions Win32 ou des utilitaires Linux ou interrompre le flux de travail en allant à Linux-shell. Juste , configurez и ! Saisie semi-automatique avancée pour les paramètres de commande et les chemins de fichiers Linux и Windows — Il s'agit d'une fonctionnalité qui n'est même pas disponible dans les commandes natives actuelles. Windows.
Le code source complet décrit ci-dessus, ainsi que des directives supplémentaires pour l'intégrer dans votre flux de travail, sont disponibles .
Quelles équipes ? Linux Qu'est-ce qui vous est le plus utile ? Quelles autres choses familières vous manquent lorsque vous travaillez dans Windows? Écrivez dans les commentaires ou !
Source: habr.com
