Board logo

标题: [系统相关] 批处理无法在for中给变量累加赋值? [打印本页]

作者: 随风    时间: 2009-2-19 14:49     标题: 批处理无法在for中给变量累加赋值?

无法在for中给变量累加赋值?
请教各位一个问题?
写代码时发现cmd的一个奇怪显现,百思不得其解。
看下面的代码,先别运行,诸位能看出语法错误吗?
但第一次pause后,就会退出。。。
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. color 1f
  4. set "var=00000"
  5. for /l %%a in (1 1 10) do set var=!var!!var!
  6. echo !var!
  7. echo 这里可以运行。
  8. pause
  9. color cf
  10. set var=!var:0= 0 !
  11. echo !var!
  12. echo 这无法运行???
  13. pause
复制代码
原因有二
第一、 取消 set var=!var:0= 0 ! 这一句
第二、 for /l %%a in (1 1 10) do set var=!var!!var!
       这句中不能有多个的!var!,改为set var=000!var!就没事
       也就是这两句不能同时出现??
       郁闷。。。。。。
作者: lhjoanna    时间: 2009-2-19 15:12

看了随风兄的问题,我认为不是那两句不能同时使用,而是在此句 set var=!var:0= 0 !后,var的长度超过了变量能保存的字符的最大长度,所以导致中途退出。换小一点就不会出现这样的问题。
作者: 随风    时间: 2009-2-19 15:23

汗啊,,原来是这么简单的问题,
变量赋值的最多应该是8190个,没注意我那代码超过了,,
作者: batman    时间: 2009-2-19 15:34

原帖由 随风 于 2009-2-19 15:23 发表
汗啊,,原来是这么简单的问题,
变量赋值的最多应该是8190个,没注意我那代码超过了,,

那小楼兄运行下下面这段代码看(我的机子上能运行出结果):
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. set "var=0"
  4. for /l %%a in (1 1 10000000) do set "var=!var!!var!"
  5. echo !var!
  6. pause>nul
复制代码

这总不止8190个字符了吧
作者: Batcher    时间: 2009-2-19 15:47

http://technet.microsoft.com/en-us/library/bb490954.aspx

The maximum individual environment variable size is 8192bytes.

The maximum total environment variable size for all variables, which includes variable names and the equal sign, is 65,536KB.

作者: wxcute    时间: 2009-2-19 18:52     标题: 错误信息如下

这里可以运行。
请按任意键继续. . .
输入行太长。
命令语法不正确。

作者: lhjoanna    时间: 2009-2-19 21:17

由batman所发代码引申出的变量最大长度问题,以下代码由batman、随风、我共同讨论测试得出,结果也只是提出一个猜想,望大家讨论补充!
1、首先给出随风的一个测试代码
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. for /l %%a in (1 1 10000) do (
  4.   set m=a!m!||(echo %%a&pause)
  5.   )
  6. pause
复制代码
结果为8190,这时可能大家会认为最大长度应该是8190。
2、现在做一个小小的改变,把以上代码中的set m=a!m!||(echo %%a&pause)改为set var=a!var!||(echo %%a&pause)。再次运行,按理说结果应该没影响,可是出乎我们的意料,结果为8188。这是为何?参照batcher给出的资料,可以推出变量名和等号都占字节。
3、现在再做出一个改变,把set m=a!m!||(echo %%a&pause),改为set “m=a!m!“||(echo %%a&pause),再次运行,结果是8188。又困惑了,我们一般来看set "str1=str2"和set str1=str2所达到的效果是一样的。再根据资料The maximum individual environment variable size is 8192bytes。我想着要从环境变量的内存空间分配角度来思考了。环境变量分配的最大空间为8192字节。其中包括变量名、等号、引号(如果有的话)、以及变量所包含的字符。
4、为了更加清晰,对刚才代码增加一些。如下:
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. for /l %%a in (1 1 10000) do (
  4.   set a=!a!0
  5. )
  6. if "!a:~8181,1!"=="" echo 8181
  7. if "!a:~8182,1!"=="" echo 8182
  8. if "!a:~8183,1!"=="" echo 8183
  9. if "!a:~8184,1!"=="" echo 8184
  10. if "!a:~8185,1!"=="" echo 8185
  11. if "!a:~8186,1!"=="" echo 8186
  12. if "!a:~8187,1!"=="" echo 8187
  13. if "!a:~8188,1!"=="" echo 8188
  14. if "!a:~8189,1!"=="" echo 8189
  15. if "!a:~8190,1!"=="" echo 8190
  16. if "!a:~8191,1!"=="" echo 8191
  17. if "!a:~8192,1!"=="" echo 8192
  18. if "!a:~8193,1!"=="" echo 8193
  19. pause
