我有一个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
答案 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 ( ... )
来确保语法正确。
但这两种都不会对您有用,因为:
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
已被删除(从最外面的循环的主体末端开始),通常不会抑制错误消息;