?". Ya sea que se trate de un desplazamiento less potente ...">

Integramos comandos de Linux en Windows usando PowerShell y WSL

Una pregunta típica de un desarrollador de Windows: "¿Por qué todavía no está aquí < LINUX> ?". Ya sea que se trate de un desplazamiento less potente o de las herramientas grep o sed habituales, los desarrolladores de Windows desean acceder fácilmente a estos comandos en el trabajo diario.

El Subsistema de Windows para Linux (WSL) ha dado un gran paso adelante en este sentido. Le permite llamar a comandos de Linux desde Windows, proxándolos a través de wsl.exe (por ejemplo, wsl ls ). Aunque esta es una mejora significativa, esta opción presenta una serie de desventajas.

  • La adición ubicua de wsl agotadora y antinatural.
  • Las rutas de Windows en los argumentos no siempre funcionan, porque las barras invertidas se interpretan como caracteres de escape, no como separadores de directorio.
  • Las rutas de Windows en los argumentos no se traducen al punto de montaje correspondiente en WSL.
  • La configuración predeterminada en los perfiles WSL con alias y variables de entorno no se tiene en cuenta.
  • La finalización de la ruta de Linux no es compatible.
  • La finalización de comandos no es compatible.
  • La finalización del argumento no es compatible.

Como resultado, los comandos de Linux se perciben en Windows como ciudadanos de segunda clase, y son más difíciles de usar que los equipos nativos. Para igualar sus derechos, debe resolver estos problemas.

Conchas PowerShell


Al usar los envoltorios de funciones de PowerShell, podemos agregar la finalización de comandos y eliminar la necesidad de prefijos wsl al traducir las rutas de Windows a las rutas WSL. Requisitos básicos para conchas:

  • Cada comando de Linux debe tener un shell de la función con el mismo nombre.
  • El shell debe reconocer las rutas de Windows pasadas como argumentos y convertirlas en rutas WSL.
  • El shell debe llamar a wsl con el comando de Linux apropiado a cualquier entrada de canalización y pasar cualquier argumento de línea de comando pasado a la función.

Dado que esta plantilla se puede aplicar a cualquier comando, podemos abstraer la definición de estos shells y generarlos dinámicamente a partir de la lista de comandos para importar.

 # 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 ' ') } } "@ } 

La lista de $command define los comandos para importar. Luego generamos dinámicamente un contenedor de funciones para cada uno de ellos usando el comando Invoke-Expression (primero eliminando cualquier alias que entre en conflicto con la función).

La función itera sobre los argumentos de la línea de comando, determina las rutas de Windows utilizando los comandos Split-Path y Test-Path , y luego convierte estas rutas en rutas WSL. Format-WslArgument las rutas a través de la función auxiliar Format-WslArgument , que definiremos más adelante. Se escapa de caracteres especiales, como espacios y corchetes, que de otro modo se malinterpretarían.

Finalmente, pasamos la entrada de la wsl y cualquier argumento de línea de comando a wsl .

Con estos contenedores, puede llamar a sus comandos de Linux favoritos de una manera más natural sin agregar el prefijo wsl y sin preocuparse por cómo se convierten las rutas:

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

Aquí se muestra el conjunto de comandos básicos, pero puede crear un shell para cualquier comando de Linux simplemente agregándolo a la lista. Si agrega este código a su perfil de PowerShell, estos comandos estarán disponibles para usted en cada sesión de PowerShell, ¡al igual que los comandos nativos!

Opciones predeterminadas


En Linux, es habitual definir alias y / o variables de entorno en los perfiles (perfil de inicio de sesión), estableciendo parámetros predeterminados para los comandos utilizados con frecuencia (por ejemplo, alias ls=ls -AFh o export LESS=-i ). Una de las desventajas de la representación a través del shell wsl.exe no interactivo es que los perfiles no están cargados, por lo tanto, estas opciones no están disponibles de forma predeterminada (es decir, ls en WSL y wsl ls se comportarán de manera diferente con el alias definido anteriormente).

