我有一个定期发布新文件的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:Pathtopublish 01
I:C:Pathtopublish 02
I:C:Pathtopublish 03
这让我怀疑延迟扩展与 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
已被删除(从最外层循环主体的末尾),以不显示一般错误消息;