我有一个包含以下内容的批处理文件:
echo %~dp0
CD Arvind
echo %~dp0
即使在更改%~dp0
的目录值之后也是如此。
但是,如果我从CSharp程序运行此批处理文件,%~dp0
的值会在 CD 之后发生变化。它现在指向新目录。以下是我使用的代码:
Directory.SetCurrentDirectory(//Dir where batch file resides);
ProcessStartInfo ProcessInfo;
Process process = new Process();
ProcessInfo = new ProcessStartInfo("mybatfile.bat");
ProcessInfo.UseShellExecute = false;
ProcessInfo.RedirectStandardOutput = true;
process = Process.Start(ProcessInfo);
process.WaitForExit();
ExitCode = process.ExitCode;
process.Close();
为什么以不同方式执行相同脚本的输出存在差异?
我在这里想念一下吗?
答案 0 :(得分:10)
This question开始讨论这一点,并进行了一些测试以确定原因。因此,在cmd.exe
内进行一些调试后...(这是针对 32位Windows XP cmd.exe 但由于行为在较新的系统版本上是一致的,可能是相同或类似的代码使用)
杰布的内部答案已经陈述
It's a problem with the quotes and %~0.
cmd.exe handles %~0 in a special way
这里的Jeb是正确的。
在正在运行的批处理文件的当前上下文中,有一个对当前批处理文件的引用,一个“变量”,其中包含正在运行的批处理文件的完整路径和文件名。
当访问变量时,从可用变量列表中检索其值,但如果请求的变量是%0
,并且已请求某个修饰符(使用~
),那么数据将在使用正在运行的批处理引用“变量”。
但~
的使用对变量有另一个影响。如果引用了该值,则会删除引号。这里代码中有一个错误。它的编码类似于(这里简化汇编程序为伪代码)
value = varList[varName]
if (value && value[0] == quote ){
value = unquote(value)
} else if (varName == '0') {
value = batchFullName
}
是的,这意味着当引用批处理文件时,执行if
的第一部分并且不使用批处理文件的完整引用,而是检索的值是用于引用的字符串调用它时的批处理文件。
那会怎么样?如果在调用批处理文件时使用了完整路径,那么就没有问题了。但是,如果调用中未使用完整路径,则需要检索批处理调用中不存在的路径中的任何元素。此检索假设相对路径。
一个简单的批处理文件(test.cmd
)
@echo off
echo %~f0
使用test
(无扩展名,无引号)调用时,我们获得c:\somewhere\test.cmd
使用"test"
(无扩展名,引号)调用时,我们获得了c:\somewhere\test
在第一种情况下,如果没有引号,则使用正确的内部值。在第二种情况下,当引用调用时,用于调用批处理文件("test"
)的字符串是不加引号和使用的。当我们请求完整路径时,它被认为是对test
。
这就是原因。怎么解决?
来自C#代码
请勿使用引号:cmd /c batchfile.cmd
如果需要引号,请使用批处理文件调用中的完整路径。这样%0
包含所有必需的信息。
来自批处理文件
可以从任何地方以任何方式调用批处理文件。检索当前批处理文件信息的唯一可靠方法是使用子例程。如果使用任何修饰符(~
),%0
将使用内部“变量”来获取数据。
@echo off
setlocal enableextensions disabledelayedexpansion
call :getCurrentBatch batch
echo %batch%
exit /b
:getCurrentBatch variableName
set "%~1=%~f0"
goto :eof
这将回显控制当前批处理文件的完整路径,与您调用文件的方式无关,无论是否带引号。
注意:为什么会有效?为什么子例程中的%~f0
引用返回不同的值?从子程序内部访问的数据不一样。执行call
时,将在内存中创建新的批处理文件上下文,并使用内部“变量”初始化此上下文。
答案 1 :(得分:5)
我试着解释为什么这种行为如此奇怪。一个相当技术性和冗长的故事,我会试着让它凝聚。这个问题的出发点是:
ProcessInfo.UseShellExecute = false;
如果您省略此声明或指定 true ,它会按预期运行。
Windows提供了两种启动程序的基本方法,ShellExecuteEx()和CreateProcess()。 UseShellExecute属性在这两者之间进行选择。前者是“聪明而友好的”#34;例如,它对shell的工作方式有很多了解。这就是为什么你可以将路径传递给任意文件,例如" foo.doc"。它知道如何查找.doc文件的文件关联,并找到知道如何打开foo.doc的.exe文件。
CreateProcess()是低级别的winapi函数,它与本机内核函数(NtCreateProcess)之间的粘合很少。注意函数的前两个参数lpApplicationName
和lpCommandLine
,您可以轻松地将它们与两个ProcessStartInfo属性匹配。
CreateProcess()提供两种不同的方式来启动程序是不可见的。第一个是将lpApplicationName设置为空字符串并使用lpCommandLine提供整个命令行。这使得CreateProcess变得友好,它在找到可执行文件后自动将应用程序名称扩展到完整路径。所以,例如," cmd.exe"扩展到" c:\ windows \ system32 \ cmd.exe"。但是当你使用lpApplicationName参数时它确实不这样做,它按原样传递字符串。
这个怪癖对程序产生影响,这些程序取决于指定命令行的确切方式。特别是对于C程序,它们假设argv[0]
包含其可执行文件的路径。它对%~dp0
有影响,它也使用了这个论点。从你的工作路径开始,在你的案例中的比目鱼只是" mybatfile.bat"而不是说," c:\ temp \ mybatfile.bat"。这使得它返回当前目录而不是" c:\ temp"。
那么假设要做什么,这在.NET Framework文档中完全没有记录,现在由你决定通过完整文件的路径名。所以正确的代码应该是这样的:
string path = @"c:\temp"; // Dir where batch file resides
Directory.SetCurrentDirectory(path);
string batfile = System.IO.Path.Combine(path, "mybatfile.bat");
ProcessStartInfo = new ProcessStartInfo(batfile);
您会看到%~dp0
现在按预期扩展。它使用path
而不是当前目录。
答案 2 :(得分:4)
乔伊的建议有所帮助。 只需更换
即可ProcessInfo = new ProcessStartInfo("mybatfile.bat");
与
ProcessInfo = new ProcessStartInfo("cmd", "/c " + "mybatfile.bat");
做了这个伎俩。
答案 3 :(得分:3)
引号和%~0
存在问题。
cmd.exe以特殊方式处理%~0
(%~1
除外)
它检查%0
是否是相对文件名,然后将它与开始目录一起添加。
如果找到文件,它将使用此组合,否则它会在实际目录前面添加 但是当名称以引号开头时,它似乎无法删除引号,而是在添加目录之前。
这就是为什么cmd /c myBatch.bat
有效的原因,因为myBatch.bat
在没有引号的情况下被调用。
您也可以使用完整的合格路径启动批处理,然后它也可以工作。
或者在更改目录之前保存批处理中的完整路径。
小test.bat
可以演示cmd.exe
@echo off
setlocal
echo %~fx0 %~fx1
cd ..
echo %~fx0 %~fx1
通过(在C:\ temp)
中调用它test test
输出应为
C:\temp\test.bat C:\temp\test
C:\temp\test.bat C:\test
因此,cmd.exe
能够找到test.bat
,但只有%~fx0
才能找到开始目录。
在通过
调用它的情况下"test" "test"
失败了
C:\temp\test C:\temp\test
C:\test C:\test
cmd.exe
甚至在更改目录之前无法找到批处理文件,它无法将名称扩展为c:\temp\test.bat
的全名
编辑:FixIt,即使%~0
有引号
存在函数调用的变通方法。
@echo off
echo This can be wrong %~f0
call :getCorrectName
exit /b
:getCorrectName
echo Here the value is correct %~0
exit /b
答案 4 :(得分:1)
命令行解释器 cmd.exe 在获取批处理文件路径的代码中有错误,如果使用双引号调用批处理文件并且路径相对于当前工作目录。
创建目录 C:\ Temp \ TestDir 。在此目录中创建名为 PathTest.bat 的文件,并复制&将以下代码粘贴到此批处理文件中:
@echo off
set "StartIn=%CD%"
set "BatchPath=%~dp0"
echo Batch path before changing working directory is: %~dp0
cd ..
echo Batch path after changing working directory is: %~dp0
echo Saved path after changing working directory is: %BatchPath%
cd "%StartIn%"
echo Batch path after restoring working directory is: %~dp0
接下来打开一个命令提示符窗口,并使用以下命令将工作目录设置为 C:\ Temp \ TestDir :
cd /D C:\Temp\TestDir
现在通过以下方式调用 Test.bat :
PathTest
PathTest.bat
.\PathTest
.\PathTest.bat
..\TestDir\PathTest
..\TestDir\PathTest.bat
\Temp\TestDir\PathTest
\Temp\TestDir\PathTest.bat
C:\Temp\TestDir\PathTest
C:\Temp\TestDir\PathTest.bat
对于所有10个测试用例,输出是 C:\ Temp \ TestDir \ 的四倍。
测试用例7和8使用相对于当前驱动器根目录的路径启动批处理文件。
现在让我们看看与之前相同的结果,但是在批处理文件名周围使用双引号。
"PathTest"
"PathTest.bat"
".\PathTest"
".\PathTest.bat"
"..\TestDir\PathTest"
"..\TestDir\PathTest.bat"
"\Temp\TestDir\PathTest"
"\Temp\TestDir\PathTest.bat"
"C:\Temp\TestDir\PathTest"
"C:\Temp\TestDir\PathTest.bat"
输出是测试用例5到10的预期值 C:\ Temp \ TestDir \ 的四倍。
但是对于测试用例1到4,第二个输出行只是 C:\ Temp \ 而不是 C:\ Temp \ TestDir \ 。
现在使用cd ..
将工作目录更改为 C:\ Temp 并运行 PathTest.bat ,如下所示:
"TestDir\PathTest.bat"
".\TestDir\PathTest.bat"
"\Temp\TestDir\PathTest.bat"
"C:\Temp\TestDir\PathTest.bat"
测试用例1和2的第二个输出结果是 C:\ TestDir \ ,根本不存在。
启动没有双引号的批处理文件会为所有4个测试用例生成正确的输出。
这清楚地表明行为是由错误引起的。
每当批处理文件以双引号启动并且启动时相对于当前工作目录的路径时,%~dp0
在批处理执行期间更改当前工作目录时获取批处理文件的路径是不可靠的。 / p>
此错误也会根据Windows shell bug with how %~dp0 is resolved向Microsoft报告。
可以通过在更改工作目录之前将批处理文件的路径立即分配给环境变量(如上面的代码所示)来解决此错误。
然后在需要批处理文件路径的地方引用此变量的值,并在必要时使用双引号。类似%BatchPath%
的内容总是更易于阅读%~dp0
。
另一种解决方法是使用完整路径启动批处理文件(并使用文件扩展名),使用双引号作为类Process
。
答案 5 :(得分:-3)
您的ProcessStart调用的批处理中的每个新行都被独立地视为新的cmd命令。
例如,如果您尝试这样:
echo %~dp0 && CD Arvind && echo %~dp0
有效。