Пауершелл еквівалент заміни процесу Баша


14

Bash має <(..)заміну процесу. Що таке еквівалент Повершелла?

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

Я також не шукаю рішення, що базується на трубі, але я можу приклеїтись посередині командного рядка.


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 *" ).
mklement

Відповіді:


4

Ця відповідь НЕ для вас , якщо ви:
- рідко, якщо взагалі колись, потрібно використовувати зовнішні CLI (до чого, як правило, варто прагнути - рідні команди PowerShell грають набагато краще разом і не потребують такої функції).
- не знайомі з заміною процесу Баша.
Ця відповідь є для вас , якщо ви:
- часто використовуйте зовнішні CLI (чи то за звичкою, чи через відсутність (хороших) альтернатив PowerShell), особливо під час написання сценаріїв.
- звикли і цінують те, що може замінити процес Баша.
- Оновлення : Тепер, коли 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.exeSFTP-передач: його пакетне (автоматизоване) використання вимагає надання вхідного файлу, що містить бажані команди, тоді як такі команди можуть бути легко створені у вигляді рядка на льоту.

Щоб максимально сумісна з зовнішніми утилітами, темп. Файл повинен використовувати 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-ModulePSv3 + для визначення функції через a динамічного модуля, гарантує, що між параметрами функції та змінними, на які посилається всередині блоку сценарію, не може бути змінних конфліктів.

$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
    }
  }
}

Зверніть увагу на можливість необов'язково вказати вихідний [файл] шлях та / або розширення імені файлу.


Ідея, що вам коли-небудь знадобиться це зробити, в кращому випадку сумнівна і просто ускладнювала б справи просто не бажаючи використовувати PowerShell.
Джим Б

1
@JimB: Я особисто використовую його psftp.exe, саме це і підштовхнуло мене до написання. Незважаючи на те, що в PowerShell бажано робити все вдома, це не завжди можливо; виклик зовнішніх CLI від PowerShell робить і продовжує відбуватися; якщо ви неодноразово стикаєтеся з CLI, які потребують введення файлів, які (більше) легко можна побудувати в пам'яті / за допомогою іншої команди, функція в цій відповіді може полегшити ваше життя.
mklement

Ти жартуєш? нічого з цього не потрібно. Мені ще потрібно знайти команду, яка приймає лише файли з командами для параметрів. Що стосується SFTP, то простий пошук показав мені 2 прості збірки доповнень для того, щоб вручну виконати FTP в PowerShell.
Джим Б

1
@JimB: Якщо ви хочете продовжити цю розмову в конструктивному порядку, змініть свій тон.
mklement

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 #>}

Це фантастична відповідь !!
GregL

Те, що ви описуєте, не є еквівалентом заміни процесу Баша. Заміна процесу призначена для використання з командами, які потребують операндів імен файлів ; тобто вихід з команди, укладеної в процес заміни, є, вільно кажучи, записаним у тимчасовий файл, і шлях до цього файлу повертається; крім того, існування файлу підпадає під команду, що є частиною заміни процесу. Якщо в PowerShell була така функція, ви очікуєте, що спрацює щось на зразок наступного:Get-Content <(Get-ChildItem)
mklement

Будь ласка, виправте мене, якщо я помиляюся, і це не те, що ви шукаєте, але це не Get-ChildItem | Get-Contentпрацює ідеально? Або ви могли б інакше спробувати Get-Content (Get-ChildItem).FullNameтой же ефект? Ви можете підходити до цього з точки зору, глибоко під впливом іншого сценарію підходу.
Джеймс Рускін

1
Так, у царині PowerShell немає необхідності в цій функції; цікавий лише для використання із зовнішніми CLI, які потребують введення файлів , і де вміст таких файлів легко будується за допомогою команди (PowerShell). Дивіться мій коментар до питання на прикладі реального світу. Можливо, вам ніколи не знадобиться така функція, але для людей, яким часто потрібно викликати зовнішні CLI, це цікаво. Потрібно, принаймні, передмовити свою відповідь, сказавши, що ви демонструєте PowerShell спосіб дій - на відміну від того, що спеціально просив ОП - і чому ви це робите.
mklement
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.