Powershell相當於Bash的進程替代

Powershell相當於Bash的進程替代

Bash 有<(..)進程替換功能。 Powershell 的等效項是什麼?

我知道有$(...),但它返回一個字串,同時<(..)返回一個外部命令可以讀取的文件,這是它所期望的。

我也不是在尋找基於管道的解決方案,而是我可以將其放在命令列中間的解決方案。

答案1

這個答案是不適合你,如果您:
- 很少(如果有的話)需要使用外部 CLI(這通常是值得努力的 - PowerShell 原生命令一起發揮得更好,並且不需要這樣的功能)。
- 不熟悉 Bash 的進程替換。
這個答案是給你的,如果您:
- 經常使用外部 CLI(無論是出於習慣還是由於缺乏(良好的)PowerShell 原生替代方案),尤其是在編寫腳本時。
- 習慣並欣賞 Bash 的進程替換可以做什麼。
-更新:現在 Unix 平台也支援 PowerShell,此功能越來越受關注 - 請參閱GitHub 上的此功能請求,這表明 PowerShell 實作了類似於進程替換的功能。

在 Unix 世界中,在 Bash/Ksh/Zsh 中,流程替代is 提供將命令輸出視為暫時的文件會自行清理;例如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如果需要,您可以使用 請求 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+,透過 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
    }
  }
}

請注意可以選擇指定輸出[檔案]路徑和/或檔案副檔名。

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

相關內容