Powershell 相当于 Bash 的进程替换

Powershell 相当于 Bash 的进程替换

Bash 具有<(..)进程替换功能。Powershell 的等效功能是什么?

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

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

答案1

这个答案是不适合你,如果你:
- 很少,如果有的话,需要使用外部 CLI(这通常是值得努力的 - PowerShell 原生命令一起玩效果更好,不需要这样的功能)。
- 不熟悉 Bash 的进程替换。
这个答案是给你的,如果您:
- 经常使用外部 CLI(无论是出于习惯还是由于缺乏(好的)PowerShell 原生替代品),尤其是在编写脚本时。
- 习惯并欣赏 Bash 的进程替换可以做什么。
-更新:现在 Unix 平台也支持 PowerShell,因此该功能越来越受关注 - 请参阅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 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
    }
  }
}

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

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

相关内容