
Bash tiene <(..)
para la sustitución de procesos. ¿Cuál es el equivalente de Powershell?
Sé que lo hay $(...)
, pero devuelve una cadena, mientras que <(..)
devuelve un archivo que el comando externo puede leer, que es lo que espera.
Tampoco estoy buscando una solución basada en tuberías, sino algo que pueda colocar en el medio de la línea de comando.
Respuesta1
Esta respuesta esNo para ti, si usted:
- rara vez, o nunca, necesita usar CLI externas (por lo que generalmente vale la pena esforzarse; los comandos nativos de PowerShell funcionan mucho mejor juntos y no necesitan dicha característica).
- No estoy familiarizado con el proceso de sustitución de Bash.
esta respuestaEs para ti, si:
- utiliza con frecuencia CLI externas (ya sea por costumbre o por falta de (buenas) alternativas nativas de PowerShell), especialmente al escribir scripts.
- Están acostumbrados y aprecian lo que puede hacer la sustitución de procesos de Bash.
-Actualizar: Ahora que PowerShell también es compatible con plataformas Unix, esta característica es cada vez más interesante; consulteesta solicitud de función en GitHub, lo que sugiere que PowerShell implemente una característica similar a la sustitución de procesos.
En el mundo Unix, en Bash/Ksh/Zsh, unsustitución de procesosEsto ofrece tratar la salida del comando como si fuera temporal.archivoque limpia lo que ensucia; por ejemplo cat <(echo 'hello')
, donde cat
ve la salida del echo
comando como elruta de un archivo temporalque contiene elsalida del comando.
Si bien los comandos nativos de PowerShell no necesitan realmente esta característica, puede resultar útil cuando se trata deCLI externas.
Emular la función en PowerShell es engorroso, pero puede valer la pena si lo necesita con frecuencia.
Imagine una función llamada cf
que acepta un bloque de script, ejecuta el bloque y escribe su salida en un archivo temporal. archivo creado a pedido y devuelve el valor temporal. ruta del archivo; p.ej:
findstr.exe "Windows" (cf { Get-ChildItem c:\ }) # findstr sees the temp. file's path.
Este es un ejemplo simple que no ilustra lanecesidadpara tal característica. Quizás un escenario más convincente sea el uso depsftp.exe
para transferencias SFTP: su uso por lotes (automatizado) requiere proporcionar una entradaarchivoque contiene los comandos deseados, mientras que dichos comandos se pueden crear fácilmente como una cadena sobre la marcha.
Para que sea lo más compatible posible con utilidades externas, el temp. el archivo debe usarUTF-8codificaciónsin lista de materiales(marca de orden de bytes) de forma predeterminada, aunque puede solicitar una lista de materiales UTF-8 con -BOM
, si es necesario.
Desafortunadamente,el automaticolimpiarEl aspecto de las sustituciones de procesos no puede serdirectamenteemulado, por lo que se necesita una llamada de limpieza explícita; la limpieza se realiza llamandocf
sin argumentos:
Parainteractivouso, tupoderautomatice la limpieza agregando la llamada de limpieza a su
prompt
función de la siguiente manera (laprompt
función devuelve el mensajecadena, pero también se puede utilizar para ejecutar comandos detrás de escena cada vez que se muestra el mensaje, similar a la$PROMPT_COMMAND
variable de Bash); Para conocer la disponibilidad en cualquier sesión interactiva, agregue lo siguiente, así como la definicióncf
siguiente, a su perfil de PowerShell:"function prompt { cf 4>`$null; $((get-item function:prompt).definition) }" | Invoke-Expression
Para usaren guiones, para garantizar que se realice la limpieza, el bloque que utiliza
cf
(potencialmente todo el script) debe incluirse en un bloquetry
/finally
, en el quecf
se solicita la limpieza sin argumentos:
# 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.
}
Aquí esta laimplementación: función avanzada ConvertTo-TempFile
y su alias sucinto, cf
:
Nota: El uso de New-Module
, que requiere PSv3+, para definir la función a través de unmódulo dinámicogarantiza que no pueda haber conflictos de variables entre los parámetros de la función y las variables a las que se hace referencia dentro del bloque de script pasado.
$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
}
}
}
Tenga en cuenta la capacidad de especificar opcionalmente una ruta de salida [archivo] y/o una extensión de nombre de archivo.
Respuesta2
Cuando no está entre comillas dobles, $(...)
devuelve un objeto PowerShell (o más bien, lo que devuelve el código adjunto), evaluando primero el código adjunto. Esto debería ser adecuado para sus propósitos ("algo [que] puedo colocar en el medio de la línea de comando"), suponiendo que la línea de comando sea PowerShell.
Puede probar esto canalizando varias versiones a Get-Member
, o incluso simplemente enviándolo directamente.
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 #>
Cuando está entre comillas dobles, como habrás notado, `"$(...)" simplemente devolverá una cadena.
De esta manera, si quisieras insertar, digamos, el contenido de un archivo directamente en una línea, podrías usar algo como:
Invoke-Command -ComputerName (Get-Content C:\Temp\Files\new1.txt) -ScriptBlock {<# something #>}