El equivalente de Powershell al proceso de sustitución de Bash

14

Bash tiene <(..)para el proceso de sustitución. ¿Cuál es el equivalente de Powershell?

Sé que hay $(...), pero devuelve una cadena, mientras que <(..)devuelve un archivo del que puede leer el comando externo, que es lo que espera.

Tampoco estoy buscando una solución basada en tubería, sino algo que pueda pegar en el medio de la línea de comando.

IttayD
fuente
3
afaik no existe tal cosa, pero sería interesante que se demuestre lo contrario.
Zoredache
44
¿Puedes dar un ejemplo simulado de cómo esperarías que se usara? Me pregunto si $ (... | select -expandproperty objectyouwanttopass) podría adaptarse a un solo caso de sustitución.
Andy
2
En PowerShell $ () es el operador de subexpresión, puede usarlo así: Write-Output "The BITS service is $(Get-Service bits | select -ExpandProperty Stauts)"para obtener el estado del servicio BITS sin cargarlo primero en una variable. En cuanto a la sustitución de procesos, esto no es exactamente lo mismo, pero aún podría resolver el problema que enfrenta
mortenya
@Andy: la característica ayudaría con las utilidades externas que requieren operandos de nombre de archivo . Un ejemplo es psftp.exepara las transferencias de SFTP: su -bopción, debe proporcionar comandos para ejecutar en el servidor a través de un archivo , el cual es un inconveniente, si sólo desea ejecutar, por ejemplo, mget *. Si PowerShell tuviera sustitución de proceso, podría hacer algo como eso psftp.exe -l user -p password somehost -b <( "mget *" ).
Mklement

Respuestas:

4

Esta respuesta NO es para usted , si usted:
- rara vez, si es que alguna vez, necesita usar CLI externas (que generalmente vale la pena esforzarse - los comandos nativos de PowerShell se juegan mucho mejor juntos y no necesitan tal característica).
- No estoy familiarizado con el proceso de sustitución de Bash.
Esta respuesta ES para usted , si usted:
- utiliza con frecuencia CLI externas (ya sea por costumbre o por falta de alternativas (buenas) nativas de PowerShell), especialmente al escribir scripts.
- están acostumbrados y aprecian lo que puede hacer la sustitución del proceso de Bash.
- Actualización : ahora que PowerShell también es compatible con plataformas Unix, esta característica es de creciente interés; consulte esta solicitud de característica en GitHub, lo que sugiere que PowerShell implemente una característica similar a la sustitución de procesos.

En el mundo Unix, en Bash / Ksh / Zsh, una sustitución de proceso ofrece el tratamiento de la salida del comando como si fuera un archivo temporal que se limpia después de sí mismo; por ejemplo cat <(echo 'hello'), donde catve la salida del echocomando como la ruta de un archivo temporal que contiene la salida del comando .

Si bien los comandos nativos de PowerShell no tienen una necesidad real de tal característica, puede ser útil cuando se trata de CLI externas .

Emular la función en PowerShell es engorroso , pero puede valer la pena si lo necesita con frecuencia.

Imagine una función llamada cfque acepte un bloque de script, ejecute el bloque y escriba su salida en una temp. archivo creado bajo demanda y devuelve la temp ruta del archivo ; p.ej:

 findstr.exe "Windows" (cf { Get-ChildItem c:\ }) # findstr sees the temp. file's path.

Este es un ejemplo simple que no ilustra bien la necesidad de tal característica. Quizás un escenario más convincente es el uso de psftp.exetransferencias SFTP: su uso por lotes (automatizado) requiere proporcionar un archivo de entrada que contenga los comandos deseados, mientras que dichos comandos se pueden crear fácilmente como una cadena sobre la marcha.

Para ser tan ampliamente compatible con las utilidades externas como sea posible, la temp. archivo debe utilizar UTF-8 codificación sin BOM (marca de orden de bytes) de forma predeterminada, aunque se puede solicitar una lista de materiales UTF-8 con -BOM, si es necesario.

