Как заменить строку на подстроку, если в строке есть скобки - PullRequest
2 голосов
/ 26 апреля 2020

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

То, что у меня пока есть, таково:

@ECHO OFF
CLS
SETLOCAL EnableExtensions EnableDelayedExpansion

SET "StartTime=%TIME: =0%"
SET "Folder=%~1"
IF "%Folder:~-1%"=="\" SET "Folder=%Folder:~0,-1%"
FOR %%G IN ("%Folder%") DO SET "Archive=%%~nxG"
ECHO Processing %Folder%...
ECHO.
PUSHD "%Folder%"
FOR /F "DELIMS=" %%G IN ('DIR /A:-D /B /O:N /S') DO (
   SET "FullPath=%%G"
   ECHO !FullPath:%Folder%\=! >> "%Archive%.$$$"
)
pause
endlocal
goto :eof

Это прекрасно работает, пока в каталоге нет (или) (например, C: \ Program Files (x86) \ AMD), затем выдает сообщение об ошибке и останавливается.

Не удается найти решение, чтобы обойти это ограничение. Любой вклад с благодарностью. Спасибо!

Ответы [ 2 ]

2 голосов
/ 26 апреля 2020

Великолепные объяснения и решения от Mofi

Вот немного более короткий и более прямой способ достижения надежного решения, которое все еще опирается на фундаментальную технику Mofi использования FOR /F tokens и delims, чтобы удалить путь root.

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

Я также использую совершенно другой метод подсчета количества токенов в пути root. Я заменяю символ новой строки \, а затем подсчитываю количество строк с помощью FIND /C, когда выводю значение. Это немного сложно, но очень эффективно и работает без обращения к GOTO l oop.

. Я также сохраняю сообщение об ошибке, если файлы не найдены, и не беспокоюсь об удалении результирующий пустой файл.

@echo off
setlocal disableDelayedExpansion

::==== Get normalized values for supplied path in %1
::==== Default to current directory if not specified
for %%F in ("%~f1.") do (  %=== The trailing dot allows paths with/without trailing \ to work %
  set "folder=%%~fF"   ==== The normalized path for the root folder
  set "archive=%%~nxF" ==== The base name of the output file
  set "att=-%%~aF"     ==== List of attributes for the root folder
)

::==== Validate path by checking for d in attributes
if %att:d=% == %att% (
  >&2 echo Supplied path is invalid or not a folder
  exit /b 1
)

::==== Special handling if drive root
if not defined archive (
  set "archive=drive %folder:~0,1% root"
  set cnt=1
  goto start
)

