C# %dp0引用的批处理文件路径在更改目录时有时发生更改的原因是什么?
我有一个包含以下内容的批处理文件:C# %dp0引用的批处理文件路径在更改目录时有时发生更改的原因是什么?,c#,batch-file,C#,Batch File,我有一个包含以下内容的批处理文件: echo %~dp0 CD Arvind echo %~dp0 即使更改了%~dp0的目录值,其值也相同。 但是,如果我从CSharp程序运行此批处理文件,%~dp0的值在CD之后发生变化。它现在指向新目录。以下是我使用的代码: Directory.SetCurrentDirectory(//Dir where batch file resides); ProcessStartInfo ProcessInfo; Process process = new P
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();
为什么用不同的方法执行同一个脚本的输出会有差异
我在这里遗漏了什么吗?由ProcessStart调用的批处理中的每一行都被独立地视为一个新的cmd命令 例如,如果您这样尝试:
echo %~dp0 && CD Arvind && echo %~dp0
它起作用了。乔伊的建议起了作用。
只要更换
ProcessInfo = new ProcessStartInfo("mybatfile.bat");
与
成功了。引号和
%~0有问题
cmd.exe以特殊方式处理%~0
(而不是%~1
)。
它检查%0
是否为相对文件名,然后在其前面加上start目录
如果可以找到一个文件,它将使用此组合,否则它将使用实际目录作为其前缀。
但是,当名称以引号开头时,似乎无法在目录前删除引号
这就是cmd/cmybatch.bat
工作的原因,因为myBatch.bat
调用时不带引号。
您还可以使用完整的限定路径启动批处理,然后它也可以工作
或者在更改目录之前,在批处理中保存完整路径
一个小的test.bat
可以演示cmd.exe的问题
@echo off
setlocal
echo %~fx0 %~fx1
cd ..
echo %~fx0 %~fx1
通过(在C:\temp中)调用它
输出应该是
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的全名
EDIT:FixIt,即使%~0
有引号,也要检索全名
存在一个函数调用的变通方法
@echo off
echo This can be wrong %~f0
call :getCorrectName
exit /b
:getCorrectName
echo Here the value is correct %~f0
exit /b
我将试图解释为什么这种行为如此奇怪。这是一个相当技术性和冗长的故事,我会尽量把它浓缩。这个问题的出发点是:
ProcessInfo.UseShellExecute = false;
您将看到,如果省略此语句或将其赋值为true,则它将按预期工作
Windows提供了两种启动程序的基本方法,ShellExecuteEx()和CreateProcess()。UseShellExecute属性在这两者之间进行选择。前者是“智能且友好”的版本,例如,它非常了解shell的工作方式。这就是为什么你可以,比如说,把路径传递给像“foo.doc”这样的任意文件。它知道如何查找.doc文件的文件关联,并找到知道如何打开foo.doc的.exe文件
是低级的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
现在按预期扩展。它正在使用路径
而不是当前目录。命令行解释器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个测试用例,输出是预期的四倍
测试用例7和8使用相对于当前驱动器根目录的路径启动批处理文件
现在让我们看一下与前面相同的操作的结果,但是使用双引号
@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
cd /D C:\Temp\TestDir
It's a problem with the quotes and %~0.
cmd.exe handles %~0 in a special way
value = varList[varName]
if (value && value[0] == quote ){
value = unquote(value)
} else if (varName == '0') {
value = batchFullName
}
@echo off
echo %~f0
@echo off
setlocal enableextensions disabledelayedexpansion
call :getCurrentBatch batch
echo %batch%
exit /b
:getCurrentBatch variableName
set "%~1=%~f0"
goto :eof