Powershell 相当于 Bash 的进程替换

Itt*_*ayD 16 powershell

Bash<(..)用于进程替换。Powershell 的等价物是什么?

我知道有$(...),但它返回一个字符串,同时<(..)返回一个外部命令可以读取的文件,这是它所期望的。

我也不是在寻找基于管道的解决方案,而是我可以在命令行中间粘贴的东西。

mkl*_*ent 5

如果您满足以下条件,则此答案不适合您
- 很少(如果有的话)需要使用外部 CLI(这通常值得努力 - PowerShell 原生命令一起发挥得更好,并且不需要这样的功能)。
- 不熟悉 Bash 的进程替换。
如果您满足以下条件,那么这个答案适合您
- 经常使用外部 CLI(无论是出于习惯还是由于缺乏(良好的)PowerShell 原生替代方案),尤其是在编写脚本时。
- 习惯并欣赏 Bash 的进程替换可以做什么。
-更新:现在 Unix 平台也支持 PowerShell,此功能越来越受关注 - 请参阅GitHub 上的此功能请求,这表明 PowerShell 实现了类似于进程替换的功能。

在 Unix 世界中,在 Bash/Ksh/Zsh 中,进程替换提供了将命令输出视为一个可以自行清理的临时文件的功能;例如cat <(echo 'hello'),where将命令cat的输出视为包含命令输出的临时文件的路径echo

虽然 PowerShell 本机命令并不真正需要此类功能,但在处理外部 CLI时它会很方便。

在 PowerShell 中模拟该功能很麻烦,但如果您发现自己经常需要它,那么可能是值得的。

想象一个名为的函数cf,它接受脚本块,执行该块并将其输出写入临时文件。根据需要创建文件,并返回临时值。文件的路径;例如:

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

这是一个简单的示例,并不能很好地说明对此类功能的需求。也许更令人信服的场景是用于psftp.exeSFTP 传输:其批量(自动)使用需要提供包含所需命令的输入文件,而此类命令可以轻松地动态创建为字符串。

为了尽可能广泛地与外部实用程序兼容,温度。默认情况下,文件应使用不带 BOM(字节顺序标记)的UTF-8编码,但如果需要,您可以请求带有 的 UTF-8 BOM。-BOM

不幸的是,进程替换的自动清理方面无法直接模拟,因此需要显式清理调用;清理是通过cf 不带参数调用来执行的:

  • 对于交互式使用,您可以通过向函数添加 cleanup 调用来自动化清理prompt,如下所示(该prompt函数返回提示字符串,但也可用于在每次显示提示时执行幕后命令,类似于 Bash 的$PROMPT_COMMAND多变的); 为了在任何交互式会话中可用,请将以下内容以及cf以下定义添加到您的 PowerShell 配置文件中:

    "function prompt { cf 4>`$null; $((get-item function:prompt).definition) }" |
      Invoke-Expression
    
    Run Code Online (Sandbox Code Playgroud)
  • 为了在 script 中使用,为了确保执行清理,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.
}
Run Code Online (Sandbox Code Playgroud)

这是实现:高级函数ConvertTo-TempFile及其简洁的别名cf

注意:使用需要 PSv3+,通过动态模块New-Module定义函数可确保函数参数和传递的脚本块内引用的变量之间不会出现变量冲突。

$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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

请注意可以选择指定输出[文件]路径和/或文件扩展名。

  • @JimB GNU Diffutils diff 仅对文件进行操作,以防您感兴趣。 (3认同)

Jam*_*kin 3

当未用双引号括起来时,$(...)返回一个 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 #>
Run Code Online (Sandbox Code Playgroud)

正如您所注意到的,当用双引号引起来时,“$(...)”将仅返回一个字符串。

通过这种方式,如果您想直接在一行上插入文件的内容,您可以使用如下所示的内容:

Invoke-Command -ComputerName (Get-Content C:\Temp\Files\new1.txt) -ScriptBlock {<# something #>}
Run Code Online (Sandbox Code Playgroud)

  • 是的,_在 PowerShell 领域_不需要此功能;它仅适用于需要文件输入的外部 CLI,并且可以使用 (PowerShell) 命令轻松构建此类文件的内容。请参阅我对这个问题的评论,了解一个现实世界的例子。_您_可能永远不需要这样的功能,但对于经常需要调用外部 CLI 的人来说,它很有趣。您至少应该在回答前说您正在演示 _PowerShell_ 做事的方式 - 而不是 OP 特别要求的 - 以及您为什么这样做。 (3认同)
  • 您所描述的并不等同于 Bash 的进程替换。进程替换设计用于需要 _filename_ 操作数的命令;也就是说,粗略地说,进程替换中包含的命令的输出被写入临时文件,并返回该文件的 _path_ ;此外,该文件的存在范围仅限于进程替换所属的命令。如果 PowerShell 有这样的功能,您会期望类似以下内容的工作:`Get-Content &lt;(Get-ChildItem)` (2认同)