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

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

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

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

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

Responder1

Esta resposta éNão para você, se você:
- raramente, ou nunca, precisar usar CLIs externos (o que geralmente vale a pena tentar - os comandos nativos do PowerShell funcionam muito melhor juntos e não precisam desse recurso).
- não estão familiarizados com a substituição de processos do Bash.
Esta respostaÉ para você, se você:
- usa CLIs externas com frequência (seja por hábito ou devido à falta de (boas) alternativas nativas do PowerShell), especialmente ao escrever scripts.
- estão acostumados e apreciam o que a substituição de processos do Bash pode fazer.
-Atualizar: agora que o PowerShell também é compatível com plataformas Unix, esse recurso é de interesse crescente - consulteesta solicitação de recurso no GitHub, o que sugere que o PowerShell implemente um recurso semelhante à substituição de processos.

No mundo Unix, em Bash/Ksh/Zsh, umsubstituição de processooferece tratar a saída do comando como se fosse temporáriaarquivoque se limpa; por exemplo cat <(echo 'hello'), onde catvê a saída do echocomando como ocaminho de um arquivo temporáriocontendo osaída de comando.

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

Emular o recurso no PowerShell é complicado, mas pode valer a pena, se você precisar dele com frequência.

Imagine uma função chamada cfque aceita um bloco de script, executa o bloco e grava sua saída em um arquivo temporário. arquivo criado sob demanda e retorna a temperatura. 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 aprecisarbem para esse recurso. Talvez um cenário mais convincente seja o uso depsftp.exepara transferências SFTP: seu uso em lote (automatizado) requer o fornecimento de uma entradaarquivocontendo os comandos desejados, enquanto tais comandos podem ser facilmente criados como uma string instantaneamente.

Para ser o mais amplamente compatível possível com utilitários externos, o temp. arquivo deve usarUTF-8codificaçãosem uma lista técnica(marca de ordem de byte) por padrão, embora você possa solicitar uma lista técnica UTF-8 com -BOM, se necessário.

Infelizmente,o automáticolimparaspecto das substituições de processos não pode serdiretamenteemulado, portanto, é necessária uma chamada de limpeza explícita; a limpeza é realizada chamandocf sem argumentos:

  • ParainterativoUsar vocêpodeautomatize a limpeza adicionando a chamada de limpeza à sua promptfunção da seguinte maneira (a promptfunção retorna o promptcorda, mas também pode ser usado para executar comandos nos bastidores sempre que o prompt for exibido, semelhante à $PROMPT_COMMANDvariável do Bash); para disponibilidade em qualquer sessão interativa, adicione o seguinte, bem como a definição cfabaixo, ao seu perfil do PowerShell:

    "function prompt { cf 4>`$null; $((get-item function:prompt).definition) }" |
      Invoke-Expression
    
  • Para usoem roteiros, para garantir que a limpeza seja realizada, o bloco que utiliza cf- potencialmente todo o script - precisa ser encapsulado em um bloco try/ finally, no qual cfsem 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.
}

Aqui está oimplementação: função avançada ConvertTo-TempFilee seu alias sucinto, cf:

Observação: O uso de New-Module, que requer PSv3+, para definir a função por meio de ummódulo dinâmicogarante que não possa haver conflitos de variáveis ​​entre os parâmetros da função e as 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 [arquivo] de saída e/ou extensão de nome de arquivo.

Responder2

Quando não está entre aspas duplas, $(...)retorna um objeto do PowerShell (ou melhor, o que quer que seja retornado pelo código incluído), avaliando primeiro o código incluído. Isso deve ser adequado aos seus propósitos ("algo que [eu] possa ficar no meio da linha de comando"), assumindo que a linha de comando seja o PowerShell.

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

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, `"$(...)" retornará apenas uma string.

Desta forma, se você quisesse inserir, digamos, o conteúdo de um arquivo diretamente em uma linha, você poderia usar algo como:

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

informação relacionada