PowerShell proporciona $ PSDefaultParameterValues , un mecanismo estándar para definir parámetros predeterminados, pero solo para cmdlets y funciones avanzadas. Por supuesto, puede realizar funciones avanzadas desde nuestros shells, pero esto introduce complicaciones innecesarias (por ejemplo, PowerShell coincide con nombres de parámetros parciales (por ejemplo, -a corresponde a -ArgumentList ), que entrará en conflicto con los comandos de Linux que aceptan nombres parciales como argumentos), y la sintaxis para definir los valores predeterminados no será la más adecuada (para definir argumentos predeterminados, se requiere el nombre del parámetro en la clave y no solo el nombre del comando).

Sin embargo, con una ligera modificación en nuestros shells, podemos implementar un modelo similar a $PSDefaultParameterValues y habilitar las opciones predeterminadas para los comandos de 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 ' ') } } 

Al $WslDefaultParameterValues a la línea de comando, enviamos los parámetros a través de wsl.exe . A continuación se muestra cómo agregar instrucciones a un perfil de PowerShell para configurar los ajustes predeterminados. ¡Ahora podemos hacerlo!

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

Dado que los parámetros se modelan después de $PSDefaultParameterValues , puede desactivarlos fácilmente temporalmente configurando la tecla "Disabled" en $true . Una ventaja adicional de una tabla hash separada es la capacidad de deshabilitar $WslDefaultParameterValues separado de $PSDefaultParameterValues .

Terminación de argumento


PowerShell permite registrar terminadores de argumentos utilizando el comando Register-ArgumentCompleter . Bash tiene potentes herramientas de finalización programables . WSL le permite invocar bash desde PowerShell. Si podemos registrar los terminadores de argumentos para nuestros envoltorios de funciones de PowerShell y llamar a bash para crear las terminaciones, entonces obtenemos la finalización completa de los argumentos con la misma precisión que en bash.

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

El código es un poco estricto sin comprender algunas de las partes internas de bash, pero básicamente hacemos lo siguiente:

  • Registramos el finalizador de argumentos para todos nuestros envoltorios de funciones pasando la lista de $commands al parámetro -CommandName para Register-ArgumentCompleter .
  • Asignamos cada comando a la función de shell que bash usa para el autocompletado (bash usa $F para definir las especificaciones de autocompletado, abreviatura para complete -F <FUNCTION> ).
  • Convierta los argumentos de PowerShell $wordToComplete , $commandAst y $cursorPosition al formato esperado por las funciones de finalización de bash de acuerdo con las especificaciones de finalización programables de bash.
  • wsl.exe la línea de comando para transferir a wsl.exe , lo que garantiza la configuración correcta del entorno, llama a la función de autocompletado adecuada y muestra los resultados con saltos de línea.
  • Luego llamamos a wsl con la línea de comando, separamos la salida con separadores de línea y generamos resultados de CompletionResults para cada uno, ordenándolos y escapando caracteres como espacios y corchetes que de otro modo serían malinterpretados.

Como resultado, nuestros shells de comandos de Linux usarán exactamente el mismo autocompletado que en bash! Por ejemplo:

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

¡Cada autocompletado proporciona valores específicos del argumento anterior, leyendo datos de configuración, como hosts conocidos, de WSL!

<TAB> pasará por los parámetros. <Ctrl + > mostrará todas las opciones disponibles.

Además, dado que bash autocomplete ahora funciona con nosotros, puede completar automáticamente las rutas de Linux directamente en PowerShell.

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

En los casos en que falla el autocompletado de bash, PowerShell vuelve al sistema predeterminado con rutas de Windows. Por lo tanto, en la práctica, puede usar simultáneamente esas y otras formas a su discreción.

Conclusión


Con PowerShell y WSL, podemos integrar comandos de Linux en Windows como aplicaciones nativas. No es necesario buscar compilaciones de Win32 o utilidades de Linux o interrumpir el flujo de trabajo cambiando al shell de Linux. ¡Simplemente instale WSL , configure su perfil de PowerShell y enumere los comandos que desea importar ! La completa autocompletación de parámetros de comando y ruta para archivos de Linux y Windows es una funcionalidad que ni siquiera se encuentra en los comandos nativos de Windows hoy en día.

El código fuente completo descrito anteriormente, así como recomendaciones adicionales para incluirlo en el flujo de trabajo están disponibles aquí .

¿Qué comandos de Linux te parecen más útiles? ¿Qué otras cosas familiares faltan cuando se trabaja en Windows? Escriba en los comentarios o en GitHub !

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


All Articles