
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 cat
die Ausgabe des echo
Befehls 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.exe
fü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 -BOM
bei 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
prompt
wie folgt zu Ihrer Funktion hinzufügen (dieprompt
Funktion gibt die Eingabeaufforderung zurückSchnur, kann aber auch verwendet werden, um bei jeder Anzeige der Eingabeaufforderung Befehle im Hintergrund auszuführen, ähnlich der$PROMPT_COMMAND
Variable von Bash); für die Verfügbarkeit in jeder interaktiven Sitzung fügen Sie Folgendes sowie diecf
folgende 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
cf
eingeschlossen werden , in dem ohne Argumente zur Bereinigung aufgerufen wird:try
finally
cf
# 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-TempFile
und 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 #>}