繁体   English   中英

具有延迟扩展的for循环内的文件名数组(Windows批处理文件)

[英]Array of file names inside a for loop with delayed expansion (windows batch file)

背景

我正在尝试将我的硬盘中的文件同步到我的MEGA帐户( https://mega.nz/ ),这是一个免费的加密在线数据存储服务。 不幸的是,MEGA还没有发布任何Windows命令行实用程序,他们当前的Windows同步客户端( https://mega.nz/#sync )需要做很多工作。 我尝试上传时,它删除了所有本地文件。 我能够在GitHub上找到一些名为Megatools( https://megatools.megous.com/ )的第三方实用程序,它们的功能严重不足。 例如:复制功能不允许您选择使用硬盘驱动器中的较新版本覆盖MEGA服务器上的文件。 因此,我试图通过编写Windows批处理文件程序来扩展Megatools。

Windows批处理同步程序

该程序非常简单:它使用FORFILEShttp://ss64.com/nt/forfiles.html )扫描本地同步文件夹,然后比较文件名,相对路径,大小和修改日期等属性与列表远程文件。 使用Megatools megals.exe函数快速填充远程文件。 属性存储在数组中。 请注意,我知道Windows批处理文件不会像其他编程语言那样处理数组,这对编译器来说只是另一个变量。

问题

因为FORFILES作为基本for循环,我使用索引数组来存储信息,所以我需要启用Delayed Expansion以使索引FORFILES运行。 我真的不太了解延迟扩展。 我已经尝试过很多研究,但这对我没有任何意义。 由于Delayed Expansion的语法涉及使用!var!封装变量!var! 而不是%var% ,我的文件名可能包含! 符号,我不知道该怎么做。

我研究了这个问题,显然解决方案是使用SETLOCAL DisableDelayedExpansionENDLOCAL语句临时禁用延迟扩展,但这涉及将使用SET语句创建的任何变量的范围限制为SETLOCALENDLOCAL之间的行。

我发现这个问题的一个解决方案是使用& SET newvar=%oldvar%追加ENDLOCAL ,但每当我尝试运行程序时newvar仍为空!

代码(删节)

@ECHO off
SETLOCAL EnableDelayedExpansion

REM ...
REM (Parameter processing code, irrelevant)
REM ...

SET localpathempty=UNSET
SET numlocal=UNSET
CALL:islocalpathempty
IF "%localpathempty%"=="FALSE" (
    ECHO Items found in local directory.  Gathering file names...
    SETLOCAL DisableDelayedExpansion
    FOR /F "tokens=*" %%a IN ('FORFILES /S /P "%localpath%." /C "cmd /c echo @FILE"') DO (
        SET /A "j+=1"
        SET "line=%%~a"
        SETLOCAL EnableDelayedExpansion
        SET localfilename[!j!]=!line!
        ECHO Array Index inside 'LOCAL': !j!
        ENDLOCAL
        ECHO Array Index outside 'LOCAL': %j%
    )
    ENDLOCAL & SET numlocal=%j%
)
IF "%localpathempty%"=="TRUE" (
    ECHO WARNING: LOCAL PATH %localpath% IS EMPTY.  PROCESSING SKIPPED.
)
ECHO Final Index: %j%
ECHO Total Items: %numlocal%

REM ...
REM (Further processing of local files and remote files)
REM ...

ENDLOCAL

GOTO :eof

REM ...
REM (functions)
REM ...

结果

Items found in local directory.  Gathering file names...
Array Index inside 'LOCAL': 1
Array Index outside 'LOCAL': 
Array Index inside 'LOCAL': 2
Array Index outside 'LOCAL': 
Array Index inside 'LOCAL': 3
Array Index outside 'LOCAL': 
Array Index inside 'LOCAL': 4
Array Index outside 'LOCAL': 
Array Index inside 'LOCAL': 5
Array Index outside 'LOCAL': 
Array Index inside 'LOCAL': 6
Array Index outside 'LOCAL': 
Array Index inside 'LOCAL': 7
Array Index outside 'LOCAL': 
Array Index inside 'LOCAL': 8
Array Index outside 'LOCAL': 
Array Index inside 'LOCAL': 9
Array Index outside 'LOCAL': 
Array Index inside 'LOCAL': 10
Array Index outside 'LOCAL': 
Array Index inside 'LOCAL': 11
Array Index outside 'LOCAL': 
Array Index inside 'LOCAL': 12
Array Index outside 'LOCAL': 
Array Index inside 'LOCAL': 13
Array Index outside 'LOCAL': 
Final Index: 
Total Items: 

如您所见,在第二个SETLOCAL EnableDelayedExpansion语句之前设置的索引不能在该代码块中存活。 我需要能够记录最终索引以及文件名。 我没有尝试通过第二个SETLOCAL EnableDelayedExpansionENDLOCAL语句传递文件名,因为索引甚至没有生存! 任何帮助将非常感谢这个问题。 我意识到我的语法可能有一些问题,但我已经尽了最大努力。 索引消失对我来说是一个巨大的谜。

PS:如果你想知道为什么我开始启用延迟扩展而不是禁用,原因是因为后来发生的绝大多数代码(此处未显示)要求启用它。

在完全处理命令行之前,首先将用% name of variable %引用的环境变量的字符串值插入命令行。

所以就行了

echo Path extensions are: %PATHEXT%

Windows命令处理器首先用环境变量PATHEXT的字符串值替换%PATHEXT% ,然后THEN解释调用内部函数echo的行,该函数将不再包含%PATHEXT%的字符串写入标准流标准输出

Windows命令处理器将以(以#结尾)开始的块解释为跨越多行的命令行。 因此Windows命令处理器在查找(正在开始一个块搜索匹配) 然后,当前行和此块中所有出现的% name变量 %都被引用变量的当前字符串值替换。

如果在这样的块内定义或修改变量,则在处理行之前的这种变量扩展通常会产生意外行为。 在执行块中的任何命令之前,整个块一旦被命令处理器解析,因此在执行块的行时不再考虑处理行期间的环境变量的更新。

这就是为什么只要在以(以#结尾)开始的块中定义或修改变量就需要延迟扩展的原因,并且当前变量值也在该块内进行评估。

注意:循环变量不是环境变量。 循环变量总是“扩展延迟”。

在命令提示符窗口中从批处理文件的第一行中删除了@ECHO OFF的批处理文件时,可以很容易地看到变量 %% 名称替换变量的当前字符串值,因为Windows命令处理器输出的是真正的在每一行处理。

但是setlocal EnableDelayedExpansion不仅仅支持延迟扩展! 现在任何地方都有特殊意义(包括echo Hello! ),它还会生成当前环境表的副本,并推送延迟扩展的当前状态,扩展的使用状态以及堆栈上的当前目录。 稍后当显式或隐式调用endlocal时,将丢弃当前环境表,并从堆栈中恢复延迟扩展的状态,扩展的使用状态和当前目录。

换句话说, setlocal始终是一个完整的本地环境。

使用块来避免混淆的简单解决方案通常是使用setlocal EnableDelayedExpansion在批处理文件的顶部启用延迟扩展,使用endlocal显式或隐式地恢复批处理文件底部的先前环境,以及引用环境变量(不是循环变量或参数)传递给批处理文件或子程序)使用! ...... ! 而不是% ... % 启用延迟扩展功能始终是安全的! ...... ! 即使引用的变量未在块中修改,也可以代替% ... %

但是,通过使用GOTOCALL ,可以始终在不使用块的情况下编写批处理文件。 因此,经常可以编写批处理文件而无需使用延迟扩展。

以下是您不使用块重写的代码。

@ECHO off
REM ...
REM (Parameter processing code, irrelevant)
REM ...

SET LocalPathEmpty=UNSET
SET NumLocal=UNSET
SET FileIndex=0
CALL :IsLocalPathEmpty

IF "%LocalPathEmpty%"=="TRUE"  GOTO NoLocalPath
IF "%LocalPathEmpty%"=="FALSE" GOTO ProcessFiles
REM There is something wrong with subroutine IsLocalPathEmpty.
GOTO :EOF

:ProcessFiles
ECHO Items found in local directory.  Gathering file names...
FOR /R "%LocalPath%" %%a IN (*) DO CALL :AddLocalFile "%%~a"
GOTO OutputSummary

:NoLocalPath
ECHO WARNING: LOCAL PATH %LocalPath% IS EMPTY. PROCESSING SKIPPED.

:OutputSummary
ECHO Final Index: %FileIndex%
ECHO Total Items: %NumLocal%

REM ...
REM (Further processing of local files and remote files)
REM ...

GOTO :EOF

REM ...
REM (functions)
REM ...

:AddLocalFile
SET /A FileIndex+=1
SET "LocalFileName[%FileIndex%]=%~1"
EXIT /B

请注意,我无法在语法错误上测试此代码,因为它不可执行。

set /? 在命令提示符窗口内执行,输出多个帮助页面,解释IF和FOR块上正常和延迟环境变量扩展的差异。

另请参阅批量回复URL的答案以及为什么我的cd%myVar%被忽略?

我也通过一个简单的for循环替换了forfiles for因为这个命令也可以递归地列出指定目录中的所有文件。 运行for /? 在命令提示符窗口中获取有关for /R语法的帮助和详细信息。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM