Переименование нескольких файлов с помощью пакетного сценария

У меня есть много файлов в этом формате:

D:\images\AAA_BBB\image\whatever1.jpg  
D:\images\AAA_BBB\image\whatever2.jpg
D:\images\FFF_EEE_CCC\image\asdf1.jpg
D:\images\FFF_EEE_CCC\image\asdf2.jpg
D:\images\WWW_XXX_YYY_ZZZ\image\someimage1.jpg
D:\images\WWW_XXX_YYY_ZZZ\image\someimage2.jpg

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

D:\images\AAA\AAA_0.jpg
D:\images\AAA\AAA_1.jpg
D:\images\EEE\EEE_0.jpg
D:\images\EEE\EEE_1.jpg
D:\images\YYY\YYY_0.jpg  
D:\images\YYY\YYY_1.jpg

Обратите внимание, что количество токенов в папке со знаками подчеркивания может быть любой длины.

Следующая попытка даст мне только второй жетон, начиная с фронта. Использование «Tokens=-2» для получения второго последнего токена ничего не выведет.

@echo off & setlocal EnableDelayedExpansion 

PushD "D:\images" || Exit /B

For /F "EOL=? Delims = " %%G In ('Dir /A:D /B 2^>NUL
 ^| %SystemRoot%\System32\findstr.exe /I /R  "^[^_][^_]*_[^_][^_]*_[^_][^_]*_"'
) Do For /F "Tokens=2 EOL=? Delims=_" %%H In ("%%G") Do (
    Set "i=-1"
    For /F "EOL=? Delims = " %%I In ('Dir "%%G\image" /A:-D /B') Do (
        Set /A i += 1
        SetLocal EnableDelayedExpansion
        Ren "%%G\image\%%I" "%%H_!i!%%~xI" && (
            If Not Exist "%%H\." MD "%%H"
            Move /Y "%%G\image\%%H_!i!%%~xI" "%%H"
        )
        EndLocal
    )
)

PopD

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

@echo off & setlocal EnableDelayedExpansion 

PushD "D:\images" || Exit /B

For /F "EOL=? Delims = " %%G In ('Dir /A:D /B 2^>NUL
 ^| %SystemRoot%\System32\findstr.exe /I /R  "^[^_][^_]*_[^_][^_]*_[^_][^_]*_"'
) Do (
    Set "dirName=%%G"
    Set "lastSegment = "
    For %%H In ("!dirName:_ = " "!") Do (
        Set "secondLastSegment=!lastSegment!"
        Set "lastSegment=%%~H"
    )
    Set "segment=%%~xG"
    Set "i=-1"
    For /F "EOL=? Delims = " %%I In ('Dir "%%G\image" /A:-D /B') Do (
        Set /A i += 1
        SetLocal EnableDelayedExpansion
        Ren "%%G\image\%%I" "!secondLastSegment!_!i!!segment!" && (
            If Not Exist "!secondLastSegment!\." MD "!secondLastSegment!"
            Move /Y "%%G\image\!secondLastSegment!_!i!!segment!" "!secondLastSegment!"
        )
        EndLocal
    )
)

PopD
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
99
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Основываясь на предоставленной вами информации, поможет ли это вам?

@Echo Off
SetLocal EnableExtensions DisableDelayedExpansion

Set "BaseDir=D:\images"

PushD "%BaseDir%" 2>NUL || Exit /B
For /F "EOL=? Delims = " %%G In ('Dir "*_*" /A:D-L /B /O:N 2^>NUL
 ^| %SystemRoot%\System32\findstr.exe /IR "[^_][^_]*_[^_][^_]*"') Do (
    PushD "%%G"
    For /F "Tokens=1,* Delims=:" %%H In ('Dir "image\*.jpg" /A:-D-L /B 2^>NUL
     ^| %SystemRoot%\System32\findstr.exe /INR "\.jpg$"') Do (
        For /F "Delims=_" %%J In ("%%G") Do (
            If Not Exist "%BaseDir%\%%J\." MD "%BaseDir%\%%J"
            Move /Y "%BaseDir%\%%G\image\%%~I" "%BaseDir%\%%J\%%J_%%H%%~xI"
        ) 1>NUL
    )
    PopD
)
PopD