复制代码
执行后显示为8189,按照刚才的分析,共占有的字节数:8189+1('a')+1('=')=8191,不是8192。这又是为何?再看下面?
5、batman提供的代码:
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. for /l %%a in (1 1 10000) do (
  4.   set a=!a!0
  5.   )
  6. set a=!a:0=1!
  7. echo !a!
  8. pause
复制代码
上面代码可以正常运行,可是把上面的代码中set a=!a:0=1!改为:set a=%a:0=1%则提示输入过长。这说明开启变量延迟内存中的分配发生变化,到底是什么变化?期待有人来提供一些资料。
6、第四步分析的还差一个字节,我猜想是不是内存自动占用一个固定的标记字节,来标志是否启动了变量延迟以及其他的一些标志。此也期待有心人来完善。
7、这就是我们的初步分析,可以知道的是单个环境变量的最大的内存分配为8192字节(有资料为证),对于这么多字节的如何分配还有待继续探索。欢迎大家一起来讨论!

[ 本帖最后由 lhjoanna 于 2009-2-21 22:44 编辑 ]
作者: 随风    时间: 2009-2-21 19:03

set m=“a!m!”||
引号笔误吧 应该是 set "m=a!m!"

[ 本帖最后由 随风 于 2009-2-21 19:05 编辑 ]
作者: lhjoanna    时间: 2009-2-21 22:44

恩,是笔误啊,谢谢兄指正!
作者: zqz0012005    时间: 2009-2-22 00:22     标题: 回复 7楼 的帖子

关于第5点,我认为可能和预处理有关系。
set a=!a:0=1!,预处理时并不展开变量(执行时才进行替换操作),所以输入行长度为该行语句字面长度13。
set a=%a:0=1%,预处理时要先对变量进行展开,展开后的长度显然超过了命令行允许的长度(这个长度应该是8192)。

[ 本帖最后由 zqz0012005 于 2009-2-22 05:22 编辑 ]
作者: zqz0012005    时间: 2009-2-22 05:42     标题: 回复 7楼 的帖子

至于第2点中推断的结论“变量名和等号都占字节”,可以进一步验证:
随风的一个测试代码
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. for /l %%a in (1 1 10000) do (
  4.   set m=a!m!||(echo %%a&pause)
  5.   )
  6. pause
复制代码
如果把变量名换成逗号“,”,利用命令行的空子,写成如下形式:
set,=a!,!||(echo %%a&pause)
则结果为8191。
逗号有多重意思,一是作为命令与参数之间必要的分隔符,二就是作为变量名。而系统有时对这种多样性处理不完善,最后的结果认为set,=a!,!语句中没有变量名,于是给变量值多分配了一位。
作者: zqz0012005    时间: 2009-2-22 06:03

关于取最大长度的一个题外话(先写在这里,整理时再分开或删除吧)
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. for /l %%a in (1 1 8193) do (
  4.   set,=a!,!||(echo !,!&echo !errorlevel!&pause)
  5. )
  6. pause
复制代码
如果换成以下形式
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. for /l %%a in (1 1 8193) do (
  4.   set,=a!,!
  5.   if !errorlevel! neq 0 (echo !,!&echo !errorlevel!&pause)
  6. )
  7. pause
复制代码
即将||改成errorlevel的形式,结果竟然不同。

这好像是for语句内部errorlevel返回值的问题。以前说过for本身是一个特殊的命令解释程序,但不知这一点有没有讨论过(我以前也发过,但讨论的人不多,我当时的分析也比较混乱,有时间再好好整理一下)。
作者: zqz0012005    时间: 2009-2-22 06:32

如果用echo命令将变量写到文本中,
!m!长度是8190,而文本中的字符是8189个“a”+2字节的回车换行符(CrLn),结果共8191个字节。
!,!长度是8191,而文本中的字符是8190个“a”+1个回车符(Cr),结果也是共8191个字节。

已经讨论过,微软的说法中“字节(byte)”是不准确的,实际可以为字符。
如果把字母a换成汉字比如“人”,则
!m!长度是8190,而文本中的字符是8189个“人”+2字节的回车换行符(CrLn),结果共(8189*2+2)个字节。
!,!长度是8191,而文本中的字符是8190个“人”+1个回车符(Cr),结果共(8190*2+1)个字节,比上面多1个字节。

