2017-02-12 2 views
2
$var [email protected]( @{id="1"; name="abc"; age="1"; }, 
      @{id="2"; name="def"; age="2"; }); 
$properties = @("ID","Name","Age") ; 
$format = @(); 
foreach ($p in $properties) 
{ 
    $format += @{label=$p ; Expression = {$_.$p}} #$_.$p is not working! 
} 
$var |% { [PSCustomObject]$_ } | ft $format 

В приведенном выше примере я хочу получить доступ к каждому свойству объекта с помощью имени переменной. Но он не может работать так, как ожидалось. Так что в моем случае, как сделать

Expression = {$_.$p} 

рабочий?

+0

Плохо, предположим, что «свойства» содержат разные имена свойств. См. Отредактированные $ свойства. – derek

+1

Вы можете просто использовать 'Expression = $ p'. – PetSerAl

+3

Или вы можете использовать 'Expression = & {$ p = $ p; {$ _. $ p} .GetNewClosure()} ', если вам действительно нужен блок сценариев для' Expression'. – PetSerAl

ответ

4

Код OP и этот ответ используют PSv3 + синтаксис. Литье хэш-таблицы в [pscustomobject] не поддерживается в PSv2, но вы можете заменить [pscustomobject] $_ на New-Object PSCustomObject -Property $_.

Как и во многих случаях в прошлом, PetSerAl предоставил ответ в кратких (но очень полезных) комментариях по этому вопросу; позвольте мне уточнить:

Вашей проблемы является не, что вы используете переменные ($p), чтобы получить доступ к свойству по себе, который делает работы (например, $p = 'Year'; Get-Date | % { $_.$p }).

Вместо того, проблема состоит в том, что $p в блоке сценария { $_.$p } не вычисляется до позже, в контексте Format-Table вызова, что означает, что то же самое, фиксированное значение используется для всех входных объектов - а именно значение $pв этой точке (что является последним значением, которое было присвоено $p в цикле foreach).

Чистейший и наиболее общее решение заключается в вызове .GetNewClosure() на блоке сценария для привязки $p в блоке сценария к значениюзатем тока, петля-итерационного конкретным.

$format += @{ Label = $p; Expression = { $_.$p }.GetNewClosure() } 

Из docs (курсив):.

В этом случае новый блок сценария закрыт в течение местных переменных в объеме, что замыкание определяется в В другими словами, текущие значения локальных переменных захватываются и заключаются внутри блока сценария, связанного с модулем.

Обратите внимание, что автоматической переменной $_ не определен внутри foreach цикла (PowerShell определяет его только в определенных контекстах как входной объект под руку, например, в блоках сценариев, переданных командлеты в трубопроводе), так что остается несвязанный, по желанию.

Предостережение:

  • .GetNewClosure() Хотя, как используется выше удобно, он имеет неэффективность недостаток неизменно захват всех локальных переменных, а не только один (s) необходимо.

  • более эффективная альтернатива, что позволяет избежать этой проблемы - и в частности, также избегает ошибки (как в Windows PowerShell v5.1.14393.693 и PowerShell ядро ​​v6.0.0-alpha.15), в котором закрытие через локальные переменные могут разрыв, а именно, когда вшита скрипт/функция имеет параметр с проверки атрибутов таких как [ValidateNotNull()]и что параметр не связанный (значение не передается)[1] - это следующий, значительно более сложное выражение Совет шляпы снова PetSerAl, и ответ Burt_Harris в here :

    $format += @{ Label = $p; Expression = & { $p = $p; { $_.$p }.GetNewClosure() } } 
    
    • & { ... } создает дочерний объект с собственными локальными переменными.
    • $p = $p затем создает локальные переменную$p от его унаследованного значения.
      Чтобы обобщить этот подход, вы должны включить такой оператор для каждой переменной, указанной в блоке сценария.
    • { $_.$p }.GetNewClosure() затем выводит блок сценария, который закрывает локальные переменные дочерней области (только $p в этом случае).
    • Ошибка была указана как an issue in the PowerShell Core GitHub repository и с тех пор been fixed - мне непонятно, в каких версиях исправление будет отправлено.
  • Для простых случаев mjolinor's answer может сделать: это косвенно создает блок сценария с помощью расширенной строки, которая включает тогда текущую $p значения буквально, но учтите, что подход сложно обобщить, потому что просто строгая значение переменной обычно не гарантирует, что она работает как часть PowerShell Исходный код (который должна оценивать расширенная строка для преобразования в скрипт блок).

Для того, чтобы соединить все это вместе:

# Sample array of hashtables. 
# Each hashtable will be converted to a custom object so that it can 
# be used with Format-Table. 
$var = @( 
      @{id="1"; name="abc"; age="3" } 
      @{id="2"; name="def"; age="4" } 
     ) 

# The array of properties to output, which also serve as 
# the case-exact column headers. 
$properties = @("ID", "Name", "Age") 

# Construct the array of calculated properties to use with Format-Table: 
# an array of output-column-defining hashtables. 
$format = @() 
foreach ($p in $properties) 
{ 
    # IMPORTANT: Call .GetNewClosure() on the script block 
    #   to capture the current value of $p. 
    $format += @{ Label = $p; Expression = { $_.$p }.GetNewClosure() } 
    # OR: For efficiency and full robustness (see above): 
    # $format += @{ Label = $p; Expression = & { $p = $p; { $_.$p }.GetNewClosure() } } 
} 

