Wir integrieren Linux-Befehle mit PowerShell und WSL in Windows

Eine typische Windows-Entwicklerfrage: „Warum ist es immer noch nicht hier < LINUX> ?“. Unabhängig davon, ob es sich um leistungsstarkes Scrollen oder um die üblichen grep oder sed Tools handelt, möchten Windows-Entwickler bei der täglichen Arbeit einen einfachen Zugriff auf diese Befehle.

Das Windows-Subsystem für Linux (WSL) hat in dieser Hinsicht einen großen Schritt nach vorne gemacht. Sie können Linux-Befehle von Windows aus aufrufen und über wsl.exe (z. B. wsl ls ) wsl ls . Obwohl dies eine signifikante Verbesserung darstellt, weist diese Option eine Reihe von Nachteilen auf.

  • Die allgegenwärtige Zugabe von wsl anstrengend und unnatürlich.
  • Windows-Pfade in den Argumenten funktionieren nicht immer, da Backslashes als Escape-Zeichen und nicht als Verzeichnis-Trennzeichen interpretiert werden.
  • Windows-Pfade in Argumenten werden nicht in den entsprechenden Einhängepunkt in der WSL übersetzt.
  • Die Standardeinstellungen in WSL-Profilen mit Aliasnamen und Umgebungsvariablen werden nicht berücksichtigt.
  • Die Vervollständigung des Linux-Pfads wird nicht unterstützt.
  • Die Befehlsvervollständigung wird nicht unterstützt.
  • Die Vervollständigung von Argumenten wird nicht unterstützt.

Infolgedessen werden Linux-Befehle unter Windows als Bürger zweiter Klasse wahrgenommen - und sie sind schwieriger zu verwenden als native Teams. Um ihre Rechte auszugleichen, müssen Sie diese Probleme lösen.

PowerShell-Shells


Mithilfe von PowerShell-Funktions-Wrappern können wir die Befehlsvervollständigung hinzufügen und die Notwendigkeit von wsl Präfixen beseitigen, indem wir Windows-Pfade in WSL-Pfade übersetzen. Grundvoraussetzungen für Muscheln:

  • Jeder Linux-Befehl muss eine Shell der Funktion mit demselben Namen haben.
  • Die Shell muss die als Argumente übergebenen Windows-Pfade erkennen und in WSL-Pfade konvertieren.
  • Die Shell sollte wsl mit dem entsprechenden Linux-Befehl für jede Pipeline-Eingabe aufrufen und alle an die Funktion übergebenen Befehlszeilenargumente übergeben.

Da diese Vorlage auf jeden Befehl angewendet werden kann, können wir die Definition dieser Shells abstrahieren und sie dynamisch aus der Liste der zu importierenden Befehle 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 (eg 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 ' ') } } "@ } 

Die $command definiert die zu importierenden Befehle. Anschließend generieren wir mit dem Befehl Invoke-Expression dynamisch einen Funktions-Wrapper für jeden von ihnen (entfernen Sie zuerst alle Aliase, die mit der Funktion in Konflikt stehen).

Die Funktion durchläuft die Befehlszeilenargumente, ermittelt die Windows-Pfade mithilfe der Befehle Split-Path und Test-Path und konvertiert diese Pfade dann in WSL-Pfade. Wir führen die Pfade durch die Format-WslArgument , die wir später definieren. Es entgeht Sonderzeichen wie Leerzeichen und Klammern, die sonst falsch interpretiert würden.

Schließlich übergeben wir die wsl Eingabe und alle Befehlszeilenargumente an wsl .

Mit diesen Wrappern können Sie Ihre bevorzugten Linux-Befehle auf natürlichere Weise wsl , ohne das Präfix wsl hinzuzufügen und ohne sich Gedanken darüber zu machen, wie die Pfade konvertiert werden:

  • man bash
  • less -i $profile.CurrentUserAllHosts
  • ls -Al C:\Windows\ | less
  • grep -Ein error *.log
  • tail -f *.log

Der grundlegende Befehlssatz wird hier angezeigt. Sie können jedoch eine Shell für jeden Linux-Befehl erstellen, indem Sie ihn einfach zur Liste hinzufügen. Wenn Sie diesen Code zu Ihrem PowerShell- Profil hinzufügen, stehen Ihnen diese Befehle in jeder PowerShell-Sitzung zur Verfügung, ebenso wie die nativen Befehle!

Standardoptionen


Unter Linux ist es üblich, Aliase und / oder Umgebungsvariablen in Profilen (Anmeldeprofil) zu definieren und Standardparameter für häufig verwendete Befehle alias ls=ls -AFh (z. B. alias ls=ls -AFh oder export LESS=-i ). Einer der Nachteile des Proxys über die nicht interaktive wsl.exe Shell besteht darin, dass keine Profile geladen werden. Daher sind diese Optionen standardmäßig nicht verfügbar (d. H. wsl ls in WSL und wsl ls verhalten sich mit dem oben definierten Alias ​​unterschiedlich).

PowerShell bietet $ PSDefaultParameterValues , einen Standardmechanismus zum Definieren von Standardparametern, jedoch nur für Cmdlets und erweiterte Funktionen. Natürlich können Sie erweiterte Funktionen aus unseren Shells -ArgumentList , dies führt jedoch zu unnötigen Komplikationen (z. B. stimmt PowerShell mit Teilparameternamen überein (z. B. -a entspricht -ArgumentList ), was mit Linux-Befehlen in Konflikt steht, die -ArgumentList als Argumente akzeptieren) Die Syntax zum Definieren von Standardwerten ist nicht am besten geeignet (zum Definieren von Standardargumenten ist der Parametername im Schlüssel erforderlich und nicht nur der Befehlsname).

