使用%~dp0引用的批处理文件路径有时会在更改目录时发生更改的原因是什么?

sun*_*ays 21 c# batch-file

我有一个包含以下内容的批处理文件:

echo %~dp0
CD Arvind
echo %~dp0
Run Code Online (Sandbox Code Playgroud)

即使更改目录值后%~dp0也是如此.但是,如果我从CSharp程序运行此批处理文件,则CD%~dp0后更改的值.它现在指向新目录.以下是我使用的代码:

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();
Run Code Online (Sandbox Code Playgroud)

为什么以不同方式执行相同脚本的输出存在差异?

我在这里想念一下吗?

MC *_* ND 12

这个问题开始讨论这一点,并进行了一些测试以确定原因.所以,在内部调试之后cmd.exe...(这是针对32位Windows XP cmd.exe但由于行为在较新的系统版本上是一致的,可能使用相同或类似的代码)

在杰布的答案中说明了这一点

It's a problem with the quotes and %~0.
cmd.exe handles %~0 in a special way
Run Code Online (Sandbox Code Playgroud)

杰布是对的.

在正在运行的批处理文件的当前上下文中,有一个对当前批处理文件的引用,一个"变量"包含正在运行的批处理文件的完整路径和文件名.

当访问变量时,从可用变量列表中检索其值,但如果请求的变量是%0,并且已经请求(~使用)某个修饰符,则使用正在运行的批处理引用"变量"中的数据.

但是使用对~变量有另一种影响.如果引用了该值,则会删除引号.这里代码中有一个错误.它的编码类似于(这里简化汇编程序为伪代码)

value = varList[varName]
if (value && value[0] == quote ){
    value = unquote(value)
} else if (varName == '0') {
    value = batchFullName
}
Run Code Online (Sandbox Code Playgroud)

是的,这意味着当引用批处理文件时,执行第一部分if并且不使用批处理文件的完整引用,而是检索的值是用于在调用批处理文件时引用批处理文件的字符串.

那么会发生什么?如果在调用批处理文件时使用了完整路径,那么就没有问题了.但是,如果调用中未使用完整路径,则需要检索批处理调用中不存在的路径中的任何元素.该检索假设相对路径.

一个简单的批处理文件(test.cmd)

@echo off
echo %~f0
Run Code Online (Sandbox Code Playgroud)

当使用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
Run Code Online (Sandbox Code Playgroud)

这将回显控制当前批处理文件的完整路径,与您调用文件的方式无关,有或没有引号.

注意:为什么它有效?为什么%~f0子程序中的引用返回不同的值?从子程序内部访问的数据不一样.当call被执行时,一个新的批文件上下文在内存中创建,并且内部"可变的"是用来初始化该上下文.


Han*_*ant 5

我会试着解释为什么这种行为如此奇怪.一个相当技术性和冗长的故事,我会尽力使它凝聚.这个问题的出发点是:

   ProcessInfo.UseShellExecute = false;
Run Code Online (Sandbox Code Playgroud)

如果省略此语句或指定true,它将按预期工作,您将看到.

Windows提供了两种启动程序的基本方法,ShellExecuteEx()和CreateProcess().UseShellExecute属性在这两者之间进行选择.前者是"智能和友好"的版本,它对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,它也使用了这个论点.自从它使用的路径以来,你的案例中的flounders就是"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);
Run Code Online (Sandbox Code Playgroud)

而且你会看到%~dp0现在按照你的预期扩展.它正在使用path而不是当前目录.


LaG*_*ere -3

ProcessStart 调用的批处理中的每个新行都被独立地视为一个新的 cmd 命令。

例如,如果您像这样尝试一下:

echo %~dp0 && CD Arvind && echo %~dp0
Run Code Online (Sandbox Code Playgroud)

有用。