[ 本帖最后由 zqz0012005 于 2009-2-22 06:47 编辑 ]
作者: Batcher    时间: 2009-2-22 12:46     标题: 回复 13楼 的帖子

可能是当年微软写帮助文档的时候,没有考虑到每个汉字占两个字节^_^
作者: 愚无尽    时间: 2009-2-22 13:49

我有没有看错时间,呵呵,各版主辛苦了,颇有研究哈
作者: lhjoanna    时间: 2009-2-22 14:11     标题: 回复 10楼 的帖子

对于第5步中的代码,如果改为如下,则可以执行。
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. for /l %%a in (1 1 8185) do (
  4.   set a=!a!0
  5.   )
  6. set a=%a:0=1%
  7. echo !a!
  8. pause
复制代码
我也认为和预处理有关,开启变量延迟后,执行到那一句才展开变量,所以set a=!a:0=1!中,把第一个等号右边的理解为一个整体,而set a=%a:0=1%,在执行之前就已经展开,所以 a=%a:0=1%以及右边的%a:0=1%都要满足不能超过最大长度。前面'a'以及'='已经占了2字节。所以%a:0=1%最大为8190字节。其中变量名a、'='、'0'、'1'、标记位各占一个字节,剩下变量a中所包含的字符串最大长度也就是8185了。
作者: lhjoanna    时间: 2009-2-22 14:13     标题: 回复 13楼 的帖子

对于微软说法中(Byte)不准确的说法,我想也是可以理解的。技术的发展或多或少都要局限于当时的环境,当时创建字符编码时我想微软也没有考虑到若干年以后全球关系发展的如此迅速,所以也就出现了90年代又花了几年时间来编写Unicode,作为一个统一的国际化编码标准。并且各个国家地区采用的字符集不一致,对于非Unicode的环境下采用活动代码页来进行编码的转换。兄测试下如下代码:
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. for /l %%a in (1 1 10000) do (
  4.   set m=a人!m!||(echo %%a&pause)
  5.   )
  6. pause
复制代码
结果为4095,也就是8190的一半。我们可以理解为8192是按字符算的,但是这确实涉及到我们经过活动代码页936的转换后,环境变量在内存中是如何分配的。比如单字节集中空格表示为20H,而经过转换后单字节字符与双字节字符应该如何统一,按原样表示还是扩充为双字节0020H。对于此我没有研究过,还需要再在网上找一些相关资料。
作者: Batcher    时间: 2009-2-22 14:45     标题: 回复 17楼 的帖子

但是微软中文网站在翻译5楼那篇文章的时候,居然照搬原文,也不去修改一下,实在不应该。
作者: myzwd    时间: 2009-2-24 00:04     标题: 回复 1楼 的帖子

掉了过引号
@echo off
setlocal enabledelayedexpansion
color 1f
set "var=00000"
for /l %%a in (1 1 10) do set var=!var!!var!
echo !var!
echo 这里可以运行。
pause
color cf
set var=!var:0= 0 !
echo !var!
echo 这无法运行???
pause


set "var=00000"这句里面的00000是字符串常量,set var=!var:0= 0 !里面电脑把它读成了数字,是数据类型不匹配。所以运行不了。改为 set var=!var:“0”=“ 0” 就正常了。!
作者: lhjoanna    时间: 2009-2-24 01:41

我认为不是变量类型不匹配,批处理脚本语言也和大部分脚本语言一样,都具有“变量无关、解释执行”等特点。给个例子:
  1. @echo off
  2. set "a=123"
  3. set "b=456"
  4. set /a c=a+b
  5. echo !c!
  6. pause
复制代码

如果按照类型匹配的原则,set /a c=a+b应该报错才对啊。上述的set var=!var:0= 0 !改为set var=!var:"0"=" 0 "!,加引号后空格自动过滤,var依然不变,"0“没有替换为" 0 ",依然是"0",之所以可以运行的原因是因为变量和替换前一样,长度没有变化,没有超过最大长度。此题的根本原因仍是字符串长度问题!
作者: myzwd    时间: 2009-2-24 11:33     标题: 但是

@echo off
setlocal enabledelayedexpansion
color 1f
set "var=00000"
for /l %%a in (1 1 10) do set var=!var!!var!
echo !var!
echo 这里可以运行。
pause
color cf
set var=!var:0= 0 !
echo !var!
echo 这无法运行???
pause


