Powershells Äquivalent zu Bashs Prozessersetzung

14

Bash hat <(..)für die Prozessersetzung. Was entspricht Powershell?

Ich weiß $(...), dass es eine gibt , aber sie gibt eine Zeichenfolge zurück, während sie <(..)eine Datei zurückgibt, aus der der äußere Befehl lesen kann, und die erwartet wird.

Ich bin auch nicht auf der Suche nach einer Pipe-basierten Lösung, sondern etwas, das ich in der Mitte der Kommandozeile festhalten kann.

IttayD
quelle
3
afaik gibt es so etwas nicht, aber es wäre interessant, sich als falsch zu erweisen.
Zoredache
4
Können Sie ein Modellbeispiel für die erwartete Verwendung geben? Ich frage mich, ob $ (... | select -expandproperty objectyouwanttopass) für einen einzelnen Substitutionsfall geeignet ist.
Andy
2
In PowerShell ist $ () der Unterausdrucksoperator. Sie können ihn folgendermaßen verwenden: Write-Output "The BITS service is $(Get-Service bits | select -ExpandProperty Stauts)"um den Status des BITS-Dienstes abzurufen, ohne ihn zuerst in eine Variable zu laden. Mit Blick auf die Prozessersetzung ist dies nicht genau dasselbe, aber es könnte das Problem, mit dem Sie konfrontiert sind, noch lösen
mortenya
@Andy: Die Funktion würde bei externen Dienstprogrammen helfen , die Dateinamenoperanden erfordern . Ein Beispiel ist psftp.exefür SFTP - Übertragungen: seine -bOption erfordert Befehle bieten auf dem Server ausgeführt werden über eine Datei , die unbequem ist, wenn man nur laufen wollen, sagen wir, mget *. Wenn PowerShell eine Prozessersetzung hätte, wären Sie in der Lage, so etwas zu tun psftp.exe -l user -p password somehost -b <( "mget *" ).
Mklement

Antworten:

4

Diese Antwort ist NICHT für Sie , wenn Sie:
- selten, wenn überhaupt, externe CLIs verwenden müssen (was im Allgemeinen erstrebenswert ist - native PowerShell-Befehle spielen viel besser zusammen und benötigen keine solche Funktion).
- sind mit Bashs Prozessersetzung nicht vertraut.
Diese Antwort ist für Sie , wenn Sie:
- häufig externe CLIs verwenden (ob aus Gewohnheit oder aufgrund fehlender (guter) PowerShell-nativer Alternativen), insbesondere beim Schreiben von Skripten.
- sind gewöhnt und wissen zu schätzen, was die Prozessersetzung von Bash leisten kann.
- Update : Da PowerShell nun auch auf Unix-Plattformen unterstützt wird, ist diese Funktion von zunehmendem Interesse - siehe diese Funktionsanforderung auf GitHub, was darauf hindeutet, dass PowerShell eine Funktion implementiert, die der Prozessersetzung ähnelt.

In der Unix-Welt wird in Bash / Ksh / Zsh durch eine Prozessersetzung die Befehlsausgabe so behandelt, als wäre es eine temporäre Datei , die nach sich selbst bereinigt. Beispiel cat <(echo 'hello'): catWobei die Ausgabe des echoBefehls als Pfad einer temporären Datei betrachtet wird, die die Befehlsausgabe enthält .

In PowerShell-nativen Befehlen ist eine solche Funktion zwar nicht unbedingt erforderlich, sie kann jedoch im Umgang mit externen CLIs hilfreich sein .

Das Emulieren der Funktion in PowerShell ist umständlich , kann sich aber lohnen, wenn Sie es häufig benötigen.

Stellen Sie sich eine Funktion mit dem Namen vor cf, die einen Skriptblock akzeptiert, den Block ausführt und seine Ausgabe in ein temporäres Verzeichnis schreibt. Datei, die auf Anforderung erstellt wird, und gibt das Temp zurück. Dateipfad ; z.B:

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

Dies ist ein einfaches Beispiel, das die Notwendigkeit einer solchen Funktion nicht gut veranschaulicht . Ein vielleicht überzeugenderes Szenario ist die Verwendung vonpsftp.exe für SFTP Transfer: sein Charge (automatisiert) Verwendung erfordert eine Eingabe bereitstellt Datei die gewünschten Befehle enthält, während solche Befehle leicht als Zeichenfolge im Fluge erzeugt werden können.

Um mit externen Versorgungsunternehmen so weit wie möglich kompatibel zu sein, sollte die temp. Datei sollte UTF-8 verwenden standardmäßig Codierung ohne Stückliste (Byte Order Mark) verwenden, obwohl Sie bei Bedarf eine UTF-8-Stückliste mit anfordern können -BOM.

Leider kann der automatische Bereinigungsaspekt von Prozessersetzungen nicht direkt emuliert werden, sodass ein expliziter Bereinigungsaufruf erforderlich ist . Die Bereinigung erfolgt durch Aufrufen cf ohne Argumente :

  • Für interaktive Nutzung, Sie können die Bereinigung , indem Sie die Bereinigung Aufruf an Ihre automatisieren promptFunktion wie folgt (die promptFunktion , um die Eingabeaufforderung zurückgibt String , sondern auch verwendet werden kann , hinter die Kulissen führen Befehle jedes Mal , wenn die Eingabeaufforderung angezeigt wird, ähnlich wie Bash $PROMPT_COMMANDVariable); Um die Verfügbarkeit in einer interaktiven Sitzung zu überprüfen, fügen Sie cfIhrem PowerShell-Profil Folgendes sowie die folgende Definition hinzu :

    "function prompt { cf 4>`$null; $((get-item function:prompt).definition) }" |
      Invoke-Expression
  • Zur Verwendung in Skripten muss der Block, der cfdas gesamte Skript verwendet, in einen try/ finally-Block eingeschlossen werden, in dem cfohne Argumente für die Bereinigung aufgerufen wird , um sicherzustellen, dass die Bereinigung durchgeführt wird :

