跟我学CMD实战系列之二 ——数据压缩备份

需求:
每天下班时,将本地硬盘上的projects目录压缩为rar文件,文件名中必须含有当天日期信息,例如projects_20070902.rar.

分析:
1. 压缩可以调用WinRAR的命令行版本rar.exe来完成;
2. 难点是如何生成含有当天日期的文件名,date可以输出当天日期,但如何转换为20070902这种格式需要一定技巧。

实战一:万事开头易
首先要解决的是怎么调用rar.exe来压缩文件。rar.exe在WinRAR安装目录下,复制一个过来即可。使用前可以先用rar /?看看help, 但千万别被这么多参数吓坏,我们只要学会最简单的就行。网上搜搜例子,自己再试验试验,不难写出下面的压缩命令:

代码:
rar a "projects.rar" @backup.lst

backup.lst中是需要备份的目录/文件列表:

backup.lst:

代码:
d:WorkProjects

如果某个目录下的某些文件不需要备份<!--more-->,可以通过exclude.lst文件来排除:

exclude.lst:

代码:
d:WorkProjectsPrivate

压缩命令调整为:

代码:
rar a -x@exclude.lst "projects.rar" @backup.lst

至此,我们已经知道如何调用rar.exe来压缩文件,同时掌握了backup.lst和exclude.lst文件的使用,想要备份什么以及不想备份什么,一切已经尽在掌握中^o^

实战二:我for故我强
接着要解决是需求中的难点:如何生成含有当天日期的文件名。这个问题在稍微高级一点的语言中都不是问题,但在纯CMD命令行下,还真没有方便便捷的法门,我们得来点曲线救国,先date一下:

引用:
lifesinger@bpwang ~# date /T
2007/09/02 日

通过date命令可以输出日期字符串,但怎样才能将这个字符串赋值给一个变量呢?回想我们实战系列一里学会的九阴真经,或许我们可以先用date /T > date.txt将日期输出到文件,再用for来读取:

代码:
@echo off
date /T > date.txt
for /f %%i in (date.txt) do set str=%%i
echo %str%

执行后,字符串已经保存到str变量中。 for真真不愧为九阴真经,那一挥刀之间的潇洒,获取永世的强大……
上面的脚本执行后将输出 2007/09/02,与我们的目标格式 20070902 只一步之遥了。进一步处理对for来说简直牛刀小试也:

代码:
date /T > date.txt
for /f %%i in (date.txt) do set str=%%i
for /f "delims=/ tokens=1,2,3" %%i in ("%str%") do set date_str=%%i%%j%%k

到了黄山脚下,没有不登顶的道理。结合实战一中的rar命令,来个一鼓作气:

代码:
@echo off

rem get date string
date /T > date.txt
for /f %%i in (date.txt) do set str=%%i
for /f "delims=/ tokens=1,2,3" %%i in ("%str%") do set date_str=%%i%%j%%k

rem rar to file
set file_name=projects_%date_str%.rar
rar a -x@exclude.lst "%file_name%" @backup.lst

rem del temp files
del /q /f date.txt

哇,运行上面的脚本,那雪白的字符,像天上的浮云一样漂亮的从窗口飘过。我for故我强,我强故我笑,叹老毛已老,试问天下英雄,谁敢与for争锋?

实战三:天外有天

古人思美女,有个绝佳的描述:“求之不得,寤寐思服,悠哉悠哉,辗转反侧”。上面仗着强大的for经,我们山路十八弯般的看似潇洒其实痛苦的把问题解决了。但那真实的直觉却告诉我:一定存在更简单优雅的方法。辗转反侧之余,且先打个游戏轻松轻松。游戏里mm的画风真不错,小桥流水清风拂面,山泉叮咚灵感飘飘……

游戏归游戏,现实的google江湖中,也有不少赏心悦目之境:

代码:
lifesinger@bpwang ~# echo %date%
2007/09/02 日

原来%date%就可以获取到当前日期,我们前面居然搞出一个date.txt文件读来读去,实在要羞愧死了……
继续观光游览:

代码:
lifesinger@bpwang ~# echo %date:~0,4%
2007
lifesinger@bpwang ~# echo %date:~5,2%
09
lifesinger@bpwang ~# echo %date:~8,2%
02
lifesinger@bpwang ~# echo %date:~0,4%%date:~5,2%%date:~8,2%
20070902

哇,一直以为九阴真经无敌天下,却不料江湖中还有如此强大的功夫。先不论孰强孰弱,我们先来看看这本九阳真经:

引用:
lifesinger@bpwang ~# set /?
Displays, sets, or removes cmd.exe environment variables.

SET [variable=[string]]

variable Specifies the environment-variable name.
string Specifies a series of characters to assign to the variable.

Type SET without parameters to display the current environment variables.

If Command Extensions are enabled SET changes as follows:

