我的cmdlet get-objects
返回一个包含公共属性的MyObject
数组:
public class MyObject{
public string testString = "test";
}
我希望没有编程技能的用户能够从数组的所有对象修改公共属性(例如本例中的testString
)。
然后将修改后的数组提供给我的第二个cmdlet,该cmdlet将对象保存到数据库中。
这意味着"编辑代码的语法"必须尽可能简单。
它应该看起来像这样:
> get-objects | foreach{$_.testString = "newValue"} | set-objects
我知道这是不可能的,因为$_ just returns a copy of the element from the array.
所以你需要在循环中通过索引访问元素然后修改属性。对于那些不熟悉编程的人来说,这真的很快变得非常复杂。
有没有"用户友好"内置的方式这样做?它不应该更复杂"而不是一个简单的foreach {property = value}
答案 0 :(得分:10)
我知道这是不可能的,因为$ _只返回数组中元素的副本(https://social.technet.microsoft.com/forums/scriptcenter/en-US/a0a92149-d257-4751-8c2c-4c1622e78aa2/powershell-modifying-array-elements)
我认为你错误地解释了那个帖子中的答案。
$_
确实是您当前正在迭代的任何枚举器返回的值的本地副本 - 但您仍然可以返回该值的已修改副本(如{ {3}}):
Get-Objects | ForEach-Object {
# modify the current item
$_.propertyname = "value"
# drop the modified object back into the pipeline
$_
} | Set-Objects
在(据称不可能)需要修改存储的对象数组的情况下,您可以使用相同的技术用新值覆盖数组:
PS C:\> $myArray = 1,2,3,4,5
PS C:\> $myArray = $myArray |ForEach-Object {
>>> $_ *= 10
>>> $_
>>>}
>>>
PS C:\> $myArray
10
20
30
40
50
这意味着"编辑代码的语法"必须尽可能简单。
值得庆幸的是,就内省而言,PowerShell非常非常强大。您可以实现一个包装函数,将$_;
语句添加到循环体的末尾,以防用户忘记:
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
}
}
现在用户可以这样做:
Get-Objects | Add-PsItem {$_.testString = "newValue"} | Set-Objects
ValueFromRemainingArguments
属性还允许用户将输入作为无界参数值提供:
PS C:\> Add-PsItem { $_ *= 10 } 1 2 3
10
20
30
如果用户不习惯使用管道
,这可能会有所帮助答案 1 :(得分:0)
这是一种更通用的方法,可以说更容易理解,也不那么脆弱:
# $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
}
以下是几个使用示例。第一个示例不适用于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
然后输出:
room size
---- ----
living 320
dining 200
kitchen 250
bed 424
room size
---- ----
living 640
dining 400
kitchen 500
bed 848
如上所述,update-data依赖于“流”数据源和接收器。没有关于第一个或第十五个元素是否被修改的概念。或者,如果数据源使用密钥(而不是索引)来访问每个元素,则数据接收器将无法访问密钥。为了处理这种情况,可以将“上下文”(例如索引或键)与数据项一起传递通过管道。 $ dataUpdater不一定需要查看上下文。以下是添加了此概念的修订版本:
# $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
}