批处理:适用于不支持延迟扩展的目录



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

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

set A_PATH=C:Pathtopublish*
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:Pathtopublish01
I:C:Pathtopublish02
I:C:Pathtopublish03

这让我怀疑延迟扩展与 for 循环配合不好,因为这是我在 stackoverflow 上找到的其他解决方案的唯一明显区别,例如 https://stackoverflow.com/a/45104510/2466431

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

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

循环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 ( ... )来语法正确。

但两者都可能对您有用,因为:

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

尽管如此,有一种方法可以解决上述dir问题,即附加并尝试列出给定目录的内容;如果给出通配符,dir返回错误;如果给出了专用目录名称,但它不存在,dir也返回错误;可以应用条件执行来利用该行为:

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%在最后一个 path 元素中包含通配符,则第一个dir命令失败(错误:The filename, directory name, or volume label syntax is incorrect.),因此跳过echo命令,但执行第二个dir命令,该命令列出所有匹配的目录名称,按创建日期/时间排序(最新的在前);

第一个dir命令返回的列表以及任何错误消息都可以使用重定向来禁止显示,因此剩余的输出是按echo或第二个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:Pathtopublish*"
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选项/T:C以考虑创建日期/时间,而不是上次修改的日期/时间; 如果要考虑后者,只需将其删除即可;
  • 添加了cd /D"!%SEARCH_PATH%!.." ||exit/B 1来更改为父目录,因为后面使用的dir /B命令只返回目录或文件名而不是完整路径;这也是在%%~nxI中使用~nx修饰符的原因,所以值来自dir /B还是echo都没有更多的区别;exit /B 1部分确保跳过剩余的代码,以防找不到/访问父目录;
  • ~-修饰符用于%%~J以确保不带引号的目录名称(请记住,我在echo命令行中加上了引号);
  • 2> nul已被删除(从最外层循环主体的末尾),以不显示一般错误消息;

最新更新