++ Оператор над переменной не изменяется, как ожидалось, в ScriptBlock

Я пытаюсь переименовать файлы, поставив префикс на основе увеличивающегося счетчика в таких файлах, как:

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

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

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


++ Оператор над переменной не изменяется, как ожидалось, в ScriptBlock

Почти уверен, что вы сталкиваетесь с проблемами области видимости. Попробуйте изменить все ссылки с $count на $script:count.

TheMadTechnician 02.07.2019 00:15
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
1
289
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

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

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

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

    • Дополнительные сведения об областях и неявном создании локальных переменных см. в статье этот ответ.

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

прагматичный, но потенциально ограничивающий обходной путь должен использовать спецификатор области действия $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 02.07.2019 03:52

@mklement0 mklement0 Если бы производительность была проблемой, я бы использовал System.IO.File.Move() напрямую или переписал скрипт с помощью Python.

Bacon Bits 02.07.2019 05:05

Необходимость прибегать к «неродным» средствам всегда неудобна и для многих является препятствием. При достаточно большом наборе входных данных использование блоков сценария с отложенной привязкой обеспечивает значительное преимущество в производительности по сравнению с вызовом ForEach-Object с повторными вызовами, и на это (а) стоит обратить внимание и (б) может быть достаточно хорошо в данной ситуации. Итак, как это часто бывает, все зависит от степени производительности PowerShell и сводится к выбору правильных конструкций. В противном случае, да, вы можете обратиться к прямому использованию платформы .NET или внешних исполняемых файлов.

mklement0 02.07.2019 06:12

@ mklement0 «Прибегать к «неродным» средствам всегда неудобно и для многих является препятствием». Говорит человек, пытающийся объяснить задержку и ссылки? Ну давай же.

Bacon Bits 02.07.2019 22:22

Блоки скриптов с отложенной привязкой — это выразительная, лаконичная и — относительно — производительная стандартная функция, которая заслуживает гораздо более широкого использования; до недавнего времени оно чахло из-за отсутствия документации; Надеюсь, мои ответы помогут немного его популяризировать. К сожалению, его реализация имеет недостатки в отношении области видимости - если вы согласны, сделайте так, чтобы ваш голос был услышан здесь. Мой ответ теперь содержит - разумный - $script:count++ обходной путь в первую очередь; те, кто ищет более надежное решение, могут использовать — несомненно, неясное — решение [ref].

mklement0 03.07.2019 05:29

Вау, в последнее время это часто всплывает. Вот моя любимая альтернатива 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 07.07.2019 23:54

@mklement0 mklement0 На самом деле это 3 блока скрипта, переданные -process, но они выполняют одну и ту же функцию.

js2010 08.07.2019 00:07

Вы правы — технически все они связаны с -Process, но фактически обрабатываются так, как если бы они были переданы -Begin, -Process и -End соответственно.

mklement0 08.07.2019 00:20

Другие вопросы по теме