Эквивалент Powershell для процесса замены Bash

14

Bash имеет <(..)для процесса замены. Каков эквивалент Powershell?

Я знаю, что это так $(...), но он возвращает строку, в то время как <(..)возвращает файл, из которого внешняя команда может прочитать, чего он и ожидает.

Я также не ищу решение на основе конвейера, но что-то, что я могу вставить в середину командной строки.

IttayD
источник
3
afaik такого нет, но было бы интересно доказать, что это неправильно.
Зоредаче
4
Можете ли вы привести пример макета того, как вы ожидаете, что он будет использоваться? Мне интересно, может ли $ (... | select -expandproperty objectyouwanttopass) соответствовать одному случаю замены.
Энди
2
В PowerShell $ () является оператором подвыражения, вы можете использовать его следующим образом: Write-Output "The BITS service is $(Get-Service bits | select -ExpandProperty Stauts)"чтобы получить статус службы BITS без предварительной загрузки ее в переменную. Глядя на процесс подстановки, это не совсем то же самое, но оно может решить проблему, с которой вы столкнулись
Мортеня,
@Andy: функция поможет с внешними утилитами, которые требуют операнды имени файла . Примером может служить psftp.exeдля передачи SFTP: его -bвариант требует , чтобы обеспечить команды для запуска на сервере с помощью файла , что неудобно, если вы просто хотите запустить, скажем, mget *. Если в PowerShell есть подстановка процессов, вы сможете сделать что-то подобное psftp.exe -l user -p password somehost -b <( "mget *" ).
Мклемент

Ответы:

4

Этот ответ НЕ для вас , если вы:
- редко, если вообще когда-либо, нуждаетесь в использовании внешних интерфейсов командной строки (к которым, как правило, стоит стремиться - собственные команды PowerShell играют намного лучше вместе и не нуждаются в такой возможности).
- не знакомы с заменой процесса Баша.
Этот ответ для вас , если вы:
- часто используете внешние CLI (будь то по привычке или из-за отсутствия (хороших) альтернатив PowerShell-нативных), особенно при написании сценариев.
- привыкли и ценят то, что может сделать замена процесса в Bash.
- Обновление : теперь, когда PowerShell поддерживается и на платформах Unix, эта функция вызывает все больший интерес - см. Этот запрос на GitHub.Это означает, что PowerShell реализует функцию, аналогичную процессу замещения.

В мире Unix, в Bash / Ksh / Zsh, подстановка процесса предлагает обрабатывать вывод команды, как если бы это был временный файл, который очищается после себя; например cat <(echo 'hello'), где catвидит выходные данные echoкоманды как путь к временному файлу, содержащему выходные данные команды .

Хотя для собственных команд PowerShell нет реальной необходимости в такой функции, она может быть полезна при работе с внешними интерфейсами командной строки .

Эмуляция функции в PowerShell громоздка , но, возможно, она того стоит, если вам часто это нужно.

Представьте себе функцию с именем, cfкоторая принимает блок скрипта, выполняет блок и записывает свои выходные данные в temp. файл создается по требованию и возвращает темп. путь к файлу ; например:

 findstr.exe "Windows" (cf { Get-ChildItem c:\ }) # findstr sees the temp. file's path.

Это простой пример, который не иллюстрирует необходимость такой функции. Возможно, более убедительным сценарием является использование psftp.exeдля передач SFTP: его пакетное (автоматическое) использование требует предоставления входного файла, содержащего нужные команды, тогда как такие команды могут быть легко созданы в виде строки на лету.

Чтобы быть максимально совместимым с внешними утилитами, насколько это возможно, временная. Файл должен использовать UTF-8 кодировке без BOM (метка порядка байтов) по умолчанию, хотя вы можете запросить BOM UTF-8 с -BOM, если это необходимо.

К сожалению, аспект автоматической очистки замен процесса не может быть напрямую эмулирован, поэтому необходим явный вызов очистки ; уборка производится по телефонуcf без аргументов :

  • Для интерактивного использования вы можете автоматизировать очистку, добавив вызов очистки в вашу promptфункцию следующим образом ( promptфункция возвращает строку приглашения , но ее также можно использовать для выполнения закулисных команд при каждом отображении приглашения, аналогично Bash.$PROMPT_COMMAND переменная); для доступности в любом интерактивном сеансе добавьте следующее, а также приведенное 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
    }
  }
}

Обратите внимание на возможность при желании указать выходной путь [файл] и / или расширение имени файла.

mklement
источник
Идея, что вам когда-либо понадобится это сделать, в лучшем случае сомнительна и просто усложнит ситуацию, просто не желая использовать PowerShell.
Джим Б
1
@JimB: я лично использую это с psftp.exe, что побудило меня написать это. Несмотря на то, что в PowerShell желательно все делать изначально, это не всегда возможно; вызов внешних CLI из PowerShell делает и будет происходить; если вы постоянно сталкиваетесь с CLI, которые требуют ввода файла, который (более) легко может быть создан в памяти / другой командой, функция в этом ответе может облегчить вашу жизнь.
Мклемент
Вы шутите? ничего из этого не требуется. Мне еще предстоит найти команду, которая принимает только файлы с командами для параметров. Что касается SFTP, простой поиск показал мне две простые сборки надстроек, позволяющие выполнять FTP в PowerShell.
Джим Б
1
@JimB: Если вы хотите продолжить этот разговор конструктивно, измените свой тон.
Мклемент
2
@JimB GNU Diffutils diff работает только с файлами, если вы заинтересованы.
Павел
2

Когда не заключены в двойные кавычки, $(...) возвращает объект PowerShell (или, скорее, все, что возвращается в прилагаемом коде), сначала оценивая вложенный код. Это должно подходить для ваших целей («что-то [I] может застрять в середине командной строки»), предполагая, что командной строкой является 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 #>}
Джеймс Раскин
источник
Это фантастический ответ !!
Грегл
То, что вы описываете, не является эквивалентом процесса замещения Bash. Подстановка процессов предназначена для использования с командами, которые требуют операндов имени файла ; то есть выходные данные команды, включенной в подстановку процесса, свободно говоря, записываются во временный файл, и путь к этому файлу возвращается; Кроме того, существование файла ограничивается командой, частью которой является подстановка процесса. Если бы PowerShell имел такую ​​функцию, можно ожидать, что сработает что-то вроде следующего:Get-Content <(Get-ChildItem)
mklement
Пожалуйста, поправьте меня, если я ошибаюсь, и это не то, что вы ищете, но не очень Get-ChildItem | Get-Contentхорошо работает? Или вы могли бы попытаться Get-Content (Get-ChildItem).FullNameна тот же эффект? Возможно, вы подходите к этому с точки зрения, находящегося под сильным влиянием другого сценариев.
Джеймс Раскин
1
Да, в сфере PowerShell эта функция не нужна; он представляет интерес только для использования с внешними интерфейсами командной строки, которые требуют ввода файлов , и где содержимое таких файлов легко создается с помощью команды (PowerShell). Смотрите мой комментарий по вопросу для реального примера. Возможно, вам никогда не понадобится такая функция, но для людей, которым часто нужно вызывать внешние CLI, она представляет интерес. Вы должны по крайней мере предвосхитить свой ответ, сказав, что вы демонстрируете способ работы PowerShell - в отличие от того, что конкретно запрашивал OP, - и почему вы это делаете.
Мклемент