简体   繁体   English

PowerShell:修改数组元素

[英]PowerShell: modify elements of array

My cmdlet get-objects returns an array of MyObject with public properties: 我的cmdlet get-objects返回带有公共属性的MyObject数组:

public class MyObject{
    public string testString = "test";
}

I want users without programming skills to be able to modify public properties (like testString in this example) from all objects of the array. 我希望没有编程技能的用户能够从数组的所有对象修改公共属性(如本例中的testString )。 Then feed the modified array to my second cmdlet which saves the object to the database. 然后将修改后的数组提供给我的第二个cmdlet,该cmdlet将对象保存到数据库中。

That means the syntax of the "editing code" must be as simple as possible. 这意味着“编辑代码”的语法必须尽可能简单。

It should look somewhat like this: 它应该看起来像这样:

> get-objects | foreach{$_.testString = "newValue"} | set-objects

I know that this is not possible, because $_ just returns a copy of the element from the array. 我知道这是不可能的,因为$ _只返回数组中元素的副本。

So you'd need to acces the elements by index in a loop and then modify the property.This gets really quickly really complicated for people that are not familiar with programming. 因此,您需要在循环中通过索引访问元素,然后修改属性。对于不熟悉编程的人来说,这真的很快变得非常复杂。


Is there any "user-friendly" built-in way of doing this? 这样做有“用户友好”的内置方式吗? It shouldn't be more "complex" than a simple foreach {property = value} 它不应该比简单的foreach {property = value}更“复杂”

I know that this is not possible, because $_ just returns a copy of the element from the array ( https://social.technet.microsoft.com/forums/scriptcenter/en-US/a0a92149-d257-4751-8c2c-4c1622e78aa2/powershell-modifying-array-elements ) 我知道这是不可能的,因为$ _只返回数组中元素的副本( https://social.technet.microsoft.com/forums/scriptcenter/en-US/a0a92149-d257-4751-8c2c- 4c1622e78aa2 / powershell-modification-array-elements

I think you're mis-intepreting the answer in that thread. 我认为你错误地解释了那个话题中的答案。

$_ is indeed a local copy of the value returned by whatever enumerator you're currently iterating over - but you can still return your modified copy of that value (as pointed out in the comments ): $_确实是您当前正在迭代的任何枚举器返回的值的本地副本 - 但您仍然可以返回该值的修改副本(如注释中指出的 ):

Get-Objects | ForEach-Object {
    # modify the current item
    $_.propertyname = "value"
    # drop the modified object back into the pipeline
    $_
} | Set-Objects

In (allegedly impossible) situations where you need to modify a stored array of objects, you can use the same technique to overwrite the array with the new values: 在(据称不可能)需要修改存储的对象数组的情况下,您可以使用相同的技术用新值覆盖数组:

PS C:\> $myArray = 1,2,3,4,5
PS C:\> $myArray = $myArray |ForEach-Object {
>>>    $_ *= 10
>>>    $_
>>>}
>>>
PS C:\> $myArray
10
20
30
40
50

That means the syntax of the "editing code" must be as simple as possible. 这意味着“编辑代码”的语法必须尽可能简单。

Thankfully, PowerShell is very powerful in terms of introspection. 值得庆幸的是,PowerShell在内省方面非常强大。 You could implement a wrapper function that adds the $_; 你可以实现一个添加$_;的包装函数$_; statement to the end of the loop body, in case the user forgets: 语句到循环体的末尾,以防用户忘记:

function Add-PsItem 
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,ValueFromPipeline,ValueFromRemainingArguments)]
        [psobject[]]$InputObject,

        [Parameter(Mandatory)]
        [scriptblock]$Process
    )

    begin {

        $InputArray = @()

        # fetch the last statement in the scriptblock
        $EndBlock = $Process.Ast.EndBlock
        $LastStatement = $EndBlock.Statements[-1].Extent.Text.Trim()

        # check if the last statement is `$_`
        if($LastStatement -ne '$_'){
            # if not, add it
            $Process = [scriptblock]::Create('{0};$_' -f $Process.ToString())
        }
    }

    process {
        # collect all the input
        $InputArray += $InputObject
    }

    end {
        # pipe input to foreach-object with the new scriptblock
        $InputArray | ForEach-Object -Process $Process
    }
}

