Eine typische Frage von Entwicklern unter Windows: „Warum gibt es hier immer noch keinen <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?». Будь то мощное пролистывание less oder gewohnten Tools grep oder sed, Entwickler unter Windows möchten leichteren Zugriff auf diese Befehle im Arbeitsalltag erhalten.
hat in dieser Hinsicht enorme Fortschritte gemacht. Es ermöglicht, Linux-Befehle aus Windows herauszuführen, indem sie über wsl.exe (zum Beispiel, wsl ls). Obwohl dies eine erhebliche Verbesserung darstellt, leidet diese Option unter mehreren Nachteilen.
- Die flächendeckende Einführung
wslist mühsam und unnatürlich. - Windows-Pfade in Argumenten funktionieren nicht immer, da Rückwärtsschrägstriche als Escape-Zeichen und nicht als Verzeichnistrenner interpretiert werden.
- Windows-Pfade in Argumenten werden nicht in den entsprechenden Einhängepunkt in WSL übersetzt.
- Standardparameter in WSL-Profilen mit Aliassen und Umgebungsvariablen werden nicht berücksichtigt.
- Die Vervollständigung von Linux-Pfaden wird nicht unterstützt.
- Die Vervollständigung von Befehlen wird nicht unterstützt.
- Die Vervollständigung von Argumenten wird nicht unterstützt.
Infolgedessen werden Linux-Befehle unter Windows als Bürger zweiter Klasse betrachtet – und ihre Nutzung ist schwieriger als die der nativen Befehle. Um sie gleichzustellen, müssen die genannten Probleme gelöst werden.
PowerShell-Funktionshüllen
Mit PowerShell-Funktionshüllen können wir die Vervollständigung von Befehlen hinzufügen und die Notwendigkeit von Präfixen beseitigen wsl, indem wir Windows-Pfade in WSL-Pfade umwandeln. Die Hauptanforderungen an die Hüllen sind:
- Für jeden Linux-Befehl sollte es eine Funktionshülle mit demselben Namen geben.
- Die Hülle muss Windows-Pfade, die als Argumente übergeben werden, erkennen und in WSL-Pfade umwandeln.
- Die Hülle sollte
wslmit dem entsprechenden Linux-Befehl auf jede Eingabe der Pipeline zugreifen und alle übergebenen Argumente der Befehlszeile an die Funktion weitergeben.
Da dieses Muster auf jeden Befehl angewendet werden kann, können wir die Definition dieser Hüllen abstrahieren und sie dynamisch aus einer Liste von Befehlen zur Importierung generieren.
# 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 ' ')
}
}
"@
} Eine Liste $command definiert die zu importierenden Befehle. Dann generieren wir dynamisch eine Funktionshülle für jeden von ihnen, indem wir den Befehl Invoke-Expression verwenden (zuerst werden alle Aliase entfernt, die mit der Funktion in Konflikt stehen könnten).
Die Funktion durchläuft die Argumente der Befehlszeile, erkennt Windows-Pfade mithilfe der Befehle Split-Path und Test-Path, und wandelt diese Pfade dann in WSL-Pfade um. Wir leiten die Pfade durch eine Hilfsfunktion Format-WslArgument, die wir später definieren werden. Sie maskiert spezielle Zeichen wie Leerzeichen und Klammern, die andernfalls falsch interpretiert würden.
Schließlich übergeben wir wsl die Eingaben des Pipelines und alle Befehlszeilenargumente.
Mit solchen Wrappern können Sie Ihre Lieblings-Linux-Befehle auf eine natürlichere Weise aufrufen, ohne ein Präfix hinzuzufügen wsl und sich keine Gedanken darüber zu machen, wie die Pfade umgewandelt werden:
man bashless -i $profile.CurrentUserAllHostsls -Al C:Windows | lessgrep -Ein error *.logtail -f *.log
Hier ist eine grundlegende Sammlung von Befehlen, aber Sie können für jeden Linux-Befehl einen Wrapper erstellen, indem Sie ihn einfach zur Liste hinzufügen. Wenn Sie diesen Code in Ihr PowerShell einfügen, werden diese Befehle in jeder PowerShell-Sitzung verfügbar sein, wie auch die nativen Befehle!
Standardparameter
Es ist üblich, Aliase und/oder Umgebungsvariablen in Profilen (Login-Profil) unter Linux zu definieren, um Standardparameter für häufig verwendete Befehle festzulegen (z.B. alias ls=ls -AFh oder export LESS=-i). Ein Nachteil des Proxy-Ansatzes über eine nicht-interaktive Shell wsl.exe ist, dass die Profile nicht geladen werden, daher sind diese Standardparameter nicht verfügbar (d.h. ls in WSL und wsl ls sich unterschiedlich verhalten werden mit dem oben definierten Alias).
PowerShell bietet , ein standardmäßiger Mechanismus zur Festlegung von Standardparametern, jedoch nur für Cmdlets und erweiterte Funktionen. Natürlich können wir unsere Shells zu erweiterten Funktionen machen, aber das bringt unnötige Komplikationen mit sich (so verknüpft PowerShell partielle Parameternamen (z.B. -a verknüpft mit -ArgumentList), die mit Linux-Befehlen in Konflikt geraten, die partielle Namen als Argumente akzeptieren), und die Syntax zur Definition von Standardwerten ist nicht optimal (um Standardargumente zu definieren, ist der Parametername im Schlüssel erforderlich, nicht nur der Befehlsname).
Mit einer kleinen Anpassung unserer Shells können wir jedoch ein Modell implementieren, das ähnlich ist $PSDefaultParameterValues, und Standardparameter für Linux-Befehle aktivieren!
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 ' ')
}
} Indem wir $WslDefaultParameterValues In die Eingabeaufforderung senden wir Parameter über wsl.exe. Unten sehen Sie, wie Sie Anweisungen zum PowerShell-Profil hinzufügen, um Standardparameter zu konfigurieren. Jetzt können wir das tun!
$WslDefaultParameterValues["grep"] = "-E"
$WslDefaultParameterValues["less"] = "-i"
$WslDefaultParameterValues["ls"] = "-AFh --group-directories-first" Da die Parameter nach $PSDefaultParameterValuesmoduliert werden, können Sie vorübergehend, indem Sie den Schlüssel "Disabled" auf den Wert $true. Ein weiterer Vorteil einer separaten Hashtabelle ist die Möglichkeit, sie separat von $WslDefaultParameterValues die automatische Vervollständigung von Argumenten $PSDefaultParameterValues.
PowerShell ermöglicht das Registrieren von Argumentvervollständiger mit dem Befehl
Register-ArgumentCompleter . In Bash gibt es leistungsstarkeprogrammierbare Mittel zur automatischen Vervollständigung. Der Code ist etwas dicht, ohne einige interne Bash-Funktionen zu verstehen, aber grundsätzlich tun wir Folgendes:
# 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]
}
}Wir registrieren den Argumentvervollständiger für alle unsere Funktionswrapper, indem wir eine Liste von
- $commands
an den Parameter-CommandNameabgleichen jede Kommando mit der Shellfunktion, die Bash für die Vervollständigung verwendet (zur Definition der Vervollständigungspezifikationen in Bash wirdfür. In Bash gibt es leistungsstarke. - $F
, abgekürzt voncomplete -FWir konvertieren die PowerShell-Argumente). - $wordToComplete
$commandAst,$cursorPositionundin das Format, das von den Vervollständigungsfunktionen in Bash gemäß den Spezifikationen derprogramierbaren Vervollständigung Wir stellen die Eingabeaufforderung zum Übertragen in - , die die korrekte Umgebungseinrichtung bietet, die entsprechende Vervollständigungsfunktion aufruft und die Ergebnisse zeilenweise ausgibt.
wsl.exeDann rufen wir - mit der Eingabeaufforderung auf, teilen die Ausgabe mit Zeilenumbruch und generieren für jedes
wslCompletionResults, indem wir sie sortieren und Zeichen wie Leerzeichen und Klammern escapen, die sonst falsch interpretiert würden.Am Ende werden unsere Linux-Kommandos genau die gleiche Vervollständigung verwenden wie in Bash! Zum Beispiel:
ssh -c -J -m -O -o -Q -w -b
ssh -c -J -m -O -o -Q -w -b
Jede Autovervollständigung bietet Werte, die spezifisch für das vorherige Argument sind, indem Konfigurationsdaten wie bekannte Hosts aus WSL eingelesen werden!
<TAB> wird die Parameter zyklisch durchlaufen. <Ctrl + пробел> zeigt alle verfügbaren Optionen an.
Außerdem können Sie jetzt dank des funktionierenden Bash-Autovervollständigens Linux-Pfade direkt in PowerShell vervollständigen!
less /etc/ls /usr/share/vim ~/.bash
In Fällen, in denen die Bash-Autovervollständigung keine Ergebnisse liefert, kehrt PowerShell zum Standardsystem mit Windows-Pfaden zurück. So können Sie in der Praxis gleichzeitig beide Pfade nach Ihrem Ermessen verwenden.
Fazit
Mit PowerShell und WSL können wir Linux-Befehle in Windows als native Anwendungen integrieren. Es ist nicht notwendig, nach Win32-Builds oder Linux-Tools zu suchen oder den Arbeitsablauf zu unterbrechen, indem man in die Linux-Shell wechselt. Einfach , konfigurieren und ! Reiche Autovervollständigung für Befehlsoptionen und Dateipfade von Linux und Windows – das ist eine Funktion, die selbst in den nativen Windows-Befehlen heute nicht verfügbar ist.
Der vollständige Quellcode, der oben beschrieben ist, sowie zusätzliche Empfehlungen für dessen Einbindung in den Arbeitsablauf sind verfügbar .
Welche Linux-Befehle halten Sie für am nützlichsten? Was fehlt Ihnen sonst noch im Umgang mit Windows? Schreiben Sie es in die Kommentare oder !
Quelle: habr.com