Desafortunadamente, el aspecto de limpieza automática de las sustituciones de proceso no se puede emular directamente , por lo que se necesita una llamada de limpieza explícita ; la limpieza se realiza llamando cf sin argumentos :

  • Para uso interactivo , puede automatizar la limpieza agregando la llamada de limpieza a su promptfunción de la siguiente manera (la promptfunción devuelve la cadena de solicitud , pero también se puede usar para ejecutar comandos detrás de escena cada vez que se muestra la solicitud, similar a Bash $PROMPT_COMMANDvariable); para disponibilidad en cualquier sesión interactiva, agregue lo siguiente y la definición de cfabajo a su perfil de PowerShell:

    "function prompt { cf 4>`$null; $((get-item function:prompt).definition) }" |
      Invoke-Expression
  • Para su uso en scripts , para garantizar que se realice la limpieza, el bloque que usa cf, potencialmente todo el script, debe estar envuelto en un bloque try/ finally, en el que cfsin argumentos se requiere la limpieza:

# Example
try {

  # Pass the output from `Get-ChildItem` via a temporary file.
  findstr.exe "Windows" (cf { Get-ChildItem c:\ })

  # cf() will reuse the existing temp. file for additional invocations.
  # Invoking it without parameters will delete the temp. file.

} finally {
  cf  # Clean up the temp. file.
}

Aquí está la implementación : función avanzada ConvertTo-TempFiley su breve alias cf:

Nota : El uso de New-Module, que requiere PSv3 +, para definir la función a través de un módulo dinámico asegura que no pueda haber conflictos de variables entre los parámetros de la función y las variables referenciadas dentro del bloque de script pasado.

$null = New-Module {  # Load as dynamic module
  # Define a succinct alias.
  set-alias cf ConvertTo-TempFile
  function ConvertTo-TempFile {
    [CmdletBinding(DefaultParameterSetName='Cleanup')]
    param(
        [Parameter(ParameterSetName='Standard', Mandatory=$true, Position=0)]
        [ScriptBlock] $ScriptBlock
      , [Parameter(ParameterSetName='Standard', Position=1)]
        [string] $LiteralPath
      , [Parameter(ParameterSetName='Standard')]
        [string] $Extension
      , [Parameter(ParameterSetName='Standard')]
        [switch] $BOM
    )

    $prevFilePath = Test-Path variable:__cttfFilePath
    if ($PSCmdlet.ParameterSetName -eq 'Cleanup') {
      if ($prevFilePath) { 
        Write-Verbose "Removing temp. file: $__cttfFilePath"
        Remove-Item -ErrorAction SilentlyContinue $__cttfFilePath
        Remove-Variable -Scope Script  __cttfFilePath
      } else {
        Write-Verbose "Nothing to clean up."
      }
    } else { # script block specified
      if ($Extension -and $Extension -notlike '.*') { $Extension = ".$Extension" }
      if ($LiteralPath) {
        # Since we'll be using a .NET framework classes directly, 
        # we must sync .NET's notion of the current dir. with PowerShell's.
        [Environment]::CurrentDirectory = $pwd
        if ([System.IO.Directory]::Exists($LiteralPath)) { 
          $script:__cttfFilePath = [IO.Path]::Combine($LiteralPath, [IO.Path]::GetRandomFileName() + $Extension)
          Write-Verbose "Creating file with random name in specified folder: '$__cttfFilePath'."
        } else { # presumptive path to a *file* specified
          if (-not [System.IO.Directory]::Exists((Split-Path $LiteralPath))) {
            Throw "Output folder '$(Split-Path $LiteralPath)' must exist."
          }
          $script:__cttfFilePath = $LiteralPath
          Write-Verbose "Using explicitly specified file path: '$__cttfFilePath'."
        }
      } else { # Create temp. file in the user's temporary folder.
        if (-not $prevFilePath) { 
          if ($Extension) {
            $script:__cttfFilePath = [IO.Path]::Combine([IO.Path]::GetTempPath(), [IO.Path]::GetRandomFileName() + $Extension)
          } else {
            $script:__cttfFilePath = [IO.Path]::GetTempFilename() 
          }
          Write-Verbose "Creating temp. file: $__cttfFilePath"
        } else {
          Write-Verbose "Reusing temp. file: $__cttfFilePath"      
        }
      }
      if (-not $BOM) { # UTF8 file *without* BOM
        # Note: Out-File, sadly, doesn't support creating UTF8-encoded files 
        #       *without a BOM*, so we must use the .NET framework.
        #       [IO.StreamWriter] by default writes UTF-8 files without a BOM.
        $sw = New-Object IO.StreamWriter $__cttfFilePath
        try {
            . $ScriptBlock | Out-String -Stream | % { $sw.WriteLine($_) }
        } finally { $sw.Close() }
      } else { # UTF8 file *with* BOM
        . $ScriptBlock | Out-File -Encoding utf8 $__cttfFilePath
      }
      return $__cttfFilePath
    }
  }
}

