无法使用 ! 解析文件名在其中但双引号?

问题描述 投票:0回答:2

我可能已经把自己逼到了一个角落,但由于某种原因,这批处理几乎可以处理任何名称,除了带有

!
的名称。我一生都无法弄清楚为什么我做错了。 如果文件名称中包含
!
,它将忽略它并创建一个不带
!
的新文件,并将其移动到批处理目录中。 实际上,该文件名的每个实例最终都会缺少
!
,甚至输出 MGL 中的引号:

setlocal enabledelayedexpansion
            set choice=
        set /p choice= Type selection and press enter: 
            if '%choice%'=='1' set console=SNES
            if '%choice%'=='1' set settings=delay="2" type="f" index="0"
            if '%choice%'=='2' set console=PSX
            if '%choice%'=='2' set settings=delay="1" type="s" index="0"
            if '%choice%'=='3' set console=gameboy
            if '%choice%'=='3' set settings=delay="1" type="f" index="1"
            if '%choice%'=='4' set console=C64
            if '%choice%'=='4' set settings=delay="1" type="f" index="1"
            
    mkdir MGL_!console! > nul 2>&1
    
    for /R %%a in (*.32x,*.a26,*.a78,*.abs,*.bin,*.bs,*.chd,*.cof,*.col,*.fds,*.gb,*.gbc,*.gba,*.gg,*.j64,*.jag,*.lnx,*.md,*.neo,*.nes,*.o,*.pce,*.rom,*.sc,*.sfc,*.sg,*.smc,*.smd,*.sms,"*.vec",*.wsc,*.ws) do (
    
        set "filepath=%%a"
        set "filepath=!filepath:%CD%=!"
        set "filepath=!filepath:\=/!"
        set "filepath=!filepath:~1!"
        
    echo ^<mistergamedescription^> > "%%~pna.mgl"
    echo     ^<rbf^>_console/!console!^</rbf^> >> "%%~pna.mgl"
    echo     ^<file !settings! path="!filepath!"/^> >> "%%~pna.mgl"
    echo ^</mistergamedescription^> >> "%%~pna.mgl"
    >nul move "%%~pna.mgl" "MGL_%console%\%%~na.mgl"
    )

输出看起来全程都不错?

     echo </mistergamedescription>  1>>"\mISTER\Done\Vectrex\1 World - A-Z\Blitz! - Action Football (USA, Europe) (0F11CE0C).mgl"
 move "\mISTER\Done\Vectrex\1 World - A-Z\Blitz! - Action Football (USA, Europe) (0F11CE0C).mgl" "MGL_!console!\Blitz! - Action Football (USA, Europe) (0F11CE0C).mgl" 1>nul
)
The system cannot find the file specified.
windows batch-file cmd escaping delayedvariableexpansion
2个回答
1
投票

我认为该问题是由于在

%%a
扩展期间启用了延迟扩展而引起的,因为受影响的路径(
\Blitz! - *
)中有一个感叹号,该路径由于延迟扩展而丢失。因此,您需要切换延迟扩展,以便仅在实际需要时启用它。这是一种改进的方法,还具有其他一些改进:

rem // Initially disable delayed expansion:
setlocal DisableDelayedExpansion

:USER_PROMPT
rem // Use quoted `set` syntax in general:
set "choice="
set /P choice="Type select and press enter: "
rem /* Use quotation marks rather than apostrophes (single quotes);
rem    then combine multiple duplicate conditions using the `&` operator;
rem    unwanted trailing spaces are prevented by the quoted `set` syntax: */
if "%choice%"=="1" set "console=SNES"    & set settings=delay="2" type="f" index="0"
if "%choice%"=="2" set "console=PSX"     & set settings=delay="1" type="s" index="0"
if "%choice%"=="3" set "console=gameboy" & set settings=delay="1" type="f" index="1"
if "%choice%"=="4" set "console=C64"     & set settings=delay="1" type="f" index="1"
rem // Retry if user entered something else:
goto :USER_PROMPT

::rem /* Alternative user prompt approach -- the `choice` command;
::rem    the `ErrorLevel` variable reflects the POSITION of the choices;
::rem    it is just coincidental here that its value equals the choice: */
::choice /C 1234 /M "Type selection: " /N
::if %ErrorLevel% equ 1 set "console=SNES"    & set settings=delay="2" type="f" index="0"
::if %ErrorLevel% equ 2 set "console=PSX"     & set settings=delay="1" type="s" index="0"
::if %ErrorLevel% equ 3 set "console=gameboy" & set settings=delay="1" type="f" index="1"
::if %ErrorLevel% equ 4 set "console=C64"     & set settings=delay="1" type="f" index="1"
::goto :EOF

mkdir "MGL_%console%" > nul 2>&1

for /R %%a in (*.32x,*.a26,*.a78,*.abs,*.bin,*.bs,*.chd,*.cof,*.col,*.fds,*.gb,*.gbc,*.gba,*.gg,*.j64,*.jag,*.lnx,*.md,*.neo,*.nes,*.o,*.pce,*.rom,*.sc,*.sfc,*.sg,*.smc,*.smd,*.sms,"*.vec",*.wsc,*.ws) do (
    rem // Delayed expansion is still disabled at this point, so it does not interfere with expansion of `%%a`:
    set "filepath=%%~a"
    rem // Immediately set target file, hence no more move is necessary later:
    set "targetfile=MGL_%console%\%%~na.mgl"
    
    rem // Now toggle delayed expansion:
    setlocal EnableDelayedExpansion
    set "filepath=!filepath:*%CD%\=!"
    ::rem // Improved variant to remove root path, avoiding immediate (`%`-)expansion:
    ::for /F "delims=" %%b in ("!CD!") do set "filepath=!filepath:*%%b\=!"
    set "filepath=!filepath:\=/!"
    
    rem // Redirect to the target file only once (also avoiding trailing spaces):
    > "!targetfile!" (
        echo ^<mistergamedescription^>
        echo     ^<rbf^>_console/!console!^</rbf^>
        echo     ^<file !settings! path="!filepath!"/^>
        echo ^</mistergamedescription^>
    )
    endlocal
)
endlocal

