powershell 如何在ForEach对象并行中传递自定义函数

ndh0cuux  于 2022-11-10  发布在  Shell
关注(0)|答案(3)|浏览(153)

我找不到传递函数的方法。只有一些变量。
在不将函数放入ForEach循环的情况下,有什么想法吗?

function CustomFunction {
    Param (
        $A
    )
    Write-Host $A
}

$List = "Apple", "Banana", "Grape" 
$List | ForEach-Object -Parallel {
    Write-Host $using:CustomFunction $_
}

pgx2nnw8

pgx2nnw81#

解决方案并不像人们希望的那样简单:


# Sample custom function.

function Get-Custom {
  Param ($A)
  "[$A]"
}

# Get the function's definition *as a string*

$funcDef = ${function:Get-Custom}.ToString()

"Apple", "Banana", "Grape"  | ForEach-Object -Parallel {
  # Define the function inside this thread...
  ${function:Get-Custom} = $using:funcDef
  # ... and call it.
  Get-Custom $_
}

注意:This answer包含一个类似的解决方案,用于在ForEach-Object -Parallel脚本块中使用调用方作用域中的脚本块

  • 注意**:如果您的函数是在一个模块中定义的,该模块位于模块自动加载功能已知的位置之一,则您的函数调用将按原样与ForEach-Object -Parallel一起工作,无需额外的工作-但每个线程都会产生(隐式)导入模块的成本。
  • 上述方法是必要的,因为除了当前位置(工作目录)和环境变量(适用于进程范围)之外,ForEach-Object -Parallel创建的线程不会看到调用者的状态,尤其是在变量和函数方面(也不会看到自定义PS驱动器和导入的模块)。
  • 从PowerShell 7.2.x开始,GitHub issue #12240中正在讨论一项增强,以支持将调用者的状态按需复制到并行线程,这将使调用者的函数自动可用。

请注意,通过字符串重新定义每个线程中的函数是至关重要的,因为尝试在没有AUX的情况下凑合使用。$funcDef变量,并尝试使用${function:Get-Custom} = ${using:function:Get-Custom}重新定义函数失败,因为${function:Get-Custom}脚本块,并且明确禁止使用带有$using:作用域说明符的脚本块,以避免跨线程(跨运行空间)问题。

  • 但是,${function:Get-Custom} = ${using:function:Get-Custom}Start-Job一起使用;有关示例,请参阅this answer
  • 它不能Start-ThreadJob一起工作,Start-ThreadJob目前从语法上允许您执行& ${using:function:Get-Custom} $_,因为${using:function:Get-Custom}被保留为脚本块*(与Start-Job不同,在Start-Job中,它被反序列化为字符串,这本身就是令人惊讶的行为--参见GitHub issue #11698),尽管它不应该。也就是说,直接跨线程使用[scriptblock]示例会导致隐蔽的故障,这就是ForEach-Object -Parallel从一开始就阻止它的原因。
  • 即使使用ForEach-Object -Parallel也会导致跨线程问题的一个类似漏洞是通过$using:作用域在每个线程中使用在调用者作用域中获得的命令信息对象(Get-Command作为函数体):这也应该防止,但不是PowerShell 7.2.7-请参阅this post

${function:Get-Custom}namespace variable notation的一个示例,它允许您通过分配[scriptblock]或包含函数体的字符串来获取一个函数(其主体作为[scriptblock]示例)和设置(定义)它。

7lrncoxx

7lrncoxx2#

我刚刚想出了另一种使用Get-Command的方法,它与呼叫接线员一起工作。$a最终成为FunctionInfo对象。
编辑:我被告知这不是线程安全的,但我不明白为什么。

function hi { 'hi' }
$a = get-command hi
1..3 | foreach -parallel { & $using:a }

hi
hi
hi
dsekswqp

dsekswqp3#

所以我想出了另一个小技巧,对于试图动态添加函数的人来说可能很有用,特别是如果您可能事先不知道它的名称,例如当函数在数组中时。


# Store the current function list in a variable

$initialFunctions=Get-ChildItem Function:

# Source all .ps1 files in the current folder and all subfolders

Get-ChildItem . -Recurse | Where-Object { $_.Name -like '*.ps1' } |
     ForEach-Object { . "$($_.FullName)" }

# Get only the functions that were added above, and store them in an array

$functions = @()
Compare-Object $initialFunctions (Get-ChildItem Function:) -PassThru |
    ForEach-Object { $functions = @($functions) + @($_) }

1..3 | ForEach-Object -Parallel {
    # Pull the $functions array from the outer scope and set each function
    # to its definition
    $using:functions | ForEach-Object {
        Set-Content "Function:$($_.Name)" -Value $_.Definition
    }
    # Call one of the functions in the sourced .ps1 files by name
    SourcedFunction $_
}

其中的主要“诀窍”是将Set-ContentFunction:和函数名一起使用,因为PowerShell基本上将Function:的每个条目视为一个路径。
当您考虑Get-PSDrive的输出时,这是有意义的。因为这些条目中的每一个都可以以相同的方式(即,使用冒号)用作“Drive”。

相关问题