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

时间:2016-05-06 19:54:26

标签: arrays windows batch-file for-loop special-characters

背景

我正在尝试将文件从我的硬盘同步到我的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以使索引按需运行。我真的不太了解延迟扩展。我已经尝试过很多研究,但这对我没有任何意义。由于延迟扩展的语法涉及使用!var!而不是%var%封装变量,并且我的文件名可能包含!符号,因此我不知道该怎么做。

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

我发现此问题的一个解决方案是在ENDLOCAL附加& SET newvar=%oldvar%,但只要我尝试运行该程序,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:如果您想知道为什么我开始启用延迟扩展而不是禁用,原因是因为后来发生的绝大多数代码(此处未显示)要求启用它。

1 个答案:

答案 0 :(得分:0)

在处理命令行之前,首先将% 变量名称 %引用的环境变量的字符串值插入命令行。

所以在

一行
echo Path extensions are: %PATHEXT%

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

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

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

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

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

在运行已删除%的批处理文件时,可以很容易地看到变量的当前字符串值替换变量 %的{​​{1}} 名称第一行批处理文件来自命令提示符窗口,因为Windows命令处理器输出每行上真正处理的内容。

@ECHO OFF不只是启用延迟扩展,现在setlocal EnableDelayedExpansion在任何地方都有特殊含义(包括!),它还会复制当前环境表并推送当前状态延迟扩展,扩展的使用状态和堆栈上的当前目录。稍后当显式或隐式调用echo Hello!时,将丢弃当前环境表,并从堆栈中恢复延迟扩展的状态,扩展的使用状态和当前目录。

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

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

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

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

%

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

从命令提示符窗口执行的

@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 输出帮助的多个页面,解释IF和FOR块上正常和延迟环境变量扩展的差异。

另请参阅Echoing a URL in Batchwhy is my cd %myVar% being ignored?

上的答案

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