Now the users can do: 现在用户可以这样做:

Get-Objects | Add-PsItem {$_.testString = "newValue"} | Set-Objects

The ValueFromRemainingArguments attribute also lets users supply input as unbounded parameter values: ValueFromRemainingArguments属性还允许用户将输入作为无限参数值提供:

PS C:\> Add-PsItem { $_ *= 10 } 1 2 3
10
20
30

This might be helpful if the user is not used to working with the pipeline 如果用户不习惯使用管道,这可能会有所帮助

Here's a more general approach, arguably easier to understand, and less fragile: 这是一种更通用的方法,可以说更容易理解,也不那么脆弱:

#  $dataSource  would be get-object in the OP
#  $dataUpdater is the script the user supplies to modify properties
#  $dataSink    would be set-object in the OP
function Update-Data {
  param(
    [scriptblock]   $dataSource,
    [scriptblock]   $dataUpdater,
    [scriptblock]   $dataSink
  )

  & $dataSource |
  % { 
      $updaterOutput = & $dataUpdater
      # This "if" allows $dataUpdater to create an entirely new object, or
      # modify the properties of an existing object
      if ($updaterOutput -eq $null) {
        $_
      } else {
        $updaterOutput
      }
    } |
  % $dataSink
}

Here are a couple of examples of use. 以下是一些使用示例。 The first example isn't applicable to the OP, but it's being used to create a data set that is applicable (a set of objects with properties). 第一个示例不适用于OP,但它用于创建适用的数据集(一组具有属性的对象)。

#      Use updata-data to create a set of data with properties
#
$theDataSource = @() # will be filled in by first update-data
update-data {

    # data source
    0..4 
  } { 

    # data updater: creates a new object with properties
    New-Object psobject | 
    # add-member uses hash table created on the fly to add properties
    # to a psobject
    add-member -passthru -NotePropertyMembers @{
               room = @('living','dining','kitchen','bed')[$_];
               size = @(320,     200,     250,      424  )[$_]}
  } {

    # data sink
    $global:theDataSource += $_
  }

$theDataSource  | ft -AutoSize


#      Now use updata-data to modify properties in data set
#      this $dataUpdater updates the 'size' property 
#

$theDataSink = @()
update-data { $theDataSource } { $_.size *= 2} { $global:theDataSink += $_}
$theDataSink | ft -AutoSize

And then the output: 然后是输出:

room    size
----    ----
living   320
dining   200
kitchen  250
bed      424

room    size
----    ----
living   640
dining   400
kitchen  500
bed      848

As described above update-data relies on a "streaming" data source and sink. 如上所述,更新数据依赖于“流”数据源和接收器。 There is no notion of whether the first or fifteenth element is being modified. 没有关于第一个或第十五个元素是否被修改的概念。 Or if the data source uses a key (rather than an index) to access each element, the data sink wouldn't have access to the key. 或者,如果数据源使用密钥(而不是索引)来访问每个元素,则数据接收器将无法访问密钥。 To handle this case a "context" (for example an index or a key) could be passed through the pipeline along with the data item. 为了处理这种情况,可以将“上下文”(例如索引或键)与数据项一起传递通过管道。 The $dataUpdater wouldn't (necessarily) need to see the context. $ dataUpdater不一定需要查看上下文。 Here's a revised version with this concept added: 这是一个修订版本,增加了这个概念:

# $dataSource and $dataSink scripts need to be changed to output/input an
# object that contains both the object to modify, as well as the context.
# To keep it simple, $dataSource will output an array with two elements:
# the value and the context. And $dataSink will accept an array (via $_) 
# containing the value and the context.
function Update-Data {
  param(
    [scriptblock]   $dataSource,
    [scriptblock]   $dataUpdater,
    [scriptblock]   $dataSink
  )

  %  $dataSource |
  % { 
      $saved_ = $_
      # Set $_ to the data object
      $_ = $_[0]

      $updaterOutput = & $dataUpdater
      if ($updaterOutput -eq $null) { $updaterOutput = $_}

      $_ = $updaterOutput, $saved_[1]
    } |
  % $dataSink
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM