Автоматическое пакетное переименование файлов в соответствии с формулой, включающей имя родительской папки, с помощью Powershell
Как я могу пакетно переименовать файлы в powershell, используя следующий код:
$nr=1;Get-ChildItem -Filter *.jpg |
Rename-Item -Newname {"PPPPPPP_{0:d3}.jpg" -f $global:nr++}
где PPPPPPP - это имя родительской папки, содержащей эти файлы.
Ожидаемый результат:
PPPPPPP_001.jpg
PPPPPPP_002.jpg
PPPPPPP_003.jpg
Файлы находятся в папке C:\USER\MAIN\BLABLABLA\PPPPPPP.
2 ответа
Получить имя родительского каталога через
$_.Directory.Name
внутри блока скрипта.Брось
$nr
переменная к[ref]
так что вы можете изменить его значение непосредственно в области вызова (через.Value
), что предпочтительнее использования модификатора области видимости$global:
- увидеть ниже.
$nr = 1
Get-ChildItem -Filter *.jpg | Rename-Item -Newname {
'{0}_{1:d3}.jpg' -f $_.Directory.Name, ([ref] $nr).Value++
} -WhatIf
-WhatIf
предварительный просмотр операции переименования; удалите его, как только вы будете уверены, что команда будет работать так, как задумано.
Необязательное чтение: изменение переменных вызывающей стороны в блоке сценария с задержкой привязки или вычисляемом свойстве:
Причина, по которой вы не могли просто использовать $nr++
в вашем блоке сценария для того, чтобы увеличить порядковый номер непосредственно:
Блоки сценариев с задержкой привязки (например, переданные в
Rename-Item -NewName
) и блоки скриптов в вычисляемых свойствах выполняются в дочерней области.- Сравните это с блоками скрипта, переданными
Where-Object
а такжеForEach-Object
, которые запускаются непосредственно в области действия вызывающего.
Неясно, является ли эта разница в поведении преднамеренной.
- Сравните это с блоками скрипта, переданными
Следовательно, попытка изменить переменные вызывающей стороны вместо этого создает блочную переменную-local, которая выходит из области действия на каждой итерации, так что следующая итерация снова видит исходное значение:
Используя вычисляемое свойство в качестве примера:
PS> $nr = 1; 1..2 | Select-Object { '#' + $nr++ }
'#' + $nr++
-------------
#1
#1 # !! the *caller's* $nr was NOT incremented
Хотя вы можете использовать модификатор области видимости, такой как $global:
или же $script:
чтобы явно ссылаться на переменную в родительской области видимости, это абсолютные области видимости, которые могут работать не так, как предполагалось: например: если вы переместите свой код в сценарий, $global:nr
больше не относится к переменной, созданной с $nr = 1
,
Кратко: создания глобальных переменных, как правило, следует избегать, учитывая, что они остаются в текущем сеансе даже после выхода из скрипта.
Надежный подход заключается в использовании Get-Variable -Scope 1
вызов надежно ссылается на непосредственную родительскую область:
PS> $nr = 1; 1..2 | Select-Object { '#' + (Get-Variable -Scope 1 nr).Value++ }
'#' + (Get-Variable -Scope 1 nr).Value++
------------------------------------------
#1
#2 # OK - $nr in the caller's scope was incremented
Хотя этот метод является надежным, он также является громоздким и многословным.
Но вы можете улучшить эффективность следующим образом:
$nr = 1; $nrVar = Get-Variable nr; 1..2 | Select-Object { '#' + $nrVar.Value++ }
С использованием [ref]
type предлагает более лаконичную альтернативу, хотя решение немного неясно:
PS> $nr = 1; 1..2 | Select-Object { '#' + ([ref] $nr).Value++ }
'#' + ([ref] $nr).Value++
---------------------------
#1
#2 # OK - $nr in the caller's scope was incremented
Приведение переменной к [ref]
возвращает объект, чей .Value
свойство может получить доступ - и изменить - значение этой переменной. Обратите внимание, что с $nr
не назначается в этот момент, это действительно вызывающий $nr
переменная, на которую ссылаются.
РЕДАКТИРОВАТЬ: изменение из-за подсказки mklement0 s.
Чтобы получить имя родителя, используйте .Directory.Name
в качестве другого параметра для оператора формата
$nr=1
Get-ChildItem *.jpg -file|
Rename-Item -Newname {"{0}_{1:d3}.jpg" -f $_.Directory.Name,([ref]$nr).Value++} -whatIf
Если вывод выглядит нормально, удалите -WhatIf
Это будет работать только тогда, когда нет пересекающихся диапазонов, выполняющих переименование, в этом случае вам, вероятно, следует использовать временное расширение.
Пример вывода на мой немецкий язык Windows
WhatIf: Ausführen des Vorgangs "Datei umbenennen" für das Ziel "Element: A:\test\150.jpg Ziel: A:\test\test_007.jpg".
WhatIf: Ausführen des Vorgangs "Datei umbenennen" für das Ziel "Element: A:\test\151.jpg Ziel: A:\test\test_008.jpg".
WhatIf: Ausführen des Vorgangs "Datei umbenennen" für das Ziel "Element: A:\test\152.jpg Ziel: A:\test\test_009.jpg".
WhatIf: Ausführen des Vorgangs "Datei umbenennen" für das Ziel "Element: A:\test\153.jpg Ziel: A:\test\test_010.jpg".
WhatIf: Ausführen des Vorgangs "Datei umbenennen" für das Ziel "Element: A:\test\154.jpg Ziel: A:\test\test_011.jpg".
WhatIf: Ausführen des Vorgangs "Datei umbenennen" für das Ziel "Element: A:\test\155.jpg Ziel: A:\test\test_012.jpg".
Другой, немного другой способ:
$nr=1;Get-ChildItem -Filter *.jpg |
Rename-Item -Newname {"$(split-path -leaf $_.Directory)_{0:d3}.jpg" -f
$global:nr++}