Equivalente de Powershell à substituição do processo de Bash

13

O Bash tem <(..) para substituição do processo. Qual é o equivalente do Powershell?

Eu sei que há $(...) , mas retorna uma string, enquanto <(..) retorna um arquivo do qual o comando externo pode ler, que é o que ele espera.

Eu também não estou procurando por uma solução baseada em pipe, mas algo que eu possa colocar no meio da linha de comando.

    
por IttayD 05.05.2015 / 07:50

2 respostas

3

Esta resposta é NÃO para você , se você:
   - raramente, ou nunca, precisam usar CLIs externas (as quais geralmente valem a pena - os comandos nativos do PowerShell funcionam muito melhor juntos e não precisam de um recurso desse tipo).    - não estão familiarizados com a substituição de processos do Bash.
Esta resposta IS for you , se você:
   - freqüentemente usam CLIs externos (seja por hábito ou por falta de (boas) alternativas nativas do PowerShell), especialmente ao escrever scripts.
   - são usados e apreciam o que a substituição de processos do Bash pode fazer.
   - Atualizar : agora que o PowerShell também é suportado em plataformas Unix, esse recurso é de interesse crescente - consulte esta solicitação de recurso no GitHub , que sugere que o PowerShell implemente um recurso semelhante ao processo de substituição.

No mundo Unix, em Bash / Ksh / Zsh, uma substituição de processo oferece tratamento de saída de comando como se fosse um arquivo temporário que é limpo após ele mesmo; por exemplo. cat <(echo 'hello') , onde cat vê a saída do comando echo como o caminho de um arquivo temporário contendo a saída do comando .

Embora os comandos nativos do PowerShell não tenham necessidade real de tal recurso, ele pode ser útil ao lidar com CLIs externas .

A emulação do recurso no PowerShell é incômoda , mas pode valer a pena, se você precisar dele com frequência.

Imagine uma função chamada cf que aceita um bloco de script, executa o bloco e grava sua saída em um temp. arquivo criado sob demanda e retorna o temp. caminho do arquivo ; por exemplo:

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

Este é um exemplo simples que não ilustra bem a necessidade para tal recurso. Talvez um cenário mais convincente seja o uso de psftp.exe para transferências de SFTP: o uso em lote (automatizado) requer o fornecimento de um arquivo de entrada contendo os comandos desejados, enquanto tais comandos podem ser facilmente criados como uma string em tempo real.

Para ser tão compatível com utilitários externos quanto possível, a temp. O arquivo deve usar UTF-8 codificação sem BOM (marca de ordem de byte) por padrão, embora você possa solicitar uma BOM UTF-8 com -BOM , se necessário.

Infelizmente, o aspecto automático cleanup das substituições de processos não pode ser diretamente , portanto é necessária uma chamada de limpeza explícita ; a limpeza é executada chamando cf sem argumentos :

  • Para uso interativo , você pode automatizar a limpeza adicionando a chamada de limpeza à sua função prompt da seguinte forma (a função prompt retorna o prompt string , mas também pode ser usado para executar comandos por trás das cenas toda vez que o prompt é exibido, similar à variável $PROMPT_COMMAND do Bash); para disponibilidade em qualquer sessão interativa, adicione o seguinte, bem como a definição de cf abaixo ao seu perfil do PowerShell:

    "function prompt { cf 4>'$null; $((get-item function:prompt).definition) }" |
      Invoke-Expression
    
  • Para usar em scripts , para garantir que a limpeza seja executada, o bloco que usa cf - potencialmente todo o script - precisa ser agrupado em try / finally block, no qual cf sem argumentos é chamado para limpeza:

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

Veja a implementação : função avançada ConvertTo-TempFile e seu alias sucinto, cf :

Nota : O uso de New-Module , que requer PSv3 +, para definir a função através de um módulo dinâmico garante que não pode haver conflitos de variáveis entre os parâmetros da função e variáveis referenciadas dentro do bloco de script passado.

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

Observe a capacidade de especificar opcionalmente um caminho de saída [arquivo] e / ou extensão de nome de arquivo.

    
por 23.01.2016 / 23:34
2

Quando não estiver entre aspas duplas, $(...) retornará um Objeto do PowerShell (ou melhor, seja o que for retornado pelo código incluído), avaliando primeiro o código incluído. Isso deve ser adequado aos seus propósitos ("algo que [eu] posso colocar no meio da linha de comando"), supondo que a linha de comando seja PowerShell.

Você pode testar isso canalizando várias versões para Get-Member , ou até mesmo enviando diretamente para ela.

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

Quando colocado entre aspas duplas, como você notou, '"$ (...)" apenas retornará uma string.

Dessa forma, se você quiser inserir, digamos, o conteúdo de um arquivo diretamente em uma linha, poderá usar algo como:

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

Tags