Powershells Äquivalent zur Prozesssubstitution von Bash

Powershells Äquivalent zur Prozesssubstitution von Bash

Bash hat <(..)für die Prozesssubstitution. Was ist das Äquivalent von Powershell?

Ich weiß, dass es das gibt $(...), aber es gibt einen String zurück, während <(..)eine Datei zurückgegeben wird, aus der der äußere Befehl lesen kann, was auch erwartet wird.

Ich suche auch keine Pipe-basierte Lösung, sondern etwas, das ich in die Mitte der Befehlszeile einfügen kann.

Antwort1

Diese Antwort istNicht für dich, wenn Sie:
- selten oder nie externe CLIs verwenden müssen (was im Allgemeinen erstrebenswert ist - PowerShell-native Befehle funktionieren viel besser zusammen und benötigen keine solche Funktion).
- mit der Prozesssubstitution von Bash nicht vertraut sind.
Diese AntwortIst für Sie, wenn Sie:
– häufig externe CLIs verwenden (sei es aus Gewohnheit oder aufgrund fehlender (guter) PowerShell-nativer Alternativen), insbesondere beim Schreiben von Skripten.
– an die Prozesssubstitution von Bash gewöhnt sind und diese zu schätzen wissen.
Aktualisieren: Da PowerShell nun auch auf Unix-Plattformen unterstützt wird, ist dieses Feature von zunehmendem Interesse - siehediese Funktionsanforderung auf GitHub, was darauf hindeutet, dass PowerShell eine Funktion ähnlich der Prozesssubstitution implementiert.

In der Unix-Welt, in Bash/Ksh/Zsh, einProzesssubstitutionbietet die Behandlung der Befehlsausgabe als wäre sie eine temporäreDateidas nach sich selbst aufräumt; z. B. sieht cat <(echo 'hello'), wobei catdie Ausgabe des echoBefehls alsPfad einer temporären Dateienthält dieBefehlsausgabe.

Obwohl PowerShell-native Befehle keine wirkliche Notwendigkeit für eine solche Funktion haben, kann sie praktisch sein, wenn man mitexterne CLIs.

Die Emulation der Funktion in PowerShell ist umständlich, aber es kann sich lohnen, wenn Sie es oft brauchen.

Stellen Sie sich eine Funktion namens vor cf, die einen Skriptblock akzeptiert, den Block ausführt und seine Ausgabe in eine bei Bedarf erstellte temporäre Datei schreibt und den Pfad der temporären Datei zurückgibt; z.B:

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

Dies ist ein einfaches Beispiel, das nicht veranschaulicht,brauchenfür eine solche Funktion gut. Ein vielleicht überzeugenderes Szenario ist die Verwendung vonpsftp.exefür SFTP-Übertragungen: die Batch-Verwendung (automatisiert) erfordert die Bereitstellung eines InputsDateidie die gewünschten Befehle enthalten, wobei solche Befehle problemlos spontan als Zeichenfolge erstellt werden können.

Um eine möglichst weitgehende Kompatibilität mit externen Dienstprogrammen zu gewährleisten, sollte die temporäre DateiUTF-8Codierungohne Stückliste(Byte-Order-Mark) ist standardmäßig aktiviert, obwohl Sie -BOMbei Bedarf auch mit eine UTF-8-BOM anfordern können.

Bedauerlicherweise,die automatischeAufräumenAspekt der Prozesssubstitution kann nichtdirektemuliert, daher ist ein expliziter Bereinigungsaufruf erforderlich; die Bereinigung erfolgt durch den Aufrufcf ohne Argumente:

  • Fürinteraktivdich benutzendürfenAutomatisieren Sie die Bereinigung, indem Sie den Bereinigungsaufruf promptwie folgt zu Ihrer Funktion hinzufügen (die promptFunktion gibt die Eingabeaufforderung zurückSchnur, kann aber auch verwendet werden, um bei jeder Anzeige der Eingabeaufforderung Befehle im Hintergrund auszuführen, ähnlich der $PROMPT_COMMANDVariable von Bash); für die Verfügbarkeit in jeder interaktiven Sitzung fügen Sie Folgendes sowie die cffolgende Definition zu Ihrem PowerShell-Profil hinzu:

    "function prompt { cf 4>`$null; $((get-item function:prompt).definition) }" |
      Invoke-Expression
    
  • Für den Einsatzin SkriptenUm sicherzustellen, dass die Bereinigung durchgeführt wird, muss der verwendete Block – möglicherweise das gesamte Skript – in einen / -Block cfeingeschlossen werden , in dem ohne Argumente zur Bereinigung aufgerufen wird:tryfinallycf

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

Hier ist dieImplementierung: erweiterte Funktion ConvertTo-TempFileund ihr prägnanter Alias cf:

Notiz: Die Verwendung von New-Module, das PSv3+ erfordert, zur Definition der Funktion über eindynamisches Modulstellt sicher, dass es zu keinen Variablenkonflikten zwischen den Funktionsparametern und den innerhalb des übergebenen Skriptblocks referenzierten Variablen kommen kann.

$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
    }
  }
}

Beachten Sie die Möglichkeit, optional einen Ausgabe[datei]pfad und/oder eine Dateinamenerweiterung anzugeben.

Antwort2

Wenn es nicht in Anführungszeichen eingeschlossen ist, $(...)wird ein PowerShell-Objekt zurückgegeben (oder vielmehr, was auch immer vom eingeschlossenen Code zurückgegeben wird), wobei zuerst der eingeschlossene Code ausgewertet wird. Dies sollte für Ihre Zwecke geeignet sein („etwas, das [ich] in die Mitte der Befehlszeile einfügen kann“), vorausgesetzt, dass es sich bei der Befehlszeile um PowerShell handelt.

Sie können dies testen Get-Member, indem Sie verschiedene Versionen an weiterleiten oder es einfach direkt ausgeben.

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

Wie Sie bemerkt haben, gibt „$(...)“ in doppelten Anführungszeichen einfach eine Zeichenfolge zurück.

Wenn Sie beispielsweise den Inhalt einer Datei direkt in eine Zeile einfügen möchten, können Sie Folgendes verwenden:

Invoke-Command -ComputerName (Get-Content C:\Temp\Files\new1.txt) -ScriptBlock {<# something #>}

verwandte Informationen