Powershell é equivalente à substituição do processo de Bash

14

O 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 que o comando externo pode ler, e é o que ele espera.

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

IttayD
fonte
3
afaik não existe, mas seria interessante provar que está errado.
Zoredache 5/05
4
Você pode dar um exemplo de mock-up de como você espera que seja usado? Gostaria de saber se $ (... | select -expandproperty objectyouwanttopass) pode servir para um único caso de substituição.
Andy
2
No PowerShell $ () é o operador de subexpressão, você pode usá-lo assim: Write-Output "The BITS service is $(Get-Service bits | select -ExpandProperty Stauts)"para obter o status do serviço BITS sem carregá-lo em uma variável primeiro. Olhando para processo de substituição, isso não é exatamente o mesmo, mas ainda pode resolver o problema que você está enfrentando
mortenya
@ Andy: O recurso ajudaria com utilitários externos que exigem operandos de nome de arquivo . Um exemplo é psftp.exepara transferências de SFTP: sua -bopção exige que você forneça comandos para executar no servidor por meio de um arquivo , o que é inconveniente, se você deseja apenas executar, digamos mget *,. Se o PowerShell tivesse substituição de processo, você seria capaz de fazer algo parecido psftp.exe -l user -p password somehost -b <( "mget *" ).
precisa saber é

Respostas:

4

Esta resposta NÃO é para você , se:
- raramente, se alguma vez precisar usar CLIs externas (o que geralmente vale a pena procurar - os comandos nativos do PowerShell funcionam muito melhor juntos e não precisam desse recurso).
- não está familiarizado com a substituição de processos de Bash.
Esta resposta é para você , se você:
- costuma usar CLIs externas (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.
- Atualização : agora que o PowerShell também é suportado nas plataformas Unix, esse recurso é de interesse crescente - consulte esta solicitação de recurso no GitHub, o que sugere que o PowerShell implemente um recurso semelhante ao processo de substituição.

No mundo Unix, no Bash / Ksh / Zsh, uma substituição de processo oferece tratamento da saída do comando como se fosse um arquivo temporário que se limpa; por exemplo cat <(echo 'hello'), onde catvê a saída do echocomando como o caminho de um arquivo temporário que contém a saída do comando .

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

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

Imagine uma função nomeada cfque aceita um bloco de script, executa o bloco e grava sua saída em um temp. arquivo criado sob demanda e retorna a 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 desse recurso. Talvez um cenário mais convincente seja o uso de psftp.exetransferências SFTP: seu uso em lote (automatizado) requer o fornecimento de um arquivo de entrada contendo os comandos desejados, enquanto esses comandos podem ser facilmente criados como uma string em movimento.

Para ser o mais amplamente possível possível com utilitários externos, a temperatura. arquivo deve usar UTF-8 codificação sem uma BOM (marca de ordem de bytes) por padrão, mas você pode solicitar um BOM UTF-8 com -BOM, se necessário.

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

  • Para uso interativo , você pode automatizar a limpeza adicionando a chamada de limpeza à sua promptfunção da seguinte forma (a promptfunção retorna a sequência de prompt , mas também pode ser usada para executar comandos nos bastidores toda vez que o prompt é exibido, semelhante ao do Bash $PROMPT_COMMANDvariável); para disponibilidade em qualquer sessão interativa, adicione o seguinte e a definição cfabaixo ao seu perfil do PowerShell:

    "function prompt { cf 4>`$null; $((get-item function:prompt).definition) }" |
      Invoke-Expression
  • Para uso em scripts , para garantir que a limpeza seja realizada, o bloco que usa cf- potencialmente todo o script - precisa ser empacotado 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á a implementação : função avançada ConvertTo-TempFilee seu alias sucinto cf:

Nota : O uso de New-Module, que requer PSv3 +, para definir a função por meio de um módulo dinâmico garante que não haja 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 [arquivo] de saída e / ou extensão de nome de arquivo.

mklement
fonte
A idéia de que você precisaria fazer isso é duvidosa, na melhor das hipóteses, e simplesmente tornaria as coisas mais difíceis por não querer simplesmente usar o PowerShell.
Jim B
1
@ JimB: Eu pessoalmente o uso psftp.exe, o que me levou a escrevê-lo. Embora seja preferível fazer tudo de forma nativa no PowerShell, isso nem sempre é possível; invocar CLIs externas do PowerShell faz e continuará a acontecer; se você estiver lidando repetidamente com CLIs que exigem entrada de arquivo que (mais) podem ser facilmente construídas na memória / por outro comando, a função nesta resposta pode facilitar sua vida.
mklement
Você está brincando? nada disso é necessário. Ainda não encontrei um comando que aceite apenas arquivos com comandos para parâmetros. No que diz respeito ao SFTP, uma pesquisa simples me mostrou dois assemblies de complemento simples para executar nativamente o FTP no PowerShell.
Jim B
1
@ JimB: Se você quiser continuar essa conversa de maneira construtiva, mude seu tom.
mklement
2
O diff @JimB GNU Diffutils opera apenas em arquivos, caso você esteja interessado.
Pavel
2

Quando não está entre aspas duplas, $(...)retorna um Objeto do PowerShell (ou melhor, o que for retornado pelo código incluído), avaliando o código incluído primeiro. Isso deve ser adequado para seus propósitos ("algo [I] pode 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 apenas produzindo 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.

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 #>}
James Ruskin
fonte
Esta é uma resposta fantástica !!
GregL
O que você está descrevendo não é equivalente à substituição de processos do Bash. A substituição de processo foi projetada para uso com comandos que requerem operandos de nome de arquivo ; isto é, a saída de um comando incluído em uma substituição de processo é, vagamente, gravada em um arquivo temporário e o caminho desse arquivo é retornado; Além disso, a existência do arquivo tem como escopo o comando do qual a substituição do processo faz parte. Se o PowerShell tivesse esse recurso, você esperaria que algo como o seguinte funcionasse:Get-Content <(Get-ChildItem)
mklement
Corrija-me se eu estiver errado, e não é isso que você está procurando, mas não Get-ChildItem | Get-Contentfunciona perfeitamente? Ou você poderia tentar Get-Content (Get-ChildItem).FullNameo mesmo efeito? Você pode abordar isso de uma visão completamente influenciada por outra abordagem de script.
James Ruskin
1
Sim, no domínio do PowerShell não há necessidade desse recurso; é de interesse apenas para uso com CLIs externas que exigem entrada de arquivo e onde o conteúdo desses arquivos é facilmente construído com um comando (PowerShell). Veja meu comentário sobre a questão para um exemplo do mundo real. Talvez você nunca precise desse recurso, mas é interessante para as pessoas que frequentemente precisam chamar CLIs externas. Você deve pelo menos preceder sua resposta dizendo que está demonstrando a maneira do PowerShell de fazer as coisas - em oposição ao que o OP pediu especificamente - e por que você está fazendo isso.
precisa saber é