SET command invoked with just a variable name, no equal sign or value
will display the value of all variables whose prefix matches the name
given to the SET command. For example:

SET P

would display all variables that begin with the letter 'P'

SET command will set the ERRORLEVEL to 1 if the variable name is not
found in the current environment.

SET command will not allow an equal sign to be part of the name of
a variable.

Two new switches have been added to the SET command:

SET /A expression
SET /P variable=[promptString]

The /A switch specifies that the string to the right of the equal sign
is a numerical expression that is evaluated. The expression evaluator
is pretty simple and supports the following operations, in decreasing
order of precedence:

() - grouping
! ~ - - unary operators
* / % - arithmetic operators
+ - - arithmetic operators
<< >> - logical shift
& - bitwise and
^ - bitwise exclusive or
| - bitwise or
= *= /= %= += -= - assignment
&= ^= |= <<= >>=
, - expression separator

If you use any of the logical or modulus operators, you will need to
enclose the expression string in quotes. Any non-numeric strings in the
expression are treated as environment variable names whose values are
converted to numbers before using them. If an environment variable name
is specified but is not defined in the current environment, then a value
of zero is used. This allows you to do arithmetic with environment
variable values without having to type all those % signs to get their
values. If SET /A is executed from the command line outside of a
command script, then it displays the final value of the expression. The
assignment operator requires an environment variable name to the left of
the assignment operator. Numeric values are decimal numbers, unless
prefixed by 0x for hexadecimal numbers, and 0 for octal numbers.
So 0x12 is the same as 18 is the same as 022. Please note that the octal
notation can be confusing: 08 and 09 are not valid numbers because 8 and
9 are not valid octal digits.

The /P switch allows you to set the value of a variable to a line of input
entered by the user. Displays the specified promptString before reading
the line of input. The promptString can be empty.

Environment variable substitution has been enhanced as follows:

%PATH:str1=str2%

would expand the PATH environment variable, substituting each occurrence
of "str1" in the expanded result with "str2". "str2" can be the empty
string to effectively delete all occurrences of "str1" from the expanded
output. "str1" can begin with an asterisk, in which case it will match
everything from the beginning of the expanded output to the first
occurrence of the remaining portion of str1.

May also specify substrings for an expansion.

%PATH:~10,5%

would expand the PATH environment variable, and then use only the 5
characters that begin at the 11th (offset 10) character of the expanded
result. If the length is not specified, then it defaults to the
remainder of the variable value. If either number (offset or length) is
negative, then the number used is the length of the environment variable
value added to the offset or length specified.

%PATH:~-10%

would extract the last 10 characters of the PATH variable.

%PATH:~0,-2%

would extract all but the last 2 characters of the PATH variable.

Finally, support for delayed environment variable expansion has been
added. This support is always disabled by default, but may be
enabled/disabled via the /V command line switch to CMD.EXE. See CMD /?

Delayed environment variable expansion is useful for getting around
the limitations of the current expansion which happens when a line
of text is read, not when it is executed. The following example
demonstrates the problem with immediate variable expansion:

set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "%VAR%" == "after" @echo If you see this, it worked
)

would never display the message, since the %VAR% in BOTH IF statements
is substituted when the first IF statement is read, since it logically
includes the body of the IF, which is a compound statement. So the
IF inside the compound statement is really comparing "before" with
"after" which will never be equal. Similarly, the following example
will not work as expected:

set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%

in that it will NOT build up a list of files in the current directory,
but instead will just set the LIST variable to the last file found.
Again, this is because the %LIST% is expanded just once when the
FOR statement is read, and at that time the LIST variable is empty.
So the actual FOR loop we are executing is:

for %i in (*) do set LIST= %i

which just keeps setting LIST to the last file found.

Delayed environment variable expansion allows you to use a different
character (the exclamation mark) to expand environment variables at
execution time. If delayed variable expansion is enabled, the above
examples could be written as follows to work as intended:

set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "!VAR!" == "after" @echo If you see this, it worked
)

set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%

If Command Extensions are enabled, then there are several dynamic
environment variables that can be expanded but which don't show up in
the list of variables displayed by SET. These variable values are
computed dynamically each time the value of the variable is expanded.
If the user explicitly defines a variable with one of these names, then
that definition will override the dynamic one described below:

%CD% - expands to the current directory string.

%DATE% - expands to current date using same format as DATE command.

%TIME% - expands to current time using same format as TIME command.

%RANDOM% - expands to a random decimal number between 0 and 32767.

%ERRORLEVEL% - expands to the current ERRORLEVEL value

%CMDEXTVERSION% - expands to the current Command Processor Extensions
version number.

%CMDCMDLINE% - expands to the original command line that invoked the
Command Processor.