::==== Normal processing for all other paths
setlocal enableDelayedExpansion
for %%L in (^"^
%==== Puts quoted newline in FOR variable L %
^") do set "folder2=!folder:\=%%~L!"   ==== Substitutes newline for all \ in folder
::==== Count the number of tokens (lines) in folder2
setlocal disableDelayedExpansion
for /f %%N in ('"(cmd /v:on /c echo !folder2!)|find /c /v """') do set cnt=%%N

:start  ==== Finally get the sorted list of files without root path
>"%archive%.$$$" (
  for /f "delims=\ tokens=%cnt%*" %%A in ('dir "%folder%" /a:-d /b /o:n /s') do echo %%B
)

Код довольно лаконичен без комментариев и лишних пустых строк

@echo off
setlocal disableDelayedExpansion
for %%F in ("%~f1.") do (
  set "folder=%%~fF"
  set "archive=%%~nxF"
  set "att=-%%~aF"
)
if %att:d=% == %att% (
  >&2 echo Supplied path is invalid or not a folder
  exit /b 1
)
if not defined archive (
  set "archive=drive %folder:~0,1% root"
  set cnt=1
  goto start
)
setlocal enableDelayedExpansion
for %%L in (^"^

^") do set "folder2=!folder:\=%%~L!"
setlocal disableDelayedExpansion
for /f %%N in ('"(cmd /v:on /c echo !folder2!)|find /c /v """') do set cnt=%%N
:start
>"%archive%.$$$" (
  for /f "delims=\ tokens=%cnt%*" %%A in ('dir "%folder%" /a:-d /b /o:n /s') do echo %%B
)

Обновление в ответ на Комментарий Мофи

Мой оригинальный ответ не учитывал возможность подстановочных знаков при вводе. Подстановочные знаки можно исключить, добавив следующее после начальных SETLOCAL

for /f "delims=*?<> tokens=2" %%A in (".%~1.") do (
  >&2 echo Wildcards * ? ^< and ^> not supported
  exit /b 1
)

Обратите внимание, что "<" и ">" являются плохо документированными подстановочными знаками (только если они указаны в кавычках путь)!

Но Мофи описал интересное поведение, которого я никогда раньше не видел. Сначала я не мог понять, как C:\test\* давал C:\.

Это просто результат того, что %~f1 видит запись каталога . в качестве первого указанного файла / папки в тестовой папке, и затем, когда мой код добавляет свою собственную точку, он становится C:\test\.., что, конечно, нормализуется до C:\.

Некоторое время я думал, что происходит специальная обработка, когда узел пути является ничем но подстановочные знаки, но я опубликовал поведение на DosTips , и респондент напомнил мне о записях каталога . и .. на всех дисках, кроме диска root.

2 голосов
/ 26 апреля 2020

Простым решением является вставка следующей командной строки ниже SET "FullPath=%%G":

SET "RelativePath=!FullPath:%Folder%\=!"

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

Затем следующая строка изменяется на:

IF DEFINED RelativePath ECHO !RelativePath!>>"%Archive%.$$$"

Эта строка записывает относительный путь в файл без завершающего пробела, так как больше нет свободного места на оператор перенаправления >>, который будет также выводиться ECHO .

Таким образом, пакетный файл будет:

@ECHO OFF
CLS
SETLOCAL EnableExtensions EnableDelayedExpansion
IF "%~1" == "" GOTO :EOF

SET "Folder=%~1"
IF "%Folder:~-1%" == "\" SET "Folder=%Folder:~0,-1%"
FOR %%G IN ("%Folder%") DO SET "Archive=%%~nxG"
DEL "%Archive%.$$$" 2>NUL
ECHO Processing %Folder%...
ECHO/
FOR /F "DELIMS=" %%G IN ('DIR "%Folder%" /A:-D /B /O:N /S') DO (
   SET "FullPath=%%G"
   SET "RelativePath=!FullPath:%Folder%\=!"
   IF DEFINED RelativePath ECHO !RelativePath!>>"%Archive%.$$$"
)
ENDLOCAL
PAUSE

Но с этим остается несколько проблем решение.

  1. Если путь к папке, назначенный переменной среды Folder, содержит знак равенства, подстановка строки внутри последней FOR l oop не работает должным образом.
  2. Путь к папке, назначенной переменной среды Folder с одним или несколькими восклицательными знаками, обработан неправильно из-за включенного отложенного расширения.
  3. Полное квалифицированное имя файла, выводимое DIR в отдельном командном процессе, запущенном в фоновом режиме с одним или несколькими восклицательными знаками, обрабатывается некорректно из-за включенного отложенного расширения.
  4. Пакетный файл выполняет не работает должным образом при вызове с root путем к папке на диске, таком как D:\ или просто \ для ссылки на root текущего диска.
  5. Пакетный файл не работает должным образом при вызове с относительным путем.
  6. Обработка пакетного файла завершается с сообщением синтаксической ошибки при вызове с """, так как это приводит к синтаксически неверной командной строке IF """ == "" GOTO :EOF во время выполнения пакетного файла.
  7. Недопустимый путь к папке, такой как C:\Temp\* или C:\Temp:, переданный в пакетный файл, в некоторых случаях не определяется пакетным файлом и приводит к непредвиденному поведению.

См. Microsoft статья о Именовании файлов, путей и пространств имен с подробностями об относительных путях.

Комментируемая летучая мышь Файл ch, указанный ниже, работает даже для этих необычных случаев использования.

@echo off
cls
setlocal EnableExtensions DisableDelayedExpansion

rem Do nothing if batch file called without a folder path
rem or with just one or more double quotes.
set "Folder=%~1"
if defined Folder set "Folder=%Folder:"=%"
if not defined Folder (
    echo Error: %~nx0 must be called with a folder path.
    goto EndBatch
)

rem Make sure the folder path contains backslashes and not forward slashes
rem and does not contain wildcard characters or redirection operators or a
rem horizontal tab character after removing all double quotes.
set "Folder=%Folder:/=\%"
for /F "delims=*?|<>    " %%I in ("%Folder%") do if not "%Folder%" == "%%I" (
    echo Error: %~nx0 must be called with a valid folder path.
    echo        "%~1" is not a valid folder path.
    goto EndBatch
)

rem Get full folder path in case of the folder was specified with a relative
rem path. If the folder path references the root of a drive like on using
rem "C:\" or just "\", redefine the folder path with full path for root of
rem the (current) drive. Determine also the name for the archive file to
rem create by this batch file in the current directory depending on name of
rem the folder to process respectively the drive in case of a root folder.
for %%I in ("%Folder%") do (
    set "Folder=%%~fI"
    set "ArchiveFile=%%~nxI.$$$"
)
if not "%Folder:~-1%" == "\" (
    set "Folder=%Folder%\"
) else (
    if "%Folder:~1,3%" == ":\" (
        set "ArchiveFile=Drive%Folder:~0,1%.$$$"
    ) else (
        for %%I in ("%Folder%.") do set "ArchiveFile=%%~nxI.$$$"
    )
)

