Powershell相当于Bash的流程替代


14

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

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

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


3
afaik没有这种东西,但是被证明是错误的会很有趣。
Zoredache 2015年

4
您能否给出一个预期如何使用的模拟示例?我想知道$(... | select -expandproperty objectyouwanttopass)是否适合单个替换情况。
安迪

2
在PowerShell $()是子表达式运算符中,可以这样使用它:Write-Output "The BITS service is $(Get-Service bits | select -ExpandProperty Stauts)"获取BITS服务的状态而无需先将其加载到变量中。从Process Substitution来看,这并不完全相同,但是它仍然可以解决您面临的问题
mortenya 2015年

@Andy:该功能将有助于需要文件名操作数的外部实用程序。一个关于SFTP传输的示例:它的选项要求您提供通过文件在服务器运行的命令,如果您只想运行,这是很不方便的。如果PowerShell具有进程替换功能,则可以执行。psftp.exe-bmget *psftp.exe -l user -p password somehost -b <( "mget *" )
mklement

Answers:


4

如果您满足以下条件,则 此答案不适合您
-很少(如果有的话)需要使用外部CLI(通常值得努力-PowerShell本机命令可以更好地协同工作,并且不需要这种功能)。
-对Bash的流程替换不熟悉。如果您是这样的话
这个答案就对您
有用:-经常使用外部CLI(无论是出于习惯还是由于缺少(好的)PowerShell本机替代品),尤其是在编写脚本时。
-习惯并欣赏Bash的流程替代可以做什么。
- 更新:现在Unix平台也支持PowerShell,此功能的重要性日益增加-请在GitHub上查看此功能请求,这表明PowerShell实施类似于进程替换的功能。

在Unix世界中,在Bash / Ksh / Zsh中,进程替换提供了将命令输出视为是一个临时文件,该文件将自己清除的功能。例如cat <(echo 'hello'),在哪里catecho命令的输出视为包含命令输出的临时文件路径

尽管PowerShell本机命令实际上并不需要这种功能,但是在处理外部CLI时它很方便。

在PowerShell中模拟该功能很麻烦,但是如果您发现自己经常需要该功能,则可能值得这样做。

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

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

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

为了尽可能广泛地与外部实用程序兼容,温度。默认情况下,文件应该使用不带BOM的UTF-8编码(字节顺序标记),尽管您可以根据需要使用来请求UTF-8的BOM 。-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
    }
  }
}

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


您可能需要执行此操作的想法充其量是可疑的,并且只是因为不想使用PowerShell而使事情变得更加困难。
Jim B

1
@JimB:我个人将它与一起使用psftp.exe,这促使我编写它。尽管最好在PowerShell中本机执行所有操作,但这并非总是可行的。从PowerShell调用外部CLI确实如此并且将继续发生;如果您发现自己反复处理需要文件输入的CLI,这些CLI可以(更多)轻松地在内存中/通过其他命令构造,则此答案中的功能可以使您的生活更轻松。
mklement

你在开玩笑?这些都不是必需的。我还没有找到仅接受带有命令参数文件的命令。就SFTP而言,一个简单的搜索向我展示了2个简单的插件程序集,它们可以在PowerShell中本地执行FTP。
Jim B

1
@JimB:如果您想以建设性的方式继续进行此对话,请更改您的语气。
mklement

2
@JimB GNU Diffutils diff仅对文件起作用,以防万一。
帕维尔

2

如果未用双引号引起来,则$(...)返回PowerShell对象(或更确切地说,所包含的代码返回的结果),首先评估包含的代码。假设命令行是PowerShell,这应该适合您的目的(“ [I]可能会粘在命令行中间”)。

您可以通过将各种版本传递到进行测试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 2015年

您所描述的与Bash的流程替代并不等同。进程替换设计用于需要文件名操作数的命令。也就是说,粗略地说,用进程替换括起来的命令的输出被写入一个临时文件,并返回该文件的路径;此外,文件的存在范围仅限于进程替换所包含的命令。如果PowerShell具有此功能,则您希望可以使用以下功能:Get-Content <(Get-ChildItem)
mklement

如果我错了,请指正我,这不是您想要的,但是效果不是Get-ChildItem | Get-Content很好吗?否则,您可以尝试Get-Content (Get-ChildItem).FullName达到相同的效果?您可能是从完全受另一种脚本方法影响的角度来解决这个问题的。
James Ruskin

1
是的,在PowerShell领域中,不需要此功能。它仅与需要文件输入的外部CLI一起使用时才有意义,并且可以使用(PowerShell)命令轻松构造此类文件的内容。请参阅我对该问题的评论,以了解实际示例。可能永远不需要这样的功能,但是对于经常需要调用外部CLI的人来说,这是很有趣的。您至少应该说出您正在演示PowerShell的工作方式(而不是OP明确要求的方式)以及为什么这样做,以此作为答案的开头
mklement
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.