Tenga en cuenta la capacidad de especificar opcionalmente una ruta de salida [archivo] y / o una extensión de nombre de archivo.

mklement
fuente
La idea de que alguna vez necesitaría hacer esto es dudosa en el mejor de los casos, y simplemente estaría haciendo las cosas más difíciles por el simple hecho de no querer usar PowerShell.
Jim B
1
@ JimB: personalmente lo uso psftp.exe, que es lo que me impulsó a escribirlo. Aunque es preferible hacer todo de forma nativa en PowerShell, eso no siempre es posible; invocar CLI externas de PowerShell ocurre y continuará sucediendo; Si se encuentra lidiando repetidamente con CLI que requieren la entrada de archivos que pueden (más) fácilmente construirse en la memoria / por otro comando, la función en esta respuesta puede facilitarle la vida.
mklement
Estas bromeando nada de eso se requiere. Todavía tengo que encontrar un comando que solo acepte archivos con comandos para parámetros. En lo que respecta a SFTP, una búsqueda simple me mostró 2 conjuntos de complementos simples para realizar FTP de forma nativa en PowerShell.
Jim B
1
@ JimB: si desea continuar esta conversación de manera constructiva, cambie su tono.
mklement
2
@JimB GNU Diffutils diff solo opera en archivos, en caso de que esté interesado.
Pavel
2

Cuando no se incluye entre comillas dobles, $(...)devuelve un Objeto de PowerShell (o más bien, lo que devuelve el código adjunto), evaluando primero el código adjunto. Esto debería ser adecuado para sus propósitos ("algo [I] puede pegarse en el medio de la línea de comando"), suponiendo que la línea de comando sea PowerShell.

Puede probar esto conectando varias versiones a Get-Member, o incluso simplemente enviándolo directamente.

PS> "$(ls C:\Temp\Files)"
new1.txt new2.txt

PS> $(ls C:\Temp\Files)


    Directory: C:\Temp\Files


Mode                LastWriteTime         Length Name                                                                      
----                -------------         ------ ----                                                                      
-a----       02/06/2015     14:58              0 new1.txt                                                                  
-a----       02/06/2015     14:58              0 new2.txt   

PS> "$(ls C:\Temp\Files)" | gm


   TypeName: System.String
<# snip #>

PS> $(ls C:\Temp\Files) | gm


   TypeName: System.IO.FileInfo
<# snip #>

Cuando se incluye entre comillas dobles, como habrá notado, `" $ (...) "simplemente devolverá una cadena.

De esta manera, si quisiera insertar, digamos, el contenido de un archivo directamente en una línea, podría usar algo como:

Invoke-Command -ComputerName (Get-Content C:\Temp\Files\new1.txt) -ScriptBlock {<# something #>}
James Ruskin
fuente
¡Esta es una respuesta fantástica!
GregL
Lo que estás describiendo no es el equivalente de la sustitución del proceso de Bash. La sustitución de procesos está diseñada para usarse con comandos que requieren operandos de nombre de archivo ; es decir, la salida de un comando encerrado en una sustitución de proceso, en términos generales, se escribe en un archivo temporal y se devuelve la ruta de ese archivo ; Además, la existencia del archivo se limita al comando del que forma parte la sustitución del proceso. Si PowerShell tuviera tal característica, esperaría que algo como lo siguiente funcionara:Get-Content <(Get-ChildItem)
mklement
Corríjame si me equivoco, y esto no es lo que está buscando, pero ¿no Get-ChildItem | Get-Contentfunciona perfectamente? ¿O de lo contrario podría intentar Get-Content (Get-ChildItem).FullNameel mismo efecto? Puede estar abordando esto desde una vista completamente influenciada por otro enfoque de secuencias de comandos.
James Ruskin
1
Sí, en el ámbito de PowerShell no hay necesidad de esta función; solo es de interés para su uso con CLI externas que requieren la entrada de archivos , y donde el contenido de dichos archivos se construye fácilmente con un comando (PowerShell). Vea mi comentario sobre la pregunta para un ejemplo del mundo real. Es posible que nunca necesite una función de este tipo, pero para las personas que con frecuencia necesitan llamar a CLI externas es de interés. Al menos, debe presentar su respuesta al decir que está demostrando la forma en que PowerShell hace las cosas, en lugar de lo que el OP solicitó específicamente, y por qué lo hace.
Mklement