我把set var=!var:0= 0 !这里改为字符串的标准写法set var=!var:"0"= "0 "! 的确可以正确的执行啊。是不是可以这样将,批处理对于非标准的写法,有的能执行,有的不能。比如说rem 写出 ::就可以正常的执行。
   但是我做采用了标准写法 可以正常执行,这该怎么解释呢?按“长度”理论,我就没想明白。


   还有就是看cmd 的set帮助是这样说的:
------------------------------------------------------------
如果您使用任何逻辑或取余操作符, 您需要将表达式字符串用
引号扩起来。


*****在表达式中的任何非数字字符串键作为环境变量
名称,这些环境变量名称的值已在--------使用前-------转换成数字。*********


如果指定
了一个环境变量名称,但未在当前环境中定义,那么值将被定为
零。这使您可以使用环境变量值做计算而不用键入那些 % 符号
来得到它们的值。如果 SET /A 在命令脚本外的命令行执行的,
那么它显示该表达式的最后值。该分配的操作符在分配的操作符
左边需要一个环境变量名称。除十六进制有 0x 前缀, 八进制
有 0 前缀的,数字值为十进位数字。因此, 0x12 与 18 和 022
相同。请注意八进制公式可能很容易搞混: 08 和 09 是无效的数字,
因为 8 和 9 不是有效的八进制位数。

---------------------------------------------------------
   还有就是如果变量值的长度是8192那就是说浮点数采用的是16为二进制数其中2^13=8192,,这是你上面观点的论据,
但是微软的资料上找不到,你的论据啊。微软的资料上却可以明确找到---转换为数据这样的说发啊。
我是越想越糊涂。不知道怎么采对。
   

  我去看到了。长度理论有道理。
which includes variable names and the equal sign,这句话,如果算上其它的符号,变量真的是超过范围了。这正好解释了把把set "var=0000" 改为了set "var=000" 可以执行的原因了。
     因为 算上name和equal 变量的初始长度是6,不含引号,机器解释for时,var的长度是12,10次循环后长度是
12*(2^10)=12288>8192,所以报错。
     
    不过 ,还是得说说解释解释为什么 set var=!var:0=0 ! 改为这样set var=!var:"0"="0 "!又可以了

我明白了 ,长度理论是对的。支持!!!!!!

[ 本帖最后由 myzwd 于 2009-2-24 13:16 编辑 ]
作者: Batcher    时间: 2009-2-24 12:19     标题: 回复 21楼 的帖子

5楼给出的链接就是微软的官方网站
作者: lhjoanna    时间: 2009-2-24 14:03     标题: 回复 21楼 的帖子

我上面已经说了啊,set var=!var:"0"=" 0 "!可以正确运行的原因是set之后的var和set之前的var一模一样,根本没有变化。长度没有发生变化,当然可以正确运行。其次解释一下为什么长度没有发生变化:我上面说到是引号自动对空格过滤。今天回来又验证一下,这种说法是不对的。引号没有过滤空格,长度不变的原因是set var=!var:"0"=" 0 "!中,set把"0"做为被替换的变量,在var中找不到"0",所以根本没有发生变化。举两个个例子:
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. set "var=123456789"
  4. set var=!var:"1"=" 5 "!
  5. echo !var!
  6. pause
复制代码
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. color 1f
  4. set "var=123456789"
  5. set var=!var:1=" 5 "!
  6. echo !var!
  7. pause
复制代码
你提出的:
【1】If you use any of the logical or modulus operators, you will need to
enclose the expression string in quotes.  给个例子:
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. set a=5
  4. set /a "a*=5,a<<=5"
  5. echo !a!
  6. pause
复制代码
这时需要把表达式用引号括起来,否则出错。
【2】The maximum total environment variable size for all variables, which includes variable names and the equal sign, is 65,536KB.
这句话我的理解为一个程序中所定义的所有环境变量所占用的内存空间不能超过64M。
作者: zqz0012005    时间: 2009-2-24 16:32

是变量类型不匹配还是输入行太长,把set "var=00000"中的0换成字母不就知道了吗?

根本不存在什么类型不匹配的问题。

而set var=!var:"0"=" 0 "! 显然没有进行任何替换操作。
作者: lhjoanna    时间: 2009-2-24 16:58     标题: 回复 24楼 的帖子

