Questa risposta NON fa per te se:
- raramente, se mai, hai bisogno di usare CLI esterne (che generalmente vale la pena cercare - i comandi nativi di PowerShell giocano molto meglio insieme e non hanno bisogno di una tale funzione).
- non hanno familiarità con la sostituzione del processo di Bash.
Questa risposta è per te se:
- usi frequentemente CLI esterne (per abitudine o per mancanza di (buone) alternative native di PowerShell), specialmente durante la scrittura di script.
- sono abituati ad apprezzare cosa può fare la sostituzione di processo di Bash.
- Aggiornamento : ora che PowerShell è supportato anche su piattaforme Unix, questa funzionalità è di crescente interesse - vedi questa richiesta di funzionalità su GitHub, il che suggerisce che PowerShell implementa una funzione simile al processo di sostituzione.
Nel mondo Unix, in Bash / Ksh / Zsh, una sostituzione di processo offre il trattamento dell'output del comando come se fosse un file temporaneo che si ripulisce da solo; ad esempio cat <(echo 'hello')
, dove cat
vede l'output del echo
comando come il percorso di un file temporaneo contenente l' output del comando .
Sebbene i comandi nativi di PowerShell non abbiano realmente bisogno di tale funzionalità, può essere utile quando si ha a che fare con CLI esterne .
Emulare la funzionalità in PowerShell è complicato , ma può valerne la pena, se ti accorgi di averne bisogno spesso.
Immagina una funzione denominata cf
che accetta un blocco di script, esegue il blocco e scrive il suo output su un temp. file creato su richiesta e restituisce la temp. percorso del file ; per esempio:
findstr.exe "Windows" (cf { Get-ChildItem c:\ }) # findstr sees the temp. file's path.
Questo è un semplice esempio che non illustra bene la necessità di una tale funzione. Forse uno scenario più convincente è l'uso dei psftp.exe
trasferimenti SFTP: il suo utilizzo in batch (automatizzato) richiede di fornire un file di input contenente i comandi desiderati, mentre tali comandi possono essere facilmente creati come una stringa al volo.
Per essere il più ampiamente possibile compatibile con le utility esterne, la temp. il file dovrebbe usare la codifica UTF-8 senza una distinta base (segno di ordine dei byte) per impostazione predefinita, sebbene sia possibile richiedere una distinta base UTF-8 con -BOM
, se necessario.
Sfortunatamente, l' aspetto della pulizia automatica delle sostituzioni di processo non può essere emulato direttamente , quindi è necessaria una chiamata di pulizia esplicita ; la pulizia viene eseguita chiamando cf
senza argomenti :
Per un uso interattivo , puoi automatizzare la pulizia aggiungendo la chiamata di pulizia alla tua prompt
funzione come segue (la prompt
funzione restituisce la stringa del prompt , ma può anche essere usata per eseguire comandi dietro le quinte ogni volta che il prompt viene visualizzato, simile a quello di Bash $PROMPT_COMMAND
variabile); per la disponibilità in qualsiasi sessione interattiva, aggiungere quanto segue e la definizione di cf
seguito al profilo PowerShell:
"function prompt { cf 4>`$null; $((get-item function:prompt).definition) }" |
Invoke-Expression
Per l'uso negli script , per garantire che venga eseguita la pulizia, il blocco che utilizza cf
- potenzialmente l'intero script - deve essere racchiuso in un blocco try
/ finally
, in cui cf
senza argomenti viene chiamato per la pulizia:
# 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.
}
Ecco l' implementazione : funzione avanzata ConvertTo-TempFile
e il suo alias succinta, cf
:
Nota : L'uso di New-Module
, che richiede PSv3 +, per definire la funzione tramite un modulo dinamico garantisce che non possano esserci conflitti di variabili tra i parametri della funzione e le variabili a cui si fa riferimento all'interno del blocco di script passato.
$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
}
}
}
Notare la possibilità di specificare facoltativamente un percorso [file] di output e / o l'estensione del nome file.