Имейте в виду, что у вас могут быть пустые каталоги, которые вы также можете очистить!

@ECHO Off
SETLOCAL ENABLEDELAYEDEXPANSION

SET "sourcedir=u:\images"

FOR /f %%e IN ('DIR /b /ad "%sourcedir%\*_*"') DO (
 rem %%e has firstlevel subdirectory name - must have at least one "_"
 SET "L2=%%e"
 FOR %%o IN (!L2:_^= !) DO SET "destsub=!prevpart!"&SET "prevpart=%%o"
 FOR /f %%y IN ('dir /b /a-d "%sourcedir%\%%e\image\*.jpg"') DO (
  MD "%sourcedir%\!destsub!" >nul 2>NUL
  SET "needtomove=Y"
  FOR /L %%u IN (0,1 9999) DO IF DEFINED needtomove IF NOT EXIST "%sourcedir%\!destsub!\!destsub!_%%u.jpg" move "%sourcedir%\%%e\image\%%y" "%sourcedir%\!destsub!\!destsub!_%%u.jpg" >nul&SET "Needtomove = "
 )
)

GOTO :EOF

Всегда сверяйтесь с тестовым каталогом перед применением к реальным данным.

Найдите все подкаталоги, содержащие _. Найдите предпоследний элемент, разделенный _, заменив _s пробелами и используя for. Для каждого *.jpg в ssubdirectory\image попытайтесь указать его место назначения (2> nul подавляет сообщения об ошибках, такие как «Каталог уже существует»), затем попытайтесь найти первое доступное имя в целевом каталоге (поскольку нет гарантии, что случайные файлы не исчезнут). t уже существуют в пункте назначения) и сделайте ход.

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

Это должно работать:

@echo off
setlocal EnableDelayedExpansion

pushd "D:\images" || exit /B

for /D %%d in (*_*) do (
   set "oldName=%%d"
   for %%b in (".!oldName:_=.!") do for %%c in ("%%~Nb") do set "newName=%%~Xc"
   set "newName=!newName:~1!"
   md "!newName!"
   set "i=-1"
   for %%f in ("%%d\image\*.jpg") do (
      set /A i+=1
      move "%%~f" "!newName!\!newName!_!i!%%~Xf"
   )
)

В ваших примерах много ненужного или избыточного кода...

  • Как правило, нет необходимости использовать команду for /F, когда вы просто хотите обработать файлы или папки; обычные for или for /D проще и работают быстрее, чем for /F.
  • Команда for /D %%d in (*_*) do обрабатывает все папки хотя бы с одним символом подчеркивания; Я не понимаю, почему вы используете findstr со сложным регулярным выражением...
  • Посмотрите мой трюк, чтобы извлечь вторую фамилию, используя модификаторы параметров команды ~Name и ~eXtension FOR.
  • Если вы обрабатываете каждую исходную папку один раз, вам просто нужно один раз создать соответствующую целевую папку. Вам не нужно проверять, существует ли такая папка в каждом файле. Если целевая папка может существовать ранее, вы можете проверить ее только один раз для каждой исходной папки...

