Powershell equivale alla sostituzione del processo di Bash


14

Bash ha <(..)per la sostituzione del processo. Qual è l'equivalente di Powershell?

So che c'è $(...), ma restituisce una stringa, mentre <(..)restituisce un file dal quale può leggere il comando esterno, che è quello che si aspetta.

Inoltre non sto cercando una soluzione basata su pipe, ma qualcosa che posso attaccare nel mezzo della riga di comando.


3
afaik non esiste una cosa del genere, ma sarebbe interessante essere smentito.
Zoredache,

4
Puoi fare un esempio di come ti aspetteresti che venga usato? Mi chiedo se $ (... | select -expandproperty objectyouwanttopass) potrebbe soddisfare un singolo caso di sostituzione.
Andy,

2
In PowerShell $ () è l'operatore di sottoespressione, è possibile utilizzarlo in questo modo: Write-Output "The BITS service is $(Get-Service bits | select -ExpandProperty Stauts)"per ottenere lo stato del servizio BITS senza caricarlo prima in una variabile. Guardando la sostituzione del processo, questo non è esattamente lo stesso, ma potrebbe ancora risolvere il problema che stai affrontando
mortenya,

@Andy: la funzione sarebbe utile con le utility esterne che richiedono operandi di nome file . Un esempio è psftp.exeper i trasferimenti SFTP: la sua -bopzione richiede di fornire comandi da eseguire sul server tramite un file , che è scomodo, se si desidera eseguire, ad esempio, mget *. Se PowerShell avesse sostituito il processo, saresti in grado di fare qualcosa del genere psftp.exe -l user -p password somehost -b <( "mget *" ).
mklement

Risposte:


4

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 catvede l'output del echocomando 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 cfche 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.exetrasferimenti 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 promptfunzione come segue (la promptfunzione 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_COMMANDvariabile); per la disponibilità in qualsiasi sessione interattiva, aggiungere quanto segue e la definizione di cfseguito 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 cfsenza 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-TempFilee 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.


L'idea che avresti mai bisogno di farlo è nella migliore delle ipotesi e renderebbe le cose più difficili per il semplice fatto di non voler usare PowerShell.
Jim B,

1
@JimB: lo uso personalmente con psftp.exe, che è ciò che mi ha spinto a scriverlo. Anche se è preferibile fare tutto nativamente in PowerShell, non è sempre possibile; invocare CLI esterne da PowerShell fa e continuerà a verificarsi; se ti ritrovi a gestire ripetutamente CLI che richiedono input di file che possono (più) essere facilmente costruiti in memoria / con un altro comando, la funzione in questa risposta può semplificarti la vita.
mklement

Stai scherzando? nulla di tutto ciò è richiesto. Devo ancora trovare un comando che accetta solo file con comandi per parametri. Per quanto riguarda SFTP, una semplice ricerca mi ha mostrato 2 semplici assemblaggi di componenti aggiuntivi per eseguire nativamente FTP in PowerShell.
Jim B,

1
@JimB: se vuoi continuare questa conversazione in modo costruttivo, cambia tono.
mklement

2
@JimB GNU Diffutils diff funziona solo su file, nel caso tu sia interessato.
Pavel,

2

Se non racchiuso tra virgolette doppie, $(...)restituisce un oggetto PowerShell (o meglio, qualunque cosa venga restituita dal codice racchiuso), valutando prima il codice racchiuso. Questo dovrebbe essere adatto ai tuoi scopi ("qualcosa [I] può attaccarsi nel mezzo della riga di comando"), supponendo che la riga di comando sia PowerShell.

Puoi testarlo eseguendo il piping di varie versioni Get-Membero semplicemente inviandolo direttamente.

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

Se racchiuso tra virgolette doppie, come hai notato, "$ (...)" restituirà solo una stringa.

In questo modo, se si desidera inserire, ad esempio, il contenuto di un file direttamente su una riga, è possibile utilizzare qualcosa del tipo:

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

Questa è una risposta fantastica !!
GregL

Quello che stai descrivendo non è l'equivalente della sostituzione del processo di Bash. La sostituzione di processo è progettata per l'uso con comandi che richiedono operandi di nome file ; vale a dire, l'output di un comando racchiuso in una sostituzione di processo viene, vagamente parlando, scritto in un file temporaneo e viene restituito il percorso di quel file ; inoltre, l'esistenza del file è compresa nel comando di cui fa parte la sostituzione del processo. Se PowerShell avesse una tale funzionalità, ti aspetteresti che funzioni come segue:Get-Content <(Get-ChildItem)
mklement

Per favore, correggimi se sbaglio, e questo non è quello che stai cercando, ma non Get-ChildItem | Get-Contentfunziona perfettamente? Oppure potresti provare Get-Content (Get-ChildItem).FullNameper lo stesso effetto? Forse ti stai avvicinando a questo punto di vista da una visione completamente influenzata da un altro approccio di scripting.
James Ruskin,

1
Sì, nel regno di PowerShell non è necessaria questa funzionalità; è interessante solo per l'utilizzo con CLI esterne che richiedono input di file e in cui il contenuto di tali file è facilmente costruito con un comando (PowerShell). Vedi il mio commento sulla domanda per un esempio del mondo reale. Potresti non aver mai bisogno di una tale funzionalità, ma per le persone che hanno spesso bisogno di chiamare CLI esterne è interessante. Dovresti almeno prefigurare la tua risposta dicendo che stai dimostrando il modo in cui PowerShell fa le cose - al contrario di ciò che l'OP ha specificamente richiesto - e perché lo stai facendo.
mklement
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.