$var | ForEach-Object { [pscustomobject] $_ } | Format-Table $format 

Это дает:

ID Name Age 
-- ---- --- 
1 abc 3 
2 def 4 

по желанию: выходные столбцы используют заголовки столбцов, указанные в $properties в то время как содержащий правильные значения.

Обратите внимание, как я удалил ненужные ; экземпляров и заменить встроенные псевдонимы % и ft с основными именами командлетов для ясности. Я также назначил различные значения age, чтобы лучше продемонстрировать, что результат правильный.


простое решение, в этом конкретных случае:

Для ссылки на значение свойства как есть, без преобразования, достаточно использовать имя свойства как Expression запись в вычисленном свойстве (хэш-таблица форматирования столбцов). Другими словами: вам не нужен экземпляр [scriptblock], содержащий выражение в этом случае ({ ... }), только [string] значение, содержащее имущество имя.

Поэтому следующий работал бы слишком:

# Use the property *name* as the 'Expression' entry's value. 
$format += @{ Label = $p; Expression = $p } 

Обратите внимание, что этот подход случается избежать исходной задачи, поскольку $p оценивается в момент назначения, поэтому цикл-итерация - фиксируются конкретные значения.


[1] Для воспроизведения: function foo { param([ValidateNotNull()] $bar) {}.GetNewClosure() }; foo терпит неудачу, когда .GetNewClosure() называется, с ошибкой Exception calling "GetNewClosure" with "0" argument(s): "The attribute cannot be added because variable bar with value would no longer be valid."
То есть, попытка включить несвязанного значение -bar параметр - переменная $bar - в замыкании , который, по-видимому, затем по умолчанию равен $null, что нарушает его атрибут проверки.
Передача действительного значения -bar заставляет проблему уйти; например, foo -bar ''.
Основания для рассмотрения это ошибка : Если сам лечит $bar при отсутствии значения параметра в -bar, как несуществующее, поэтому функции должна .GetNewClosure().

+0

@PetSerAl: В стороне: кажется, что атрибут является только проблемой в параметрах _, а не переменных; например, '[ValidateNotNull()] $ o = '''. Любая идея почему? – mklement0

+2

Переменные не могут нарушать атрибуты проверки. Такое нарушение было бы уловлено при присваивании '[ValidateNotNull()] $ o = $ null' или при попытке добавить атрибут' $ o = $ null; (gv o). Атрибуты.Добавить ([ValidateNotNull] :: новый()) '. Но неиспользуемые параметры имели бы значение по умолчанию, даже если оно нарушает атрибуты проверки. Когда 'GetNewClosure()' захватывает такой параметр, то он терпит неудачу. Это должно быть вопросом об этом в Stack Overflow уже. – PetSerAl

+1

P.S. Исходный код для захвата переменной находится в методе CaptureLocals() в https://github.com/PowerShell/PowerShell/blob/02b5f357a20e6dee9f8e60e3adb9025be3c94490/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs –

0

Доступ ничего внутри массив HashTables будет немного привередливы, но переменная расширения корректируется следующим образом:

$var [email protected]( @{id="1"; name="Sally"; age="11"; }, 
      @{id="2"; name="George"; age="12"; }); 
$properties = "ID","Name","Age" 
$format = @(); 

$Var | ForEach-Object{ 
    foreach ($p in $properties){ 
     $format += @{ 
      $p = $($_.($p)) 
     } 
    } 
} 

Вам нужен еще один цикл, чтобы иметь возможность привязать его к определенному элементу в ваш массив. Как я уже говорил, я думаю, что переход с массивом объектов будет гораздо более чистым, но я не знаю, с чем вы имеете дело.

+0

Мы (теперь, возможно, после того, как вы ответили) знаете, что OP хочет, чтобы '$ format' содержал _calculated properties_ для последующего использования с' Format-Table', поэтому это не сработает. – mklement0

1

Хотя весь подход кажется ошибочным для данного примера, так же, как упражнение при его работе, ключ будет управлять изменением переменной в нужное время. В вашем цикле foreach$_ имеет значение null ($_ действует только в конвейере). Вам нужно подождать, пока он не достигнет цикла Foreach-Object, чтобы попробовать его оценить.

Это похоже на работу с минимальным количеством рефакторинга:

$var [email protected]( @{id="1"; name="abc"; age="1"; }, 
     @{id="2"; name="def"; age="2"; }); 
$properties = @("ID","Name","Age") ; 
$format = @(); 
foreach ($p in $properties) 
{ 
    $format += @{label=$p ; Expression = [scriptblock]::create("`$`_.$p")} 
} 
$var | % { [PSCustomObject] $_ } | ft $format 

Создание ScriptBlock из расширяемой строки позволит $p расширить для каждого имени свойства. Escaping $_ сохранит его как литерал в строке, пока он не будет показан как скриптовый блок, а затем будет оценен в цикле ForEach-Object.

Смежные вопросы