++ Оператор над переменной не меняется, как ожидается в ScriptBlock - PullRequest
3 голосов
/ 02 июля 2019

Я пытаюсь переименовать файлы, добавив в файлы префикс, основанный на инкрементном счетчике, например:

$directory = 'C:\Temp'
[int] $count=71; 

gci $directory | sort -Property LastWriteTime | `
rename-item -newname {"{0}_{1}" -f $count++, $_.Name} -whatif

Тем не менее, все обрабатываемые файлы 71_ и $count в $count++ никогда не увеличиваются, а имена файлов имеют префикс одинаковый? Почему?


enter image description here

Ответы [ 3 ]

5 голосов
/ 02 июля 2019

Причина, по которой вы не можете просто использовать $count++ в своем блоке сценария для непосредственного увеличения порядкового номера:

  • Блоки сценариев с задержкой привязки - например, тот, который вы передали Rename-Item -NewName - и блоки скриптов в вычисленные свойства , выполняемые в дочернем области .

    • Сравните это с блоками сценариев, переданными в Where-Object и ForEach-Object, которые выполняются непосредственно в области действия вызывающего.преднамеренное .
  • Следовательно, , пытаясь изменить переменные вызывающего, вместо этого создает block -локальную переменную, которая выходит из области видимости вкаждая итерация , поэтому следующая итерация снова увидит исходное значение из области видимости вызывающего.

    • Чтобы узнать больше о областях и неявном создании локальной переменной, см. этот ответ .

Обходные пути

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

$directory = 'C:\Temp'
[int] $count=71

gci $directory | sort -Property LastWriteTime |
  rename-item -newname { '{0}_{1}' -f $script:count++, $_.Name } -whatif

Thisбудет работать:

  • в интерактивном сеансе (в командной строке, в глобальной области действия).

  • в сценарии, покапеременная $count была инициализирована в области верхнего уровня сценария .

    • То есть, если вы переместили свой код в функцию с помощью переменная локальная $count, больше не будет работать.

Гибкое решение требует надежного относительного ссылка на родительский охват :

Существует два варианта:

  • концептуально ясный, но многословный и сравнительно медленный , из-за необходимости вызова командлета: (Get-Variable -Scope 1 count).Value++
gci $directory | sort -Property LastWriteTime |
  rename-item -newname { '{0}_{1}' -f (Get-Variable -Scope 1 count).Value++, $_.Name } -whatif
  • несколько неясно, но быстрее и более кратко : ([ref] $count).Value++
gci $directory | sort -Property LastWriteTime |
  rename-item -newname { '{0}_{1}' -f ([ref] $count).Value++, $_.Name } -whatif

[ref] $count фактически совпадает с Get-Variable -Scope 1 count (при условии, что в родительской области была установлена ​​переменная $count)


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

1 голос
/ 02 июля 2019

Ух ты, в последнее время это очень популярно. Вот моя любимая альтернатива foreach multi scriptblock. gci с подстановочным знаком дает полный путь к $ _ позже. Вам не нужен символ продолжения обратной черты после канала или оператора.

$directory = 'c:\temp'

gci $directory\* | sort LastWriteTime |
foreach { $count = 71 } { rename-item $_ -newname ("{0}_{1}" -f
$count++, $_.Name) -whatif } { 'done' }
1 голос
/ 02 июля 2019

@ mklement0 ответ правильный, но я думаю, что это гораздо проще понять, чем иметь дело со ссылками:

Get-ChildItem $directory | 
    Sort-Object -Property LastWriteTime |
    ForEach-Object {
        $NewName = "{0}_{1}" -f $count++, $_.Name
        Rename-Item $_ -NewName $NewName -WhatIf
    }
...