我正在尝试将文件从我的硬盘同步到我的MEGA帐户(https://mega.nz/),这是一个免费的加密在线数据存储服务。不幸的是,MEGA尚未发布任何Windows命令行实用程序,它们当前的Windows同步客户端(https://mega.nz/#sync)需要大量工作。我尝试上传时,它删除了所有本地文件。我能够在GitHub上找到一些名为Megatools(https://megatools.megous.com/)的第三方实用程序,它们的功能严重不足。例如:复制功能不允许您选择使用硬盘驱动器中的较新版本覆盖MEGA服务器上的文件。因此,我试图通过编写Windows批处理文件程序来扩展Megatools。
程序非常简单:它使用FORFILES
(http://ss64.com/nt/forfiles.html)扫描本地同步文件夹,然后根据远程列表比较文件名,相对路径,大小和修改日期等属性文件。使用Megatools megals.exe
函数快速填充远程文件。属性存储在数组中。请注意,我知道Windows批处理文件不会像其他编程语言那样处理数组,这对编译器来说只是另一个变量。
因为FORFILES
用作基本for循环,并且我使用索引数组来存储信息,所以我需要启用Delayed Expansion以使索引按需运行。我真的不太了解延迟扩展。我已经尝试过很多研究,但这对我没有任何意义。由于延迟扩展的语法涉及使用!var!
而不是%var%
封装变量,并且我的文件名可能包含!
符号,因此我不知道该怎么做。
我研究了这个问题,显然解决方案是使用SETLOCAL DisableDelayedExpansion
和ENDLOCAL
语句暂时禁用延迟扩展,但这涉及将使用SET
语句创建的任何变量的范围限制为SETLOCAL
和ENDLOCAL
之间的行。
我发现此问题的一个解决方案是在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 EnableDelayedExpansion
和ENDLOCAL
语句传递文件名,因为索引甚至没有生存!任何帮助将非常感谢这个问题。我意识到我的语法可能有一些问题,但我已经尽了最大努力。索引消失对我来说是一个巨大的谜。
PS:如果您想知道为什么我开始启用延迟扩展而不是禁用,原因是因为后来发生的绝大多数代码(此处未显示)要求启用它。
答案 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 Batch和why is my cd %myVar% being ignored?
上的答案我还用简单的set /?
循环替换forfiles
,因为此命令还可以递归地列出指定目录中的所有文件。在命令提示符窗口中运行for
以获取有关for /?
语法的帮助和详细信息。