В вашем коде есть несколько проблем, которые я хочу устранить:

  • Ваш фильтр findstr слишком строг, поэтому он позволит обрабатывать только два последних изображения в вашем списке примеров. Позвольте мне порекомендовать полностью удалить его и вместо этого использовать маску каталога *_*.
  • Нет необходимости пытаться создать целевой каталог для каждого файла изображения; достаточно одной попытки для исходного каталога.
  • Проверка существования каталога с помощью if exist приводит к неправильному результату при добавлении \. к пути (когда он указывает на файл, а не на каталог, он также сообщается как существующий); вместо этого следует добавить \*, чтобы файлы считались несуществующими.
  • Вы должны указать маску имени файла, например *.jpg и/или *.png, для файлов изображений, которые будут обрабатываться, чтобы отфильтровать непреднамеренные.
  • Возможно, вам следует указать порядок сортировки файлов изображений, которые будут обрабатываться, потому что в противном случае файловая система определяет порядок, который может быть не таким, как вы ожидаете.
  • Переменная segment будет пустой, потому что %%~xG будет пустой (исходя из ваших примеров). Вы, скорее всего, имели в виду %%~xI.
  • Хотя технически это работает, нет необходимости сначала переименовывать текущий файл изображения, а затем перемещать его в другой подкаталог; достаточно одной операции движения.
  • После перемещения файлов изображений могут остаться пустые подкаталоги, которые следует очистить.
  • При использовании setlocal для локализации среды использование pushd в большинстве случаев не требуется, поскольку setlocal также включает в себя текущий рабочий каталог. Помните, что отсутствие popd может оставить нежелательные записи стека pushd, в отличие от setlocal, потому что endlocal всегда неявно выполняется, когда пакетный скрипт завершается.
  • Расширение переменной с задержкой не должно быть включено глобально, потому что имена файлов/каталогов, содержащие ! или ^ в своих именах, могут вызвать проблемы.

Вот ваш код с указанными исправленными проблемами (не касаясь внутренней методологии):

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Usage of `pushd`/`popd` is usually not needed when using `setlocal/`endlocal`:
cd /D "%~dp0images" || exit /B

for /F "eol=| delims = " %%G in ('
    rem/ // The `findstr` filter was too strong and is unnecessary ^(a simple `dir` is enough^)^^! ^& ^
        2^> nul dir /B /A:D-H-S-L "*_*"
') do (
    set "dirName=%%G"
    set "lastSegment = "
    rem // Toggle delayed expansion to have it enabled only when necessary to not lose `!`-symbols:
    setlocal EnableDelayedExpansion
    for %%H in ("!dirName:_ = " "!") do for %%J in ("!lastSegment!") do (
        endlocal
        set "secondLastSegment=%%~J"
        set "lastSegment=%%~H"
        setlocal EnableDelayedExpansion
    )
    rem /* Creation of the target directory is now done once per source directory
    rem    (to check whether a directory exists, append `\*` rather than `\.`): */
    if not exist "!secondLastSegment!\*" md "!secondLastSegment!"
    endlocal
    set "targetDir=%%G\image"
    rem // Since `%%~xG` is going to be blank, `segment` will be empty and is therefore useless:
    rem/ set "segment=%%~xG"
    set /A "i=-1"
    rem /* The `dir` should specify the file name mask(s) to exclude non-image files
    rem    (a sort order is now specified; hidden, system and linked items are now excluded): */
    for /F "eol=| delims = " %%I in ('cd /D "%%G\image" ^&^& dir /B /A:-D-H-S-L /O:DN "*.jpg" "*.png"') do (
        set /A "i+=1"
        set "fileName=%%I"
        rem // Variable `segment` is empty; use `%%~xI` instead:
        set "fileExt=%%~xI"
        setlocal EnableDelayedExpansion
        set "newFileName=!secondLastSegment!_!i!!fileExt!"
        rem/ ren "!targetDir!\!fileName!" "!newFileName!" && (
            rem The target directory has already been created at this point.
            rem/ move /Y "!targetDir!\!newFileName!" "!secondLastSegment!"
        rem/ )
        
        
        rem /* Instead of renaming and then moving, a single move operation suffices
        rem    (note that variable `segment` is empty, hence use `%%~xI` instead): */
        > nul move /Y "!targetDir!\!fileName!" "!secondLastSegment!\!newFileName!"
        rem // Clean up potentially empty sub-directory:
        2> nul rd "!targetDir!"
        endlocal
    )
    rem // Clean up potentially empty source directory:
    2> nul rd "%%G"
)

endlocal
exit /B

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