
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 cat
vê a saída do echo
comando 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 cf
que 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.exe
para 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
prompt
função da seguinte maneira (aprompt
função retorna o promptcorda, mas também pode ser usado para executar comandos nos bastidores sempre que o prompt for exibido, semelhante à$PROMPT_COMMAND
variável do Bash); para disponibilidade em qualquer sessão interativa, adicione o seguinte, bem como a definiçãocf
abaixo, 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 blocotry
/finally
, no qualcf
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.
}
Aqui está oimplementação: função avançada ConvertTo-TempFile
e 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 #>}