基本上很少使用批处理文件,一是因为对它了解的少,二是似乎没有完整的资料说明。
昨天,又有同事说要弄个小工具,对目录改名,首先想到了批处理,然后上网上搜索,找到一段代码,拿过来少做修改,结果发现老是有问题,没办法,花了一天的时间,仔细研究了一下有关的问题,最后终于问题解决了
一、我的任务目标
先说明我的目标,然后为了达成这个目标,中间遇到了哪些困难,然后再逐个问题分析
在一个目录中有很多子目录,要对这些子目录(只本级目录,不涉及孙目录)进行目录改名,格式:
0300000、0300001、0300002、...、0300100、0300101、...
前面固定为03开始,后面5个数字,从0开始,不够5位的前面补0
首先搜索到一段目录改名的代码,该代码的要求是,在现有的目录名前面加上固定的一个字符串:
FOR /f "tokens=*" %%i IN ('dir /a:d /b') DO ren "%%i" "prev_%%i"
一看这个语句很简单,虽然for和do之间的部分不好理解,但对我来说似乎不是特别重要,我主要关心的是后面的 "prev_%%i" 该如何修改才能达到我的目标。
为了分步实施计划,首先不固定为 0300000 这样的格式,第一步先简单修改,固定的前辍加编号即可,我对它修改后的大致样子如下:
set counter=0
FOR /f "tokens=*" %%i IN ('dir /a:d /b') DO (
ren "%%i" "newname_%counter%"
set counter=%counter%+1
)
此语句达不到我的目标,问题逐个分析如下
二、变量附值失败
这个部分花了90%的时间。
我发现只有第一个目录可以成功改名为 newname_0,后面的都改不了,先怀疑的是,变量名 counter 太过通用,是个关键字也说不定,为了避免这个问题,就把它改成 fcer:
set fcer=0
FOR /f "tokens=*" %%i IN ('dir /a:d /b') DO (
ren "%%i" "newname_%fcer%"
set fcer=%fcer%+1
)
此修改仍然不能正常工作,于是为了弄清楚情况,在后面添加了一行打印:
set fcer=0
FOR /f "tokens=*" %%i IN ('dir /a:d /b') DO (
ren "%%i" "newname_%fcer%"
set fcer=%fcer%+1
echo value = %fcer%
)
通过打印发现,输出的所有值都是 value = 0,这说明 set fcer=%fcer%+1 这一行没有生效,然后再搜索批处理变量附值的问题,大家说法也很简单,其中有两个附值方法:
A. set fcer=!fcer!+1
B. set /a fcer=%fcer%+1
我不确定 /a 究竟是做什么的,反正我这样写了也没有正常工作,采用A方法,也还是不行,这就奇怪了,网上搜索,说得明白,没有见哪个说不行,难道是我的电脑有问题?不得其解。由于是采用 set 附值的,所以在cmd下输入 set /? 看看帮助说不定可以解决问题.
以下是 set /? 中的一小段说明:
延迟环境变量扩充允许您使用一个不同的字符(惊叹号)在执行时间扩充环境变量。如果延迟的变量扩充被启用,可以将上面例子写成以下所示,以达到预期效果:
这样看,采用 !来代替%应该是没有问题的,但是我采用方法A也不能正常工作,继续看帮助,还有如下一段:
终于添加了延迟环境变量扩充的支持。该支持总是按默认值被停用,但也可以通过 CMD.EXE 的 /V 命令行开关而被启用/停用。请参阅 CMD /?
仔细看了一下延迟环境变量扩充的示例以及对示例的解释,终于有所发现,如下是一整段:
考虑到读取一行文本时所遇到的目前扩充的限制时,延迟环境变量扩充是很有用的,而不是执行的时候。以下例子说明直接变量扩充的问题:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "%VAR%" == "after" @echo If you see this, it worked
)
不会显示消息,因为在读到第一个 IF 语句时,BOTH IF 语句中的 %VAR% 会被代替;原因是: 它包含 IF 的文体,IF 是一个复合语句。所以,复合语句中的 IF 实际上是在比较 "before" 和"after",这两者永远不会相等。同样,以下这个例子也不会达到预期效果:
set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%
原因是,它不会在目前的目录中建立一个文件列表,而只是将LIST 变量设成找到的最后一个文件。这也是因为 %LIST% 在FOR 语句被读取时,只被扩充了一次;而且,那时的 LIST 变量是空的。因此,我们真正执行的 FOR 循环是:
for %i in (*) do set LIST= %i
这个循环继续将 LIST 设成找到的最后一个文件。
总体的意思是说, for 是一个整体,当进入到这一个语句时,所有对应的变量全都会一次性替换成目标值,所以为了在每次循环时才替换,所以需要启用延迟环境变量扩充的功能,按照上面的说明,重新启动cmd: cmd /V:on,然后再执行我的代码,终于看到了 value = 0、value = 1、value = 2、... 这样的打印,实在不容易
由于每次启动cmd时带参数,就相当不方便了,在问题解决之前也发了个贴子,大家的回复中提到了这个问题,解决办法就是通过语句打开延时环境变量的扩充功能:
setlocal enabledelayedexpansion
注意,后面的是一个整体,中间没有空格
加上这一句后,直接双击我的bat文件,也成功改名,就不用再 cmd /V:on 的方式启动了cmd窗口了
二、字符串获取
这部分比较容易,仍然看 set /? 的帮助信息:
也可以为扩展名指定子字符串。
%PATH:~10,5%
会扩展 PATH 环境变量,然后只使用在扩展结果中从第 11 个(偏移量 10)字符开始的五个字符。如果没有指定长度,则采用默认值,即变量数值的余数。如果两个数字(偏移量和长度)都是负数,使用的数字则是环境变量数值长度加上指定的偏移量或长度。
%PATH:~-10%
会提取 PATH 变量的最后十个字符。
%PATH:~0,-2%
会提取 PATH 变量的所有字符,除了最后两个。
首先参考网上的方法,在我的名字前面加上足够的0,然后加上计数器,再从后面截取5个,就达到了我的目的。
set startwith=000000
FOR /f "tokens=*" %%i IN ('dir /a:d /b') DO (
set newname=%startwith%!fcer!
set newname=03!newname:~-5!
echo dir name from "%%i" to "!newname!"
ren "%%i" "!newname!"
set /a fcer=!fcer!+1
)
以上的代码是我当前正常使用的了,这部分我想说的是,对字符串来说,两边双引号的使用,我在修改成上面代码之前,大概是这样子的:
set newname=“%startwith%!fcer!”
set newname=”03!newname:~-6!“
echo dir name from "%%i" to "!newname!"
实际运行的时候,发现中间的那行打印是 dir name from "old name" to "0300000"" 这样的,最后是两个双引号,仔细检查,是因为前面两个set对newname附值时带了双引号,所以通过 ~-6 截出来的,是带了结尾的双引号,但在实际改名的时候,双引号会被省略,所以运行结果是没错,但这个截取字符串的写法就和大家说的不一致,因为我需要的是截取最后的5个而不是6个,所以为了使写法正常,就去掉了附值行中的双引号,并把截取改成 ~-5,这样一切就正常了
三、if语句
为了使用方便,添加了一个参数,但是要判断是否有参数,再搜索,修改后大致如下:
set fcer=0
if not "%1" == "" (set fcer=%1)
如果带有一个参数,就把它附给计数器,这样就可以通过参数来修改起始的序号了。进一步改进成如下的样子:
set fcer=0
if "%1" == "?" (
echo usage:
echo do [startnum]
echo if there is no parameter, the number will start from 0.
goto end
) else (
if not "%1" == "" (set fcer=%1)
)
这样就添加了一个用法说明,即如果参数为 ? 号就打印使用说明,否则就修改起始序号,如果没有参数,则不对fcer变量修改,这样它就是初始值0了
四、空格
习惯了其它的编程在=号的两边加了空格,方便阅读,这里也习惯地加上了,结果发现附值不成功,比如
set startwith = 000000
通过打印发现,startwith 是空的,修改成如下的样子:
set startwith=000000
附值正常起来了