
Bash имеет <(..)
для подстановки процесса. Какой эквивалент у Powershell?
Я знаю, что есть $(...)
, но он возвращает строку, тогда как <(..)
возвращает файл, из которого внешняя команда может выполнить чтение, чего она и ожидает.
Мне также не нужно решение на основе конвейера, а что-то, что я могу вставить в середину командной строки.
решение1
Этот ответНе для тебя, если вы:
- редко, если вообще когда-либо, нуждаетесь в использовании внешних CLI (к чему, как правило, стоит стремиться - собственные команды PowerShell работают вместе гораздо лучше и не нуждаются в такой функции).
- не знакомы с подстановкой процессов в Bash.
Этот ответДля вас, если вы:
- часто используете внешние CLI (по привычке или из-за отсутствия (хороших) альтернатив PowerShell), особенно при написании скриптов.
- привыкли и цените возможности подстановки процессов в Bash.
-Обновлять: Теперь, когда PowerShell поддерживается и на платформах Unix, эта функция вызывает все больший интерес - см.этот запрос функции на GitHub, что предполагает, что PowerShell реализует функцию, похожую на подстановку процессов.
В мире Unix, в Bash/Ksh/Zsh,замена процессапредлагает обрабатывать вывод команды так, как если бы это был временныйфайлкоторый убирает за собой; например cat <(echo 'hello')
, где cat
видит вывод команды echo
какпуть временного файласодержащийвывод команды.
Хотя собственные команды PowerShell не нуждаются в такой функции, она может быть полезна при работе свнешние CLI.
Эмуляция этой функции в PowerShell обременительна, но может быть оно того стоит, если вам это часто нужно.
Представьте себе функцию с именем cf
, которая принимает блок скрипта, выполняет этот блок и записывает его вывод во временный файл, создаваемый по требованию, и возвращает путь к временному файлу.; например:
findstr.exe "Windows" (cf { Get-ChildItem c:\ }) # findstr sees the temp. file's path.
Это простой пример, который не иллюстрируетнуждатьсядля такой функции хорошо. Возможно, более убедительным сценарием является использованиеpsftp.exe
для SFTP-передач: его пакетное (автоматизированное) использование требует предоставления входных данныхфайлсодержащие нужные команды, тогда как такие команды можно легко создать в виде строки «на лету».
Чтобы обеспечить максимальную совместимость с внешними утилитами, временный файл должен использоватьUTF-8кодированиебез спецификации-BOM
(метка порядка байтов) по умолчанию, хотя при необходимости можно запросить BOM UTF-8 с помощью .
К сожалению,автоматическийочисткааспект замещения процесса не может бытьнапрямуюэмулируется, поэтому необходим явный вызов очистки; очистка выполняется вызовомcf
без аргументов:
Дляинтерактивныйиспользовать тебяможетавтоматизируйте очистку, добавив вызов очистки в вашу
prompt
функцию следующим образом (prompt
функция возвращает приглашениенить, но также может использоваться для выполнения скрытых команд каждый раз при отображении приглашения, аналогично$PROMPT_COMMAND
переменной Bash); для доступности в любом интерактивном сеансе добавьте следующее, а также определениеcf
ниже в свой профиль PowerShell:"function prompt { cf 4>`$null; $((get-item function:prompt).definition) }" | Invoke-Expression
Для использованияв сценариях, чтобы гарантировать выполнение очистки, блок, который использует
cf
(потенциально весь скрипт), необходимо обернуть в блокtry
/finally
, в которомcf
без аргументов вызывается очистка:
# 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.
}
Вотвыполнение: расширенная функция ConvertTo-TempFile
и ее краткое название, cf
:
Примечание: Использование New-Module
, которое требует PSv3+, для определения функции черездинамический модульгарантирует отсутствие конфликтов переменных между параметрами функции и переменными, на которые ссылаются внутри переданного блока скрипта.
$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
}
}
}
Обратите внимание на возможность указать путь к выходному [файлу] и/или расширение имени файла.
решение2
Если не заключено в двойные кавычки, $(...)
возвращает объект PowerShell (или, скорее, все, что возвращается заключенным кодом), сначала оценивая заключенный код. Это должно подойти для ваших целей («что-то [I] может застрять в середине командной строки»), предполагая, что command-line — это PowerShell.
Вы можете проверить это, передав различные версии в конвейер Get-Member
или даже просто выведя их напрямую.
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 #>
Как вы заметили, при заключении в двойные кавычки `"$(...)" просто вернет строку.
Таким образом, если вы хотите вставить, скажем, содержимое файла непосредственно в строку, вы можете использовать что-то вроде:
Invoke-Command -ComputerName (Get-Content C:\Temp\Files\new1.txt) -ScriptBlock {<# something #>}