Я пытаюсь переименовать файлы, поставив префикс на основе увеличивающегося счетчика в таких файлах, как:
$directory = 'C:\Temp'
[int] $count=71;
gci $directory | sort -Property LastWriteTime | `
rename-item -newname {"{0}_{1}" -f $count++, $_.Name} -whatif
Тем не менее, все обработанные файлы 71_
и $count
в $count++
никогда не увеличиваются, а имена файлов имеют одинаковый префикс? Почему?
Причина, по которой вы не можете просто использовать $count++
в своем блоке скрипта для прямого увеличения порядкового номера, заключается в следующем:
Блоки скрипта с отложенной привязкой — например, тот, который вы передали Rename-Item -NewName
— и блоки скрипта в рассчитанные свойства запускаются в Область ребенок.
Where-Object
и ForEach-Object
, которые выполняются непосредственно в области действия вызывающей стороны.
Это неясно, является ли эта разница в поведении преднамеренной.Следовательно, попытка изменить переменные вызывающей стороны вместо этого создает локальную переменную блокировать, которая выходит за пределы области видимости на каждой итерации., чтобы следующая итерация снова увидела исходное значение из области действия вызывающей стороны.
прагматичный, но потенциально ограничивающий обходной путь должен использовать спецификатор области действия $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
Это будет работать:
в интерактивном сеансе (в командной строке, в глобальной области).
в скрипте, если переменная $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
и восстановить его впоследствии, что делает этот подход непрактичным.
Ответ @ mklement0 правильный, но я думаю, что это гораздо проще понять, чем иметь дело со ссылками:
Get-ChildItem $directory |
Sort-Object -Property LastWriteTime |
ForEach-Object {
$NewName = "{0}_{1}" -f $count++, $_.Name
Rename-Item $_ -NewName $NewName -WhatIf
}
Да, решение [ref]
неясно, поэтому я решил изменить структуру своего ответа, чтобы сначала предложить - прагматичное, но ограниченное - решение $script:count++
. Для небольшого количества операций переименования это может не иметь значения, но тот факт, что ваше решение вызывает Rename-Item
для каждого входного файла, может стать проблемой для производительности.
@mklement0 mklement0 Если бы производительность была проблемой, я бы использовал System.IO.File.Move() напрямую или переписал скрипт с помощью Python.
Необходимость прибегать к «неродным» средствам всегда неудобна и для многих является препятствием. При достаточно большом наборе входных данных использование блоков сценария с отложенной привязкой обеспечивает значительное преимущество в производительности по сравнению с вызовом ForEach-Object
с повторными вызовами, и на это (а) стоит обратить внимание и (б) может быть достаточно хорошо в данной ситуации. Итак, как это часто бывает, все зависит от степени производительности PowerShell и сводится к выбору правильных конструкций. В противном случае, да, вы можете обратиться к прямому использованию платформы .NET или внешних исполняемых файлов.
@ mklement0 «Прибегать к «неродным» средствам всегда неудобно и для многих является препятствием». Говорит человек, пытающийся объяснить задержку и ссылки? Ну давай же.
Блоки скриптов с отложенной привязкой — это выразительная, лаконичная и — относительно — производительная стандартная функция, которая заслуживает гораздо более широкого использования; до недавнего времени оно чахло из-за отсутствия документации; Надеюсь, мои ответы помогут немного его популяризировать. К сожалению, его реализация имеет недостатки в отношении области видимости - если вы согласны, сделайте так, чтобы ваш голос был услышан здесь. Мой ответ теперь содержит - разумный - $script:count++
обходной путь в первую очередь; те, кто ищет более надежное решение, могут использовать — несомненно, неясное — решение [ref]
.
Вау, в последнее время это часто всплывает. Вот моя любимая альтернатива foreach с несколькими блоками сценариев. gci с подстановочным знаком дает полный путь к $_ позже. Вам не нужен символ продолжения обратной галочки после вертикальной черты или оператора.
$directory = 'c:\temp'
gci $directory\* | sort LastWriteTime |
foreach { $count = 71 } { rename-item $_ -newname ("{0}_{1}" -f
$count++, $_.Name) -whatif } { 'done' }
Блок сценария -Begin
для инициализации переменной $count
концептуально привлекателен, но ничем не отличается от инициализации $count
до вызова foreach
(ForEach-Object
), потому что блоки сценария ForEach-Object
выполняются непосредственно в области действия вызывающей стороны. Другими словами: переменная $count
остается в области действия вызывающего объекта в любом случае. Это может не всегда иметь значение, но вызов Rename-Item
для каждого входного объекта неэффективен по сравнению с одним вызовом с блоком скрипта с отложенной привязкой. Более простой способ избежать проблемы полного пути $_
(больше не проблема в PS Core) — использовать $_.FullName
@mklement0 mklement0 На самом деле это 3 блока скрипта, переданные -process, но они выполняют одну и ту же функцию.
Вы правы — технически все они связаны с -Process
, но фактически обрабатываются так, как если бы они были переданы -Begin
, -Process
и -End
соответственно.
Почти уверен, что вы сталкиваетесь с проблемами области видимости. Попробуйте изменить все ссылки с
$count
на$script:count
.