无论如何,我仍然不明白你是否真的想在每次

*.mgl
循环迭代中覆盖
for /R
文件。


0
投票

文件名包含一个或多个

!
的问题是由于在执行命令行
set "filepath=%%a"
时启用延迟扩展而导致的。该命令行被第二次解析,因为启用了延迟扩展,导致将分配给循环变量
a
的文件名中的所有感叹号解释为延迟扩展变量引用的开始/结束。因此,在将剩余字符串分配给环境变量
filepath
之前,会从文件名字符串中删除单个感叹号。

这是重写的代码,以避免这个问题和其他几个可能的问题。

@echo off
setlocal EnableExtensions DisableDelayedExpansion
cls
echo(
echo Select a system to create MGL files for:
echo(
echo    1. SNES
echo    2. PSX
echo    3. Gameboy
echo    4. C64
echo(
:Console0
%SystemRoot%\System32\choice.exe /C 1234 /N /M "Please make your selection:"
goto Console%ErrorLevel%

:Console1
set "console=SNES"
set "settings=delay="2" type="f" index="0""
goto CreateFolder

:Console2
set "console=PSX"
set "settings=delay="1" type="s" index="0""
goto CreateFolder

:Console3
set "console=gameboy"
set "settings=delay="1" type="f" index="1""
goto CreateFolder

:Console4
set "console=C64"
set "settings=delay="1" type="f" index="1""

:CreateFolder
mkdir "MLG_%console%" 2>nul
if exist "MLG_%console%\" goto GetBasePathLength

for %%I in (".\MLG_%console%") do echo ERROR: Failed to create the directory: "%%~fI"
echo(
@setlocal EnableExtensions EnableDelayedExpansion & for /F "tokens=1,2" %%G in ("!CMDCMDLINE!") do @endlocal & if /I "%%~nG" == "cmd" if /I "%%~H" == "/c" pause
goto EndBatch

:GetBasePathLength
set "BasePath=_%CD%"
if not "%BasePath:~-1%" == "\" set "BasePath=%BasePath%\"
setlocal EnableDelayedExpansion
set "BasePathLength=0"
for /L %%I in (12,-1,0) do (
    set /A "BasePathLength|=1<<%%I"
    for %%J in (!BasePathLength!) do if "!BasePath:~%%J,1!" == "" set /A "BasePathLength&=~1<<%%I"
)
endlocal & set "BasePathLength=%BasePathLength%"

for /R %%I in (*.32x,*.a26,*.a78,*.abs,*.bin,*.bs,*.chd,*.cof,*.col,*.fds,*.gb,*.gbc,*.gba,*.gg,*.j64,*.jag,*.lnx,*.md,*.neo,*.nes,*.o,*.pce,*.rom,*.sc,*.sfc,*.sg,*.smc,*.smd,*.sms,"*.vec",*.wsc,*.ws) do (
    set "FileName=%%I"
    set "OutputFile=MLG_%console%\%%~nI.mgl"
    setlocal EnableDelayedExpansion
    set "FileName=!FileName:~%BasePathLength%!"
    set "FileName=!FileName:\=/!"
    (
        echo ^<mistergamedescription^>
        echo     ^<rbf^>_console/!console!^</rbf^>
        echo     ^<file !settings! path="!FileName!"/^>
        echo ^</mistergamedescription^>
    )>"!OutputFile!"
    endlocal
)

:EndBatch
endlocal

我建议先阅读:

看起来奇怪的 FOR 命令行,末尾带有命令

pause
,仅当处理批处理文件的 Windows 命令处理器实例启动且第一个参数不区分大小写 /C 时才运行
PAUSE
,如双击时完成的那样在批处理文件上。如果首先打开命令提示符窗口,然后在命令提示符窗口中执行批处理文件,并且在当前目录中创建输出目录时发生错误,则不会执行PAUSE,在这种情况下,可以读取错误消息命令提示符窗口而不使用
pause

FOR 循环首先将完全限定的文件名分配给环境变量

FileName
,同时禁用延迟扩展以处理路径/名称中带有一个或多个问号的正确文件名。

相对于当前目录的路径的输出文件名也被分配给环境变量

OutputFile
,同时禁用延迟扩展也适用于像
Blitz! - Action Football (USA, Europe) (0F11CE0C).mgl
这样的文件名。

启用下一个延迟扩展,这会导致在后台执行其他操作,如此答案中所述,其中包含有关命令SETLOCALENDLOCAL的所有详细信息。

然后从文件名中删除基本路径,并使用延迟扩展将所有

\
替换为
/

接下来,创建并打开输出文件,使用延迟扩展将 ECHO 的四行输出写入文件中。最后关闭输出文件并使用命令ENDLOCAL恢复之前的执行环境。

要了解所使用的命令及其工作原理,请打开命令提示符窗口,执行以下命令,并完整、仔细地阅读每个命令显示的帮助页面。

  • choice /?
  • cls /?
  • echo /?
  • endlocal /?
  • for /?
  • goto /?
  • if /?
  • mkdir /?
  • pause /?
  • set /?
  • setlocal /?
© www.soinside.com 2019 - 2024. All rights reserved.