不知errorlevel的问题有什么进展,是不是涉及到for解释机制的问题了?
作者: myzwd    时间: 2009-2-24 18:59     标题: 回复 1楼 的帖子

我的例子:
@echo off
set tt
setlocal enabledelayedexpansion

for /l %%1 in (1 1 2323) do (

set tt=!tt!%%1)
set tt
pause
结果是tt为:123...2323,这是从1到2323的拼接。
这里里面的1位数有:9个
          2位数有:90个
          3位数有:900个
          4位数有:1324个
这样tt变量的总长度=9+2*90+3*900+4*1324=8185
------------------
还有7个字节就不知道跑哪去了。因为把tt换成ttt还是同样的结果。

The maximum individual environment variable size is 8192bytes.
最大的当环境变量的大小是8192b。我想这句话里面不应该包含名和等号才能解释上面的把tt换成ttt结果相同这个现象。因为它已经预留了给那些特殊符号的起指示作用的空间。值是不知道它是怎么留的,留的多少。
我猜测是7个字节。但是不能以上面一个例子来肯定。
The maximum total environment variable size for all variables, which includes variable names and the equal sign, is 65,536KB.
最大的全部环境变量的大小是,包括名字,等号,为65536kb
在计算全部变量的总大小时才考虑等号和名。


晕。管它是多少,反正关系不大。

[ 本帖最后由 myzwd 于 2009-2-24 19:48 编辑 ]
作者: myzwd    时间: 2009-2-24 19:55     标题: 回复 23楼 的帖子

我试过了 你讲的完全对
我是用下面的例子做的
set tt=123a123
set tt=%tt:a=b%
-----------------------------
set tt=%tt:"a"="b"%
------------------------
结果是前一种有变化,后一种有变化。因为它们在运用时转换为数值的值不一样。
(cmd的set帮助说:它们都是要转换为数值计算的。)
作者: zqz0012005    时间: 2009-2-24 20:11     标题: 回复 27楼 的帖子

(cmd的set帮助说:它们都是要转换为数值计算的。)

请注意上下文!那说的是使用/a开关进行数学运算时才要转换。
set /?
...
/A 命令行开关指定等号右边的字符串为被评估的数字表达式。该表达式
评估器很简单并以递减的优先权顺序支持下列操作:
...
如果您使用任何逻辑或取余操作符, 您需要将表达式字符串用
引号扩起来。在表达式中的任何非数字字符串键作为环境变量
名称,这些环境变量名称的值已在使用前转换成数字。如果指定
了一个环境变量名称,但未在当前环境中定义,那么值将被定为
零。这使您可以使用环境变量值做计算而不用键入那些 % 符号
来得到它们的值。...


这里有一个以前说过多次的,常常让新手误解的地方:微软总喜欢把bat中的变量称为环境变量,很容易与系统环境变量这一概念混淆。
作者: zqz0012005    时间: 2009-2-24 20:16     标题: 回复 26楼 的帖子

注意还要受到总的输入行长度的限制。
set var=value
即:set命令+空格[+引号]+变量名+等号+变量值[+引号]
这一条命令的总长度不能超过8192。
作者: zqz0012005    时间: 2009-2-24 20:18

http://technet.microsoft.com/en-us/library/bb490954.aspx

The maximum individual environment variable size is 8192bytes.

The maximum total environment variable size for all variables, which includes variable names and the equal sign, is 65,536KB

实际上,第一句话后面也应该加上: which includes the variable name and the equal sign.

即:The maximum individual environment variable size is 8192bytes, which includes the variable name and the equal sign.
作者: myzwd    时间: 2009-2-25 11:56     标题: 回复 29楼 的帖子

你这么一说好象可以把我差的那7b找回来,但是我把tt换成ttt还是一样的结果,
所以我猜测是把那7b留来记录set命令代码了,包含你提到的=,名等等。
其实 ,弄的这么晕,全怪微软。

[ 本帖最后由 myzwd 于 2009-2-25 12:01 编辑 ]
作者: zqz0012005    时间: 2009-3-14 16:39

这个帖子的标题是不是该一下?

“变量的最大长度问题”比较合适
作者: powerbat    时间: 2011-10-20 03:03

回复 11# zqz0012005

errorlevel的原因在这里:

[讨论]对批处理中errorlevel的几点猜测

http://www.bathome.net/viewthread.php?tid=14277




欢迎光临 批处理之家 (http://bbs.bathome.net/) Powered by Discuz! 7.2