批处理之家's Archiver

netbenton 发表于 2009-11-28 19:01

批处理脚本高级编程技巧——变量嵌套

批处理脚本高级编程技巧——变量嵌套
一,为什么要使用变量嵌套?
  1,可提高代码的效率
  2,可以缩减代码
二,批处理变量的种类
  1, %0~%9 , %*:   批处理运行的命令行参数,在代码行预处理时扩展,可视为最高优先级扩展。

    如果存在变量:
12a=###
!a=$$$
a=aaa

echo;%12a%
得到结果为:第一个参量+2a

echo;%!a%!
得到结果为:$$$

2, %var%:  在代码行预处理时扩展,其实与%1~%9 ,%*是同时扩展取值的,
    但是%1%得到的只有可能是参量值,绝不可能得到变量值,所以视为次优先级扩展,

   ********** 预处理问题 **********
    要很好的理解%号变量,需要知道一点,预处理一次是读取一组来进行的  
      如:
  
        [1]
        set a=aa&echo %bb%

        [2]
        if aa==%bb% (
                echo 等
        )

        [3]
        (
                echo %aa%
                echo %bb%
        )
        上面就是所“组”的形式。

      还要知道:%号变量在预处理读取代码行时已经扩展了
        例如:[code]        set kh=)
        (echo %kh%&pause
        echo bb
        )
        [/code]因为%kh%扩展了,其值中的 ")" 与前面的 "(" 形成了一组代码,其实只读取了一行,
        运行代码遇到pause暂停后,修改bat文件, 把echo bb改为:echo cc,并保存后,再按键继续可见到显示的是"cc"
   ********** 预处理问题 **********

  3, for()变量: %%a %%1 %%中 等形式均可使用,

    虽然 %%a%或者%%1 等形式得到的还是for 变量的值,而不是作为:%a% %1 扩展,
    为什么呢?
    因为预处理在处理%var%形式变量时,是从左到右以两个%号作为一组变量的,而以%+一个数字作为一组参量的,
    还有一点就是如果一组%号的两个%号间没有其它字符时,将留下一个%号,
    所以是要将for()变量视为第三优先级,
    另外变量%var% 及%1扩展后的值里面,如果含有有效的for ()变量%a的话,该值中的%a 会得到for()变量再次扩展
    更说明了for()变量要比%var% %0~%9,%*要低一级
    这个例子是变量的嵌套扩展的一种形式,
    其实for ()的变量名应该是:经预处理后的一个 %+一个字符,以cmd批处理脚本里面,可以是数字,字母,或字符和汉字都可以。
    for变量仅在该组for()内有效,并向下一级子for ()继承
    如果父级for()与子级for()有相同的有效变量时,得到的是当前级的扩展值
例如:[code]  
for /f "tokens=1,2,3" %%a in ("aa ab ac") do (
   for /f "tokens=1,2,3" %%b in ("$a $b $c") do (
      echo;%%a-%%b-%%c-%%d &rem 结果为:aa-$a-$b-$c
     rem 而不是:aa-ab-ac-$c
   )
      echo;%%a %%b %%c %%d &rem 结果为:aa-ab-ac-
)[/code]4,!var!:  变量延迟打开时有效,在命令运行时扩展,可视为四级优先级,
为什么要这么认为呢?
    如果for()变量扩展后,重新组成的表达式中,含有!号的话,命令运行时会再次进行!var!的取值扩展
这也是变量嵌套扩展的一种形式
     !var!变量,在一对setlocal ... 和endlocal内有效,并向下一级子环境继承

  5,set/a 运算命令变量,这可以说最低级的变量扩展了,就连!var!变量也扩展后,
     如果得到的是以非数字及非运行符开头的话,就会再次进行变量扩展之后才执行运算。

三、只有了解变量扩展的优先级,才可能理解变量嵌套的用法。  
    变量嵌套扩展的方式有:
*** 注意:要开启变量延迟,否则!var!变量无从谈起 ***
    1,先%1或%var% 再 !var!
只要%1或%var%扩展的值中含有或和旁边的字符组合后含有:!var!,!var:n=m!,!var:~2,2! 等形式
就会再次进行!号变量的扩展
        例如:[code]        setlocal enabledelayedexpansion
        set aa=bb
        set abb=##
        echo 程序文件名:%0
        echo 嵌套扩展:!a%aa%!
        pause
        [/code]2,先%1或%var% 再 for ()%a