Mit einer geringfügigen Änderung unserer Shells können wir jedoch ein Modell implementieren, das $PSDefaultParameterValues ähnelt, und Standardoptionen 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 ' ') } } 

Durch $WslDefaultParameterValues an die Befehlszeile senden wir die Parameter über wsl.exe . Im Folgenden wird gezeigt, wie Sie einem PowerShell-Profil Anweisungen zum Konfigurieren der Standardeinstellungen hinzufügen. Jetzt können wir es schaffen!

 $WslDefaultParameterValues["grep"] = "-E" $WslDefaultParameterValues["less"] = "-i" $WslDefaultParameterValues["ls"] = "-AFh --group-directories-first" 

Da Parameter nach $PSDefaultParameterValues modelliert werden, können Sie sie einfach vorübergehend $PSDefaultParameterValues , indem Sie den Schlüssel "Disabled" auf $true . Ein zusätzlicher Vorteil einer separaten Hash-Tabelle ist die Möglichkeit, $WslDefaultParameterValues getrennt von $PSDefaultParameterValues zu deaktivieren.

Argumentvervollständigung


PowerShell ermöglicht das Registrieren von Argumentterminatoren mit dem Befehl Register-ArgumentCompleter . Bash verfügt über leistungsstarke programmierbare Vervollständigungswerkzeuge . Mit WSL können Sie Bash über PowerShell aufrufen. Wenn wir die Argumentterminatoren für unsere PowerShell-Funktions-Wrapper registrieren und bash aufrufen können, um die Abbrüche zu erstellen, erhalten wir die vollständige Vervollständigung der Argumente mit der gleichen Genauigkeit wie in bash selbst!

 # 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] } } 

Der Code ist ein wenig eng, ohne einige der Bash-Interna zu verstehen, aber im Grunde machen wir Folgendes:

  • Wir registrieren den Argument-Finalizer für alle unsere Funktions-Wrapper, indem wir die Liste der $commands an den Parameter -CommandName für Register-ArgumentCompleter .
  • Wir ordnen jeden Befehl der Shell-Funktion zu, die bash für die automatische Vervollständigung verwendet (bash verwendet $F , um die Spezifikationen für die automatische Vervollständigung zu definieren, kurz für complete -F <FUNCTION> ).
  • Konvertieren Sie die PowerShell-Argumente $wordToComplete , $commandAst und $cursorPosition in das Format, das von den Bash-Abschlussfunktionen gemäß den programmierbaren Bash-Abschlussspezifikationen erwartet wird.
  • Wir wsl.exe die Befehlszeile für die Übertragung in wsl.exe , die die korrekte Umgebungseinstellung sicherstellt, die entsprechende Autovervollständigungsfunktion aufruft und die Ergebnisse mit Zeilenumbrüchen anzeigt.
  • Dann rufen wir wsl mit der Befehlszeile auf, trennen die Ausgabe mit Zeilentrennzeichen und generieren CompletionResults für jedes, sortieren sie und maskieren Zeichen wie Leerzeichen und Klammern, die sonst falsch interpretiert würden.

Infolgedessen verwenden unsere Linux-Befehlsshells genau die gleiche automatische Vervollständigung wie in bash! Zum Beispiel:

  • ssh -c <TAB> -J <TAB> -m <TAB> -O <TAB> -o <TAB> -Q <TAB> -w <TAB> -b <TAB>

Jede automatische Vervollständigung liefert Werte, die für das vorherige Argument spezifisch sind, und liest Konfigurationsdaten wie bekannte Hosts aus der WSL!

<TAB> durchläuft die Parameter. <Ctrl + > zeigt alle verfügbaren Optionen an.

Da bash autocomplete jetzt bei uns funktioniert, können Sie Linux-Pfade auch direkt in PowerShell automatisch vervollständigen!

  • less /etc/<TAB>
  • ls /usr/share/<TAB>
  • vim ~/.bash<TAB>

In Fällen, in denen die automatische Vervollständigung von Bash fehlschlägt, kehrt PowerShell zum Standardsystem mit Windows-Pfaden zurück. In der Praxis können Sie diese und andere Methoden also nach eigenem Ermessen gleichzeitig verwenden.

Fazit


Mit PowerShell und WSL können wir Linux-Befehle als native Anwendungen in Windows integrieren. Sie müssen nicht nach Win32-Builds oder Linux-Dienstprogrammen suchen oder den Workflow durch Umschalten auf die Linux-Shell unterbrechen. Installieren Sie einfach WSL , konfigurieren Sie Ihr PowerShell-Profil und listen Sie die Befehle auf, die Sie importieren möchten ! Die umfangreiche automatische Vervollständigung von Befehls- und Pfadparametern für Linux- und Windows-Dateien ist eine Funktionalität, die heutzutage in nativen Windows-Befehlen nicht mehr zu finden ist.

Der oben beschriebene vollständige Quellcode sowie zusätzliche Empfehlungen für die Aufnahme in den Workflow finden Sie hier .

Welche Linux-Befehle finden Sie am nützlichsten? Welche anderen vertrauten Dinge fehlen bei der Arbeit unter Windows? Schreiben Sie in den Kommentaren oder auf GitHub !

Source: https://habr.com/ru/post/de469257/


All Articles