简言之,set命令的作用是显示、设置或删除cmd.exe的环境变量。简单对比的话,可以将set理解成vbs里的dim, 或是js里的var. set的一般性用法在上面的经书中已经讲得很详细了。在这里我想引用中国DOS联盟论坛Will Sort的一篇文章来介绍set命令中最难理解的延迟扩展:

引用:
在中国DOS联盟论坛找到一篇Will Sort的解释文章,言简意赅的解释了环境变量延迟扩展,摘录于此,以备查看:

关于环境变量延迟扩展,使用set /?可以查看到部分说明,不过考虑到其粗劣的翻译水平,建议在查看之前,首先chcp 437切换为英文查看原英文说明。鉴于文中已说得十分详尽,而且有数个代码示例,应该不难理解。在此仅略作一些补充。

在许多可见的官方文档中,均将使用一对百分号闭合环境变量以完成对其值的替换行为称之为“扩展(expansion)”,这其实是一个第一方的概念,是从命令解释器的角度进行称谓的,而从我们使用者的角度来看,则可以将它看作是引用(Reference)、调用(Call)或者获取(Get)。

而命令解释器是扩展环境变量的行为大致如下:首先读取命令行的一条完整语句,在进行一些先期的预处理之后,命令被解释执行之前,会对其中用百分号闭合的字符串进行匹配,如果在环境空间中找到了与字符串相匹配的环境变量,则用其值替换掉原字符串及百分号本身,如果未得到匹配,则用一个空串替换,这个过程就是环境变量的“扩展”,它仍然属于命令行的预处理范畴。

而一条“完整的语句”,在NT的命令解释器CMD中被解释为“for if else”等含有语句块的语句和用“& | && ||”等连接起来的复合语句。

因此,当CMD读取for语句时,其后用一对圆扩号闭合的所有语句将一同读取,并完成必要的预处理工作,这其中就包括环境变量的扩展,所以在for中的所有语句执行之前,所有的环境变量都已经被替换为for之前所设定的值,从而成为一个字符串常量,而不再是变量。无论在for中将那些环境变量如何修改,真正受到影响的只是环境变量空间,而非for语句内部。

而为了能够在for语句内部感知环境变量的动态变化,CMD设计了延迟的环境变量扩展特性,也就是说,当CMD读取了一条完整的语句之后,它不会立即执行变量的扩展行为,而会在某个单条语句执行之前再进行扩展,也就是说,这个扩展行为被“延迟”了。

延迟环境变量扩展特性在CMD中缺省是关闭的,开启它的方法目前有两个:一是CMD /vn,它会打开一个新的命令行外壳,在使用exit退出这个外壳之前,扩展特性始终有效,常用于命令行环境中;二是setlocal EnableDelayedExpansion,它会使环境变量的修改限制到局部空间中,在endlocal之后,扩展特性和之前对环境变量的修改将一同消失,常用于批处理语句中。

上面的引用可能有点抽象,我举一个例子来说明延迟环境变量扩展特性:

testSet.cmd:

代码:
@echo off

rem 打开延迟扩展特性
setlocal enabledelayedexpansion

set /a n=0
for /f %%i in ('dir /b') do (
	set /a n=!n!+1
	)

echo 当前目录下的文件和目录总数为:%n%

endlocal

pause

上面的脚本能用来统计目录下的文件和目录总数,如果不打开延迟扩展,在for循环中将无法递增n.

好了,罗马不是一日建成的,如果上面的概念暂时没看懂,可以先记着有这样一本经书来查阅就可以了。我们言归正题,看看用set如何来解决我们最初的问题:

backup.cmd:

代码:
@echo off

rem get date string
set date_str=%date:~0,4%%date:~5,2%%date:~8,2%

rem rar to file
set file_name=projects_%date_str%.rar
rar a -x@exclude.lst "%file_name%" @backup.lst

原来费劲周折好不容易通过for才获取到的日期字符串,现在用set一句话就搞定了^o^
上面的代码看起来漂亮多了,嘿嘿

小结:寻找最恰当的武器

在程序员的世界里,经常面临着各种争议和多种选择,像C++和C#的兄弟打架,C#和Java长久宿怨,还有php、asp、jsp等各种脚本语言之间的争风吃醋,这些争吵喧闹一定程度上促进了大众对这些语言的了解以及这些语言自身的发展。但如果因为精通某种语言或技术,就一味的只用这一种语言或技能来解决所有问题,在很多时候这是不明智的。在实战系列一中我极力推销和强调for的强大功能,for虽强大,但有它的适用范围,一味滥用就会导致此系列实战二中的费力不讨好。在这里,set明显比for好用^o^

对各种语言技能,对各种工具,我们应该尽量先有个初略的大体了解,然后解决具体问题时,再挑选最合适的工具来做,这往往能事半功倍。
且记于此,与各位朋友共勉。

http://blog.csdn.net/xhhjin/article/details/7311893

原文地址:https://www.cnblogs.com/findumars/p/8289539.html