只要%1或%var%扩展后的值里面,含有有效的for ()变量%a的话,该值中的%a 会再次得到for()变量扩展
        例如:[code]        set #=%%a
        for %#% in (aa bb cc) do (echo;%#%)
        pause
        [/code]3,先for()%a 再!var!
只要for()%a扩展的值中含有或和旁边的字符组合后含有:!var!,!var:n=m!,!var:~2,2! 等形式
就会再次进行!号变量的扩展,这也就是为什么开启了变量延迟后,会丢失!号的原因。
        例如:[code]        set #aa=$a
        set #bb=$b
        set #cc=$c

        for %%a in (aa bb cc) do (echo;%%a : !#%%a!)
        pause
        [/code]4,先%1或%var% 再 for ()%a  再!var!
也就是第2+第3种用法的

        例如:[code]        setlocal enabledelayedexpansion
        set #=%%a
        set aaa=##a
        set abb=##b
        set acc=##c
        for %#% in (aa bb cc) do (echo %%a : a%#% : !a%#%!)
        pause
        [/code]5,多级嵌套:先%1或%var% 再 for ()%a  再!var! 再for ()%b 再!var!
主要是for ()%a 和!var!间的循环嵌套,看例子:[code]  
for %%a in (aaa_$a bbb_$b ccc_$c) do (
    set str=%%a
for /f "tokens=1,2 delims=_" %%b in ("!str!") do (         rem !str!作为for /f 的字符串表达式
  echo;[2f] %%b+%%c
  set %%c=%%a        &rem 第一次循环时:相当于set $a=aaa_$a
  for /f "tokens=1,2 delims=_" %%1 in ("!%%c!") do ( &rem 相当于:!$a!作为for /f 的字符串表达式
   echo;[3f] %%1=%%2
  )  
)
)
set $           &rem 显示变量,方便理解[/code]** 这点是变量嵌套的最巧用法,是提高批处理脚本的关键 **
    6,运算命令变量,就是在其它变量都扩展完了之后,还可以再进行扩展一次。[code] set a=100
set b=a
set "c=^!b^!*2"
set/a d=%c%+10[/code]代码中:%c% => !b!*2 => a*2 => 100*2 所以结果进行了以下运算:[code] set /a d=100*2+10[/code]7,管道方式:[code]set aa=d 100 &((echo;%%aa%%&echo;q)|debug)[/code]虽然是在同一行,也没有用到call ,但是%%a%%扩展成了d 100
原因是(echo;%%a%%&echo;q)是在批处理外运行的,并且通过管道把显示值传给后面的命令debug 作为输入行。

    8,效率较低级的call 方式变量嵌套,相当于从第1种方式到第5种方式再[b]循环[/b]一次。(有所重复,就不详述)

四、变量嵌套扩展的实际应用。
    1,if 用法[code]  
set "str= help open quit run "
set in=输入:
if "!str: %in% =!" neq "!str!" goto :m_%in%
:m_help
:m_open
:m_quit
:m_run[/code]2,for 用法:[code] set n1=姓名:!@n!     性别:!@xb!
set n2=学历:!@xl!     年龄:!@g!
set n3=民族:!@m!     体重:!@tz!
::前面先不要开变量延迟
setlocal enabledelayedexpansion
for /f "tokens=1-6" %%1 in ("benton 男 中专 18岁 汉 90kg") do (
set @n=%%1&set @xb=%%2&set @xl=%%3&set @g=%%4&set @m=%%5&set @tz=%%6
)
for /l %%a in (1,1,3) do (
for %%b in (!n%%a!) do (echo;%%b)
)
set n  &rem 查看变量方便理解[/code]3,其它补充
[b][请看本主题三楼]
[/b]
如果,看了本贴,还是不理解变量延迟的话,请再看其它相关贴子:

[url=http://bbs.verybat.org/viewthread.php?tid=10871][b][color=cyan]交流贴:call变量延迟与!!变量延迟的优先级问题[/color][/b][/url]

[url=http://bbs.bathome.net/viewthread.php?tid=2205&highlight=%B1%E4%C1%BF%C0%A9%D5%B9][b][color=cyan]浅谈for命令中的变量扩展[/color][/b][/url]

[url=http://bbs.bathome.net/viewthread.php?tid=6367&highlight=%2Bbatman][color=cyan][分享]从一个有趣的结果来谈谈批处理变量嵌套[/color][/url]

(待续。。。)


学习实践:
[url=http://bbs.bathome.net/viewthread.php?tid=6440&highlight=%2Bbatman][color=lime]【练习-053】按要求补全变量嵌套中的代码[/color][/url]

本人的文笔不好,水平有限,有不全和表达不清楚的地方,大家就当作可以开心一下吧。
本着共同学习的出发点,还请各位在一笑而过之后,作出批评指正。谢谢

[quote]
鸣谢:

batman版主 yslyxqysl版主 zqz0012005版主

指正错误
[/quote]

zqz0012005 发表于 2009-11-28 19:25

漏掉%*,与%0~%9同一级别的。

管道方式的原理解释不够“正点”。是管道运行机制带来两次预处理造成的。
类似的
set var=1&for /f %%- in ('echo %%var%%') do echo %%-

建议可以把相关老帖集中到一起。

zqz0012005 发表于 2009-11-28 20:15

虽然 %%a%或者%%1 得到的还是for 变量的值,却还是要视为第三优先级
====================================
根据原理的话,这个不应该这么算。
其实是bat从左到右的处理方式决定的,前面的两个%%是百分号对自己转义得到%号本身,
再与后面的符号结合形成for的变量%a、%1

只要for()%a扩展的值中含有或和旁边的字符组合后含有:!var!,!var:n=m!,!var:~2,2! 等形式
就会再次进行!号变量的扩展
====================================
补充:
%1、%var%、%%a形式的变量展开后,不只是根据有没有感叹号对!!(!var!及其替换、截取形式),而是根据语句中是否存在!号,只要含有!号,就要再次处理。
对含!号语句的再次处理也会拖累^再次受到处分。


另外,call的效率还是要比管道、for /f 解析命令输出等方式的效率高多了,毕竟是内部命令。

zjw767676 发表于 2009-11-29 10:30

好贴!!!!!!!!
忍不住灌下水!!!!!O(∩_∩)O~

batman 发表于 2009-11-29 10:34

忍不住要说两句

        记得随风版主在一次交谈中提到:我们在日常对批处理的应用中,多是考虑其实用性其它的简洁度、效率倒放在了其次。的确是

如此,我们大家在平常利用批来解决实际问题时都是以最终解决问题为根本目的,只要问题解决了就万事ok了。很少会去想到自己

的代码是不是还可以减减肥提提效,这是很正常的,我想也不用为此而自责,毕竟你的批再复杂再慢也比你手动去处理高效了很多。

    大家都可以回忆下自己刚接触批时写出的第一个批处理是怎么样的,是不是现在看起来是那么的稚嫩,而现在你是不是再也不

会写这样的代码来了?而这一切又说明了什么?我认为这说明你得到提高了,很显然这种提高与你平常对批的不懈学习和领悟是分不

开的。虽然为了解决实际问题,你还在写着拖沓冗余的代码,虽然你一次一次在高手们简练高效的代码前惊叹,虽然你总感觉自己还

是个批中新手,但请你相信,我的朋友,只要你坚持并努力下去,你就会得到不断的提高。当有一天,你站在了新的高度再回望自己

所走过的学习历程,你会发现这会是人生一笔不可多得的财富, 同时,你也会感觉到学习所带给的无穷乐趣。

    net版主也是从批处理新手逐渐走向成熟的,而且他成长的速度是惊人的,这得益于他刻苦的学习态度和高效的学习方法。而今

天他打破以往只发代码贴的习惯,费尽心血写下了这篇详细讲解变量嵌套的教程,其中更涉及到了cmd的最基本的处理机制,的确

是难能可贵。虽然,这其中蕴含的知识百相当广泛加上net版主因为时间原因在语句上欠通俗,刚开始时你可能会难以看懂和理解,

但我相信只要大家仔细地读认真地学,把握好变量嵌套的规律,并结合教程进行大量的练习,大家都能掌握好这一重要的批处理技

术,而迈上新的台阶的。
   
    本人罗索了一大堆,也扯了一通似与本贴主题无关的话题,无非就是想告诉大家,这是一篇不可或缺的好教程,大家一定要多读

多想多练,掌握好批处理变量嵌套的技巧,以不辜负编者的一片苦心。

zqz0012005 发表于 2009-11-29 22:41

set a=100
set b=a
set "c=^!b^!*2"
set/a c=%b%+10
代码中:%b% => !b!*2 => a*2 => 100*2 所以结果进行了以下运算:
======================================================
瞅着有点不对劲,笔误吧,第四行应该是%c%:set/a c=%c%+10

netbenton 发表于 2009-11-30 11:46

谢谢指正,笔误,见笑了,一楼已经更正。

summerflower 发表于 2009-11-30 12:18

看了第一遍,没看懂,本来不打算再研究的,看了batman的话,决定重新看看
看到第一段代码就卡壳了,这样的代码能运行吗?真是晕死聊
[code]12a=###
!a=$$
a=aaa
echo;%12a%
得到结果为:第一个参量+2a
echo;%!a%!
得到结果为:$$[/code]

netbenton 发表于 2009-11-30 19:24

re 8 楼
本来是作为说明文档的,是不能运行的,
不小心加[ code] 了。

kevinpeng 发表于 2009-11-30 22:46

真是强贴,我有一段时间没来这了,发现落后了很多!学了一部分倒退了

zaixinxiangnian 发表于 2010-4-4 19:39

我只道此贴是很精典,很好,对于我初学者来说很难懂,不过看到BATMANE对我们的鼓励,我会努力的

wankoilz 发表于 2010-5-26 10:50

受益匪浅,受益匪浅!

keiamy 发表于 2010-7-30 18:05

看了幾遍還是不明白, 但.....
還是再努力吧﹗
多謝netbenton的細心解釋和batman的鼓勵啦"" ^.^

rogue_1983 发表于 2010-7-31 00:53

真是好教程

特别感谢楼主一片苦心。菜鸟再这里学习了!!!!!!!强烈支持!!!!

sea2moon 发表于 2010-9-8 13:59

看的有些云里雾里~~
消化中~~~

lvsehuaxue 发表于 2010-9-14 18:37

欢迎

表示欢迎!非常感谢!

wc726842270 发表于 2010-10-1 03:38

看来看明白还得用些时日啊。先收藏了

wc726842270 发表于 2010-12-26 02:15

唉,真是强贴。确实啊,用好FOR,SET,IF真是P是精髓,现在回来看也不是那么容易看明白,真是经典

[[i] 本帖最后由 wc726842270 于 2011-3-4 15:26 编辑 [/i]]

myzam 发表于 2011-3-4 22:20

回复 3楼 的帖子

谢谢分享。
对了哈,我看每一门编程语言都有一本手册。
在网上还没见到过批处理手册,要是本论坛出一本批处理手册就好了。例子要浅显。
可以分批次出。最后在整理。最后是chm格式。

hnzz110 发表于 2013-5-28 23:04

真复杂啊,严重打击学习积极性

shelluserwlb 发表于 2014-12-25 13:48

[i=s] 本帖最后由 shelluserwlb 于 2014-12-26 07:54 编辑 [/i]

挺复杂的,不过认真看还是看得懂的。

慕夜蓝化 发表于 2014-12-27 10:21

你为什么不说清楚呢?
echo;%!a%!
得到结果为:$$$
就这一点,通过开启本地环境变量延迟扩展的先后顺序就能得出三种结果。
一旦讲出来就是通篇大论的,说清楚啊拜托。
一开始开启延迟扩展的话,!是无法作为变量名的啊,
也就是说永远无法取得!a的值,因为我们只是为a赋予了值。
你没有开启延迟扩展,便为!a赋值为$$$
之后再开启延迟扩展,才能得到这个$$$的结果。而!在开启延迟扩展的情况下,就是特殊符号,后面你无论跟多少!都无法被输出的。
而如果一直未开启延迟扩展,echo;%!a%!
结果则为 $$$!。
真是的。

Linuxer 发表于 2014-12-29 12:21

有点难度,,多看几遍吧。{:3_55:}

ann 发表于 2015-1-6 18:45

还不错,大部分都能弄懂。得要耐心啊

ai20110304 发表于 2018-8-29 15:24

感谢版主的分享。提高P技术

页: [1]

Powered by Discuz! Archiver 7.2  © 2001-2009 Comsenz Inc.