迷人的发现。
DosTips 上的某处有一篇 jeb 帖子描述了主脚本与 CALLed 子例程中的工作方式%0
和变体:从子例程中给出子例程标签,但添加一个修饰符,如与运行脚本路径一起使用,即使 SHIFT 有被使用。%~f0
%0
%~f0
但我不记得 jeb 的帖子描述了%0
主例程(无子例程)中引用与未引用之间的区别。
我在下面扩展了 MC ND 的测试。我的脚本是c:\test\test.bat
.
@echo off
setlocal
echo(
echo Upon entry:
echo ---------------------------------------------------------
echo %%shift%% : %shift%
echo %%cd%% : %cd%
echo %%0 : %0
echo %%1 : %1
echo %%~d0 : %~d0
echo %%~p0 : %~p0
echo %%~n0 : %~n0
echo %%~x0 : %~x0
echo %%~f0 : %~f0
call echo call %%%%~f0 : %%~f0
echo ---------------------------------------------------------
set "shift=FALSE"
d:
echo(
echo Current directory set to D:\
:top
call :getInfo
:getInfo
echo(
if "%0" equ ":getInfo" (
<nul set /p "=From subroutine "
) else (
<nul set /p "=From main "
)
if "%shift%" equ "TRUE" (echo after SHIFT) else (echo before SHIFT)
echo ---------------------------------------------------------
echo %%shift%% : %shift%
echo %%cd%% : %cd%
echo %%0 : %0
echo %%1 : %1
echo %%~d0 : %~d0
echo %%~p0 : %~p0
echo %%~n0 : %~n0
echo %%~x0 : %~x0
echo %%~f0 : %~f0
call echo call %%%%~f0 : %%~f0
echo ---------------------------------------------------------
if "%0" equ ":getInfo" exit /b
if "%shift%" equ "TRUE" exit /b
shift
set "shift=TRUE"
goto top
这是使用test
命令和test
第一个参数的结果:
C:\test>test test
Upon entry:
---------------------------------------------------------
%shift% :
%cd% : C:\test
%0 : test
%1 : test
%~d0 : C:
%~p0 : \test\
%~n0 : test
%~x0 : .bat
%~f0 : C:\test\test.bat
call %~f0 : C:\test\test.bat
---------------------------------------------------------
Current directory set to D:\
From subroutine before SHIFT
---------------------------------------------------------
%shift% : FALSE
%cd% : D:\
%0 : :getInfo
%1 :
%~d0 : C:
%~p0 : \test\
%~n0 : test
%~x0 : .bat
%~f0 : C:\test\test.bat
call %~f0 : C:\test\test.bat
---------------------------------------------------------
From main before SHIFT
---------------------------------------------------------
%shift% : FALSE
%cd% : D:\
%0 : test
%1 : test
%~d0 : C:
%~p0 : \test\
%~n0 : test
%~x0 : .bat
%~f0 : C:\test\test.bat
call %~f0 : C:\test\test.bat
---------------------------------------------------------
From subroutine after SHIFT
---------------------------------------------------------
%shift% : TRUE
%cd% : D:\
%0 : :getInfo
%1 :
%~d0 : C:
%~p0 : \test\
%~n0 : test
%~x0 : .bat
%~f0 : C:\test\test.bat
call %~f0 : C:\test\test.bat
---------------------------------------------------------
From main after SHIFT
---------------------------------------------------------
%shift% : TRUE
%cd% : D:\
%0 : test
%1 :
%~d0 : D:
%~p0 : \
%~n0 : test
%~x0 :
%~f0 : D:\test
call %~f0 : D:\test
---------------------------------------------------------
C:\test>
以下是使用引用值的结果:
C:\test>"test" "test"
Upon entry:
---------------------------------------------------------
%shift% :
%cd% : C:\test
%0 : "test"
%1 : "test"
%~d0 : C:
%~p0 : \test\
%~n0 : test
%~x0 :
%~f0 : C:\test\test
call %~f0 : C:\test\test
---------------------------------------------------------
Current directory set to D:\
From subroutine before SHIFT
---------------------------------------------------------
%shift% : FALSE
%cd% : D:\
%0 : :getInfo
%1 :
%~d0 : C:
%~p0 : \test\
%~n0 : test
%~x0 : .bat
%~f0 : C:\test\test.bat
call %~f0 : C:\test\test.bat
---------------------------------------------------------
From main before SHIFT
---------------------------------------------------------
%shift% : FALSE
%cd% : D:\
%0 : "test"
%1 : "test"
%~d0 : D:
%~p0 : \
%~n0 : test
%~x0 :
%~f0 : D:\test
call %~f0 : D:\test
---------------------------------------------------------
From subroutine after SHIFT
---------------------------------------------------------
%shift% : TRUE
%cd% : D:\
%0 : :getInfo
%1 :
%~d0 : C:
%~p0 : \test\
%~n0 : test
%~x0 : .bat
%~f0 : C:\test\test.bat
call %~f0 : C:\test\test.bat
---------------------------------------------------------
From main after SHIFT
---------------------------------------------------------
%shift% : TRUE
%cd% : D:\
%0 : "test"
%1 :
%~d0 : D:
%~p0 : \
%~n0 : test
%~x0 :
%~f0 : D:\test
call %~f0 : D:\test
---------------------------------------------------------
C:\test>
我从 XP 和 Win 7 得到相同的结果。
在子例程中,一切都按预期工作。
但我无法从主要层面解释这种行为。在 SHIFT 之前,未加引号的命令使用执行脚本的真实路径。但是引用的命令使用命令行中的字符串代替,并使用当前工作驱动器和目录填充缺失值。然而,在 SHIFT 之后,未引用和引用的值的行为相同,它只是与实际传递的参数和当前工作驱动器和目录一起工作。
因此,在脚本中的任何位置获取执行脚本的路径信息的唯一可靠方法是使用子例程。如果当前驱动器和/或目录自启动以来已更改,或者如果有%0
. 很奇怪。充其量,我会将其归类为设计缺陷。在最坏的情况下,一个彻头彻尾的错误。
更新
实际上,修复代码的最简单方法是简单地使用 PUSHD 和 POPD,但我认为这不是您真正想要的 :-)
pushd R:
popd
我曾经认为您可以%~0
通过在更改工作目录之前捕获环境变量中的值来解决问题。但是,如果使用封闭引号调用脚本但没有.bat
扩展名,则可能会失败。如果您只查找驱动器,它可以工作,但路径、基本名称、扩展名、大小和时间戳等其他内容可能会失败。
事实证明,获得正确值的唯一方法是使用 CALLed 子例程。
请注意,在不明确的情况下可能会出现另一个潜在问题。两者^
和!
都可用于文件和文件夹名称。如果在启用延迟扩展的情况下捕获具有这些值的名称,则它们可能会损坏。批处理文件启动时通常会禁用延迟扩展,但它可能会在启用延迟扩展的情况下启动。您可以在捕获值之前显式禁用延迟扩展,但是还有另一个使用函数调用的选项。
下面的脚本定义了一个:currentScript
可以在任何情况下使用的函数,并且保证给出正确的值。您传入一个变量的名称来接收该值,并可选择传入一个修饰符字符串(不带波浪号)。默认选项是F
(完整路径,相当于DPNX
)
功能在:currentScript
底部。脚本的其余部分是用于演示和测试功能的测试工具。它对比了使用函数与%0
直接使用的结果。
@echo off
setlocal disableDelayedExpansion
set arg0=%0
if "%arg0:~0,1%%arg0:~0,1%" equ """" (set "arg0=Quoted") else set "arg0=Unquoted"
call :header
echo %%~tzf0 = "%~tzf0"
call :currentScript rtn tzf
echo :currentScript result = "%rtn%"
setlocal enableDelayedExpansion
call :header
echo %%~tzf0 = "%~tzf0"
call :currentScript rtn tzf
echo :currentScript result = "!rtn!"
endlocal
d:
call :header
echo %%~tzf0 = "%~tzf0"
call :currentScript rtn tzf
echo :currentScript result = "%rtn%"
setlocal enableDelayedExpansion
call :header
echo %%~tzf0 = "%~tzf0"
call :currentScript rtn tzf
echo :currentScript result = "!rtn!"
exit /b
:header
set "rtn="
setlocal
echo(
echo(
if "!" equ "" (set "delayed=ON") else set "delayed=OFF"
if "%cd%\" equ "%~dp0" (set "cwd=Original") else set "cwd=Modified"
echo %arg0%: %cwd% working directory, Delayed expansion = %delayed%
echo ---------------------------------------------------------------------------
exit /b
:currentScript rtnVar [options]
setlocal
set "notDelayed=!"
setlocal disableDelayedExpansion
set "options=%~2"
if not defined options set "options=f"
call set "rtn=%%~%options%0"
if not defined notDelayed set "rtn=%rtn:^=^^%"
if not defined notDelayed set "rtn=%rtn:!=^!%"
endlocal & endlocal & set "%~1=%rtn%" !
exit /b
当我给脚本起一个疯狂的名字时,这里有一些测试结果test^it!.bat
。我用不带引号和带引号的值进行了测试。可以看到:CurrentScript
函数总是有效,但是直接展开%~tzf0
经常会失败。
C:\test>TEST^^IT!.BAT
Unquoted: Original working directory, Delayed expansion = OFF
---------------------------------------------------------------------------
%~tzf0 = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"
:currentScript result = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"
Unquoted: Original working directory, Delayed expansion = ON
---------------------------------------------------------------------------
%~tzf0 = "11/07/2013 08:17 PM 1400 C:\test\testit.bat"
:currentScript result = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"
Unquoted: Modified working directory, Delayed expansion = OFF
---------------------------------------------------------------------------
%~tzf0 = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"
:currentScript result = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"
Unquoted: Modified working directory, Delayed expansion = ON
---------------------------------------------------------------------------
%~tzf0 = "11/07/2013 08:17 PM 1400 C:\test\testit.bat"
:currentScript result = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"
C:\test>"TEST^IT!.BAT"
Quoted: Original working directory, Delayed expansion = OFF
---------------------------------------------------------------------------
%~tzf0 = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"
:currentScript result = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"
Quoted: Original working directory, Delayed expansion = ON
---------------------------------------------------------------------------
%~tzf0 = "11/07/2013 08:17 PM 1400 C:\test\testit.bat"
:currentScript result = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"
Quoted: Modified working directory, Delayed expansion = OFF
---------------------------------------------------------------------------
%~tzf0 = "D:\TEST^IT!.BAT"
:currentScript result = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"
Quoted: Modified working directory, Delayed expansion = ON
---------------------------------------------------------------------------
%~tzf0 = "D:\TESTIT.BAT"
:currentScript result = "11/07/2013 08:17 PM 1400 C:\test\test^it!.bat"
C:\test>"TEST^IT!"
Quoted: Original working directory, Delayed expansion = OFF
---------------------------------------------------------------------------
%~tzf0 = "C:\test\TEST^IT!"
:currentScript result = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"
Quoted: Original working directory, Delayed expansion = ON
---------------------------------------------------------------------------
%~tzf0 = "C:\test\TESTIT"
:currentScript result = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"
Quoted: Modified working directory, Delayed expansion = OFF
---------------------------------------------------------------------------
%~tzf0 = "D:\TEST^IT!"
:currentScript result = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"
Quoted: Modified working directory, Delayed expansion = ON
---------------------------------------------------------------------------
%~tzf0 = "D:\TESTIT"
:currentScript result = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"
C:\test>
我还使用 、 和 的名称进行了测试,test^it.bat
并且都正常工作(未显示)。test!.bat
test.bat