为什么"%~fI"参数扩展能够“访问”#34;不存在的驱动器?

时间:2016-12-21 15:55:53

标签: windows batch-file cmd

我使用以下命令:

C:\>for %I in (a: b: c: ">:" "&:") do @rem %~fI
C:\>pushd c:
C:\>set "

和输出:

  

=&安培;:=&安培;:\

     

=>:=>:\

     

= A:= A:\

     

= B:= B:\

     

= C:= C:\

     

...

由于=Drive:变量存储了最后访问的路径相应的驱动器,看起来%~fI扩展以某种方式访问​​不存在的驱动器(这是不可能的)。 (所有参数扩展都会创建这样的变量)

4 个答案:

答案 0 :(得分:3)

for可替换参数中使用修饰符来请求路径元素时,for命令(以及检索正在读取的变量内容的函数)使用{{3函数使输入字符串适应可以处理的内容。此API函数(以及此API调用的某些OS基本函数)在请求相对路径时生成指示的行为。你可以测试这个c代码(对不起,只是一个快速的代码测试),用ex调用可执行文件。 ;:作为第一个参数。

#define _WIN32_WINNT   0x0500
#include <windows.h>
#include <stdio.h>

#define BUFFER_SIZE 4096

int main(int argc, char **argv){

    char buffer[BUFFER_SIZE];
    DWORD ret;

    LPTSTR lpszVariable; 
    LPTCH lpvEnv; 

    if (argc != 2) return 1;

    if (0 == GetFullPathName( argv[1], BUFFER_SIZE, buffer, NULL )){
        printf ("GetFullPathName failed (%d)\n", GetLastError());
        return 2;
    }

    printf("current active directory: %s\r\n", buffer );

    if (NULL == (lpvEnv = GetEnvironmentStrings())) { 
        printf("GetEnvironmentStrings failed (%d)\n", GetLastError()); 
        return 3;
    }

    lpszVariable = (LPTSTR) lpvEnv;
    while (*lpszVariable) {
        if (lpszVariable[0]== '=') printf("%s\n", lpszVariable);
        lpszVariable += lstrlen(lpszVariable) + 1;
    }
    FreeEnvironmentStrings(lpvEnv);
    return 0;
}

获得类似

的内容
D:\>test ;:
current active directory: ;:\
=;:=;:\
=C:=C:\Windows\System32
=D:=D:\
=ExitCode=00000000

已编辑 2016/12/23

这适用于Windows 10,但由于Windows 7的行为相同,它应该共享相同或相似的代码。

环境字符串到控制台的输出由DisplayEnvVariable函数处理。在较旧的Windows版本中(已检查并且XP以这种方式执行),此函数调用GetEnvironmentStrings来检索值,但现在(检查并在Vista中它已更改)使用指向内存区域的指针。不知怎的(抱歉,此刻我不能给这个问题更多的时间),它指向一个非更新的环境副本(在这种情况下,更新不是由cmd命令生成的,而是从基础{{ 1}}解析当前驱动路径时调用的函数,生成观察到的行为。

没有必要执行Rtlpushd命令,对环境或任何进程创建的任何更改都将导致更新指针。

cd

您可以使用简单的@echo off setlocal enableextensions disabledelayedexpansion echo = before ------------------------------ set " for %%a in ( ";:" ) do rem %%~fa echo = after ------------------------------- set " <nul >nul more echo = after more -------------------------- set " 替换more行,以获得相同的结果

答案 1 :(得分:2)

Windows命令解释程序尝试通过处理%~fI来使用像QueryDirectory这样的扩展名来获取存储介质上文件或目录的真实名称,因为在使用Sysinternals Process Monitor时可以看到它。但循环变量I只保存set中定义的字符串值。

从程序员的角度来看,命令解释器应该如何处理以下代码?

@echo off
pushd "%SystemRoot%\Temp"
del #abcdefghi.tmp 2>nul
for %%I in (#abcdefghi.tmp) do echo %%~fI
popd

系统进程的临时文件目录中很可能没有名称为#abcdefghi.tmp的文件。但是输出仍然是

C:\Windows\Temp\#abcdefghi.tmp

Windows命令解释程序必须构建一个字符串,因为批处理代码需要一个字符串。它不能用任何内容替换%%~fI或用错误消息文本替换,因为这肯定会导致在批处理文件中进一步处理命令行时出现未定义的行为。

完全退出批处理也不是Windows命令解释程序的选项,因为 FOR 循环可用于检查文件是否存在。

因此,Windows命令解释器尽可能从当前目录和当前循环变量字符串构建一个带路径的有效文件名,或只是文件路径,或只是文件名,...独立于文件/目录的存在或创建字符串的有效性。

批处理代码的命令行用户和编写者必须检查扩展循环变量引用的有效性或存在而不是Windows命令解释器。

答案 2 :(得分:2)

我认为有两件事有所贡献:

  1. 驱动器号实际上可以是除空格/\以外的每个字符。查看subst命令,该命令例如也接受&(虽然subst未列出):

    C:\>subst X: C:\
    
    C:\>subst ^&: C:\
    
    C:\>subst
    X:\: => C:\
    
    C:\>X:
    
    X:\>^&:
    
    &:\>        
    
  2. for除非确实需要,否则不会访问文件系统,在以下情况下会这样:

    • 通配符(如?*)用于集合中(文件系统需要立即通过for命令访问);
    • for引用(%I)扩展需要来自文件系统的信息:

      • 对于修饰符~s~a~t~z~$ENV:,当然需要来自文件系统的信息;
      • 用于修饰符~n~x~p,以及~f的相应部分,它们只是~dpnx,文件系统是访问以保存案例(如果路径不存在,则保留原始案例);
      • 对于修饰符~n~x~p~d以及~f,如果是相对路径,则需要访问文件系统提供,指定或不指定专用驱动器(例如,abc\defD:dataP:),因为当前工作目录路径(在给定驱动器的情况下)需要确定的;

    因此,~d修饰符以及~f的相应部分由纯字符串操作处理,只要不根据上述条件访问文件系统。 / p>

    只需尝试原始代码,但使用绝对路径,例如for %I in (a:\ b:\ c:\ ">:\" "&:\") do @rem %~fI等,您会发现没有相应的=Drive:变量。

  3. 摘要

    驱动器字母几乎可以是任何字符(请参阅subst)。 只要for访问文件系统以搜索驱动器和路径,访问的驱动器就会记录在=Drive:变量中。

答案 3 :(得分:1)

IMO驱动器并未真正访问,但在早期阶段进行了解析 一个简单的for循环可用于解析不存在的drives \ pathes \ files

> cd
C:\Users\LotPings

> for %A in (\nonexistent) do @echo %~pnxA
\nonexistent

> for %A in (\nonexistent\a.b) do @echo %~pnxA
\nonexistent\a.b

> for %A in (\nonexistent\a.b) do @echo %~nxA
a.b

> for %A in (\nonexistent\a.b) do @echo %~fA
C:\nonexistent\a.b

> for %A in (^>\nonexistent\a.b) do @echo %~zA
ECHO ist eingeschaltet (ON).

> for %A in (^>\nonexistent\a.b) do @echo %~aA
ECHO ist eingeschaltet (ON).

> for %A in (^>\nonexistent\a.b) do @echo %~fA
C:\Users\LotPings\.\nonexistent\a.b

最后一个非常有趣