# 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 die Implementierung : Erweiterte Funktion ConvertTo-TempFileund ihr prägnanter Alias,cf :

Hinweis : Die Verwendung von New-Module, für die PSv3 + erforderlich ist, um die Funktion über ein dynamisches Modul zu definieren stellt sicher, dass keine Variablenkonflikte zwischen den Funktionsparametern und Variablen wird, auftreten können.

$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 Ausgabepfad [Datei] und / oder eine Dateinamenerweiterung anzugeben.

mklement
quelle
Die Idee, dass Sie dies jemals tun müssten, ist bestenfalls zweifelhaft und würde die Dinge nur erschweren, weil Sie PowerShell einfach nicht verwenden möchten.
Jim B
1
@JimB: Ich persönlich benutze es mit psftp.exe, was mich dazu veranlasste, es zu schreiben. Auch wenn es vorzuziehen ist, alles nativ in PowerShell auszuführen, ist dies nicht immer möglich. Das Aufrufen externer CLIs von PowerShell aus geschieht und wird auch weiterhin geschehen. Wenn Sie immer wieder mit CLIs zu tun haben, für die eine Dateieingabe erforderlich ist, die (leichter) im Speicher oder mit einem anderen Befehl erstellt werden kann, kann die Funktion in dieser Antwort Ihr Leben erleichtern.
mklement
Machst du Witze? nichts davon ist erforderlich. Ich habe noch keinen Befehl gefunden, der nur Dateien mit Befehlen für Parameter akzeptiert. In Bezug auf SFTP zeigte mir eine einfache Suche 2 einfache Add-In-Assemblys, mit denen ich nativ FTP in PowerShell ausführen kann.
Jim B
1
@JimB: Wenn Sie dieses Gespräch konstruktiv fortsetzen möchten, ändern Sie Ihren Ton.
Mklement
2
@JimB GNU Diffutils diff bearbeitet nur Dateien, falls Sie interessiert sind.
Pavel
2

Wenn nicht in doppelte Anführungszeichen eingeschlossen, $(...) ein PowerShell-Objekt (oder was auch immer vom beigefügten Code zurückgegeben wird) zurückgegeben, wobei der beigefügte Code zuerst ausgewertet wird. Dies sollte für Ihre Zwecke geeignet sein ("etwas [I] kann in der Mitte der Befehlszeile bleiben"), vorausgesetzt, die Befehlszeile ist PowerShell.

Sie können dies testen, indem Sie verschiedene Versionen Get-Memberweiterleiten oder sogar 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 nur eine Zeichenfolge zurück.

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

Invoke-Command -ComputerName (Get-Content C:\Temp\Files\new1.txt) -ScriptBlock {<# something #>}
James Ruskin
quelle
Das ist eine fantastische Antwort !!
GregL
Was Sie beschreiben, entspricht nicht der Prozessersetzung durch Bash. Die Prozessersetzung ist für die Verwendung mit Befehlen vorgesehen, für die Dateinamenoperanden erforderlich sind . Das heißt, die Ausgabe eines Befehls, der in einer Prozessersetzung enthalten ist, wird lose in eine temporäre Datei geschrieben, und der Pfad dieser Datei wird zurückgegeben. Darüber hinaus ist die Existenz der Datei auf den Befehl beschränkt, zu dem die Prozessersetzung gehört. Wenn PowerShell eine solche Funktion hätte, würden Sie erwarten, dass Folgendes funktioniert:Get-Content <(Get-ChildItem)
mklement
Bitte korrigieren Sie mich, wenn ich falsch liege, und das ist nicht das, wonach Sie suchen, aber es funktioniert nicht Get-ChildItem | Get-Contentperfekt? Oder könntest du sonst versuchen, Get-Content (Get-ChildItem).FullNameden gleichen Effekt zu erzielen? Sie nähern sich dem möglicherweise aus einer Sicht, die durch einen anderen Skriptansatz stark beeinflusst wird.
James Ruskin
1
Ja, im Bereich von PowerShell ist diese Funktion nicht erforderlich. Es ist nur für die Verwendung mit externen CLIs von Interesse, für die eine Dateieingabe erforderlich ist, und bei denen der Inhalt solcher Dateien mit einem (PowerShell) -Befehl einfach erstellt werden kann. Siehe meinen Kommentar zur Frage für ein reales Beispiel. Möglicherweise benötigen Sie eine solche Funktion nie. Für Benutzer, die häufig externe CLIs aufrufen müssen, ist sie jedoch von Interesse. Sie sollten Ihrer Antwort zumindest vorab sagen, dass Sie die PowerShell- Vorgehensweise demonstrieren - im Gegensatz zu dem, wonach das OP speziell gefragt hat - und warum Sie dies tun.
Mklement