批次:适用于无法进行延迟扩展的目录

时间:2019-08-20 20:09:18

标签: windows batch-file for-loop cmd

我有一个CI系统,该系统定期发布新文件。 在脚本中,我想获取几个(单独发布)文件的最新版本。常规文件结构如下:C:\Path\to\publish\<token>\<flavor>\file。每个发布文件中有3个可变部分。 (而且在某些情况下,所有3个都可以包含通配符)

我当前拥有的设置利用延迟扩展使其可配置:

set A_PATH=C:\Path\to\publish\*
set A_SUBDIR=<flavor>
set A_NAME=file_*.txt

call :searchFile A
goto :logicUsingA

:searchFile
SETLOCAL ENABLEDELAYEDEXPANSION
set SEARCH_PATH=%~1_PATH
set SEARCH_NAME=%~1_NAME
set SEARCH_SUBDIR=%~1_SUBDIR
FOR /D %%I IN ('DIR !%SEARCH_PATH%! /O:-D /B') do (
 echo I:%%I
 FOR %%J IN (!%SEARCH_SUBDIR%!) do (
  echo J:%%J
  FOR /F "delims=" %%K IN ('DIR %%I\%%J\!%SEARCH_NAME%! /A:-D /O:-D /B') do (
   echo K:%%K
   ENDLOCAL
   set "%~1=%%I\%%J\%%K"
   GOTO :EOF
  )
 )
) 2> nul

该系统几乎完成了它需要做的事情,即基于配置选择一些文件并用它们填充变量。 不幸的是,我们发现执行此脚本出了点问题。

FOR /D %%I IN ('DIR !%SEARCH_PATH%! /O:-D /B')应该按照最新的顺序对目录进行排序。但是,其下面的打印将打印:(假设001在002之前创建的...)

I:'DIR
I:C:\Path\to\publish\001
I:C:\Path\to\publish\002
I:C:\Path\to\publish\003

这使我怀疑延迟扩展不能与for循环配合使用,因为这是我在stackoverflow上发现的其他解决方案(例如https://stackoverflow.com/a/45104510/2466431

)唯一明显的区别

是否可以指示for循环执行此dir命令,将其视为项目列表?