rem Verify the existence of the folder. The code above processed also
rem folder paths of folders not existing at all and also invalid folder
rem paths containing for example a colon not (only) after drive letter.
if not exist "%Folder%" (
    echo Error: Folder "%Folder%" does not exist.
    goto EndBatch
)

rem Determine the number of folders in folder path.
set "FolderCount=0"
set "FolderRemain=%Folder%"
:CountFolders
set /A FolderCount+=1
for /F "tokens=1* delims=\" %%I in ("%FolderRemain%") do if not "%%J" == "" set "FolderRemain=%%J" & goto CountFolders

rem Write all files with path relative to base folder into the archive file.
echo Processing "%Folder%" ...
(for /F "tokens=%FolderCount%* delims=\" %%I in ('dir "%Folder%" /A:-D /B /O:N /S 2^>nul') do echo %%J)>"%ArchiveFile%"

rem Delete the archive file on being an empty file because of no file found.
for %%I in ("%ArchiveFile%") do if %%~zI == 0 (
    del "%ArchiveFile%"
    echo/
    echo Info: There are no files in folder: "%Folder%"
)

:EndBatch
endlocal
echo/
pause

ВНИМАНИЕ: В разделительном файле должен быть символ горизонтальной табуляции после разделителей *?|<>.

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

  • call /? ... для объяснения %~nx0 (имя пакетного файла без пути) и %~1 (аргумент 1 без окружающих двойных кавычек)
  • cls /?
  • del /?
  • dir /?
  • echo /?
  • endlocal /?
  • for /?
  • goto /?
  • if /?
  • pause /?
  • rem /?
  • set /?
  • setlocal /?

См. Также:

Вот еще раз расширенный пакетный файл в более компактной форме без комментариев:

@echo off & cls & setlocal EnableExtensions DisableDelayedExpansion

set "Folder=%~1"
if defined Folder set "Folder=%Folder:"=%"
if not defined Folder echo Error: %~nx0 must be called with a folder path.& goto EndBatch

set "Folder=%Folder:/=\%"
for /F "delims=*?|<>    " %%I in ("%Folder%") do if not "%Folder%" == "%%I" (
    echo Error: %~nx0 must be called with a valid folder path.
    echo        "%~1" is not a valid folder path.
    goto EndBatch
)

for %%I in ("%Folder%") do set "Folder=%%~fI" & set "ArchiveFile=%%~nxI.$$$"
if not "%Folder:~-1%" == "\" (set "Folder=%Folder%\") else if "%Folder:~1,3%" == ":\" (set "ArchiveFile=Drive%Folder:~0,1%.$$$") else for %%I in ("%Folder%.") do set "ArchiveFile=%%~nxI.$$$"
if not exist "%Folder%" echo Error: Folder "%Folder%" does not exist.& goto EndBatch

set "FolderCount=0"
set "FolderRemain=%Folder%"
:CountFolders
set /A FolderCount+=1
for /F "tokens=1* delims=\" %%I in ("%FolderRemain%") do if not "%%J" == "" set "FolderRemain=%%J" & goto CountFolders

echo Processing "%Folder%" ...
(for /F "tokens=%FolderCount%* delims=\" %%I in ('dir "%Folder%" /A:-D /B /O:N /S 2^>nul') do echo %%J)>"%ArchiveFile%"
for %%I in ("%ArchiveFile%") do if %%~zI == 0 del "%ArchiveFile%"& echo/& echo Info: There are no files in folder: "%Folder%"

:EndBatch
endlocal & echo/& pause
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...