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