编辑:该代码试图实现的是bash单行代码的Windows批处理变体:ls -t C/Path/to/publish/*/<flavor>/file_*.txt

1 个答案:

答案 0 :(得分:1)

循环for /D %%I in ('dir !%SEARCH_PATH%! /O:-D /B') do ( ... )是错误的,因为在我们的情况下,必须使用for /F来捕获和解析dir之类的命令的输出。因此,您可以使用for /D %%I in ("!%SEARCH_PATH%!") do ( ... )for /F "delims=" %%I in ('dir "!%SEARCH_PATH%!" /O:-D /B') do ( ... )来确保语法正确。

但这两种都不会对您有用,因为:

    除非存在wildcards,否则
  • for /D不会访问文件系统;
  • for /D不提供排序选项(它从文件系统获取目录时返回目录);
  • dir,给出目录名称后,列出其内容,而不仅仅是返回其名称;

尽管如此,用dir可以解决上述问题,即追加\并尝试列出给定目录的内容;如果给出了通配符,则dir返回错误;如果给出了专用的目录名但它不存在,则dir也会返回错误; conditional execution可以用于利用该行为:

dir /B /A:D "%SomeDir%\" && (echo "%SomeDir%") || dir /B /A:D /O:-D /T:C "SomeDir%"

这提供了以下结果:

  • 如果%SomeDir%指向现有的专用目录,则第一个dir命令会列出其内容,因此它会成功执行,因此将执行echo命令,并返回(引用的)变量内容,但第二个dir命令被跳过; (在这里echo字符串的引号是为了保护空格或其他特殊字符;尽管周围的引号""以后也很容易删除,但这没问题;)
  • 如果%SomeDir%指向找不到的专用目录,则第一个dir命令将失败(错误:The system cannot find the file specified.),因此将跳过echo命令,但是执行第二个dir命令,该命令也会失败(错误:File Not Found);
  • 如果%SomeDir%在最后一个路径元素中包含通配符,则第一个dir命令失败(错误:The filename, directory name, or volume label syntax is incorrect.),因此echo命令被跳过,但是执行第二个dir命令,该命令列出所有匹配的目录名称,并按创建日期/时间(最新的)排序;

可以使用redirection禁止第一个dir命令返回的列表以及任何错误消息,因此其余输出为echo或第二个{{ 1}}:

dir

现在可以将其应用于您的脚本:

> nul 2>&1 dir /B /A:D "%SomeDir%\" && (echo "%SomeDir%") || 2> nul dir /B /A:D /O:-D /T:C "SomeDir%"

在枚举文件而不是目录的最内层循环中,我应用了相同的技术,但是我跳过了@echo off set "A_PATH=C:\Path\to\publish\*" set "A_SUBDIR=<flavor>" set "A_NAME=file_*.txt" call :searchFile A echo A:%A% rem goto :logicUsingA goto :EOF :searchFile set "%~1=" setlocal EnableDelayedExpansion set "SEARCH_PATH=%~1_PATH" set "SEARCH_NAME=%~1_NAME" set "SEARCH_SUBDIR=%~1_SUBDIR" cd /D "!%SEARCH_PATH%!\.." || exit /B 1 for /F "delims=" %%I in (' ^> nul 2^>^&1 dir /B /A:D "!%SEARCH_PATH%!\" ^ ^&^& ^(echo "!%SEARCH_PATH%!"^) ^ ^|^| 2^> nul dir /B /A:D /O:-D /T:C "!%SEARCH_PATH%!" ') do ( echo I:%%~nxI for /F "delims=" %%J in (' ^> nul 2^>^&1 dir /B /A:D "%%~nxI\!%SEARCH_SUBDIR%!\" ^ ^&^& ^(echo "!%SEARCH_SUBDIR%!"^) ^ ^|^| 2^> nul dir /B /A:D /O:-D /T:C "%%~nxI\!%SEARCH_SUBDIR%!" ') do ( echo J:%%~J for /F "delims=" %%K in (' ^> nul 2^>^&1 dir /B /A:-D "%%~nxI\%%~J\!%SEARCH_NAME%!\" ^ ^|^| 2^> nul dir /B /A:-D /O:-D /T:C "%%~nxI\%%~J\!%SEARCH_NAME%!" ') do ( echo K:%%K endlocal set "%~1=%CD%\%%~nxI\%%~J\%%K" goto :EOF ) ) ) goto :EOF 命令,因为它永远都不会被执行。我在此处保留两个echo命令的唯一原因是,当您指定某个文件名(不使用通配符)但实际上存在一个具有该名称的目录(然后是一个dir意外返回其内容)。

还有一些其他更改:

  • 首先清除变量dir(在子例程%~1中,以防万一根本找不到文件);
  • 引用的:searchFile语法始终使用(set);
  • 现在引用所有路径,以免出现空格或其他特殊字符的麻烦;
  • 添加了set "VAR=Value"选项dir,以考虑创建日期/时间,而不是最后修改的日期/时间;如果要考虑后者,只需将其删除;
  • 已添加
  • cd /D /T:C exit "!%SEARCH_PATH%!\.." ||以更改到父目录,因为后来使用的/B 1命令仅返回目录或文件名,但是没有完整的路径;这也是在dir /B中使用~nx-modifier的原因,因此该值来自%%~nxI还是dir /B不再有任何区别; echo部分确保在找不到/访问父目录的情况下跳过其余代码;
  • ~-modifiers用于exit /B 1中,以确保目录名称未加引号(请记住,我在%%~J命令行中加了引号);
  • echo已被删除(从最外面的循环的主体末端开始),通常不会抑制错误消息;