[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程[批处理精品]批处理版照片整理器
[批处理精品]纯批处理备份&还原驱动[批处理精品]CMD命令50条不能说的秘密[在线下载]第三方命令行工具[在线帮助]VBScript / JScript 在线参考
返回列表 发帖

[数值计算] [已解决]批处理()嵌套的变量延时与endlocal作用域问题

本帖最后由 amwfjhh 于 2014-11-12 09:40 编辑

以下代码源自于plp626发贴子:http://bbs.bathome.net/thread-15721-1-1.html
我试着将其排版一下时发现一些涉及变量延时及endlocal作用域的问题,代码如下:
  1. @echo off
  2. set localon=setlocal enabledelayedexpansion
  3. set localoff=endlocal
  4. REM 获得100000次执行 @echo off 任务的耗时,存放在ct1变量内
  5. set t1=%time%
  6. for /l %%a in (1 1 100000)do @echo off
  7. set t2=%time%
  8. REM pause
  9. echo t1 : %t1%    t2 : %t2%
  10. call :_etime t1 t2 tc1
  11. set /a tc1*=10
  12. echo 10万次echo off耗时 %tc1% 毫秒
  13. REM pause
  14. REM 初始工作(该处可省略),比如定义一些变量等,以便后面高效执行你的代码
  15. rem 。。。。
  16. REM 获得500次执行“你的代码”任务的耗时,存放在ct2变量内
  17. set t1=%time%
  18. for /l %%a in (1 1 500)do (
  19. set str=0123_ABCDXYZabcdxyz
  20. rem 版本一代码(或者版本二代码,但相应的str值请更换)
  21. echo do something else...>nul 2>nul
  22. )
  23. set t2=%time%
  24. echo t1 : %t1%    t2 : %t2%
  25. REM pause
  26. call :_etime t1 t2 tc2
  27. set /a tc2*=10
  28. echo 你的代码耗时 %tc2% 毫秒
  29. REM PAUSE
  30. REM 计算执行一次“你的代码”与执行一次“@echo off”的耗时比
  31. set/a rate=200*tc2/tc1
  32. echo 一次任务与一次“@echo off命令”耗时比=%rate%
  33. pause
  34. goto :EOF
  35. (
  36. :_etime <begin> <end> <ret> //求时差
  37. echo %1 %2 %3
  38. REM pause>nul
  39. %localon%
  40. Set /a c=(!%2:~,2!-!%1:~,2!)*360000+(1!%2:~3,2!-1!%1:~3,2!)*6000+1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!
  41. echo c : %c%
  42. set /a c+=-8640000*(c^>^>31)
  43. echo c : %c%
  44. %localoff%&set %3=%c%
  45. echo c : %c%
  46. goto :eof
  47. )
复制代码
具体疑问为:
1)将%localoff%(即endlocal,下同)后面的&set %3=%c%放至下一行,则%3取不到任何值,从帮助上得之,endlocal之后变量会还原为setlocal之前的值,此时为空可以理解,但为什么跟在同一行的%localoff%之后,它还能取到本已“无效”的值呢?
2)关于:_etime函数前的那个括号,现在这样书写可以得到正常值,但如果把:_etime所在行与上面的(行换下位置的话,则会提示此时不应该有*360000+之类的错误提示,如果用^将()转义,则又得不到正确的计算值,请问这又是为何,()的特殊应有到底有何禁忌?为什么我其它批处理里面有相类似的调用都能正常执行,到这里就会碰到变量延时的问题呢?还是说当()遇到set语句中再有()时,才会出现此类报错?


先谢谢各位前辈指教,如有参考内容,也请在回贴中指明地址,定当认真拜读,谢谢。

了解了这些细微差别,就可以像写C程序一样来写批处理了,先注册函数名(可忽略),将函数体写在主程序之后,主程序执行完毕直接 GOTO :EOF退出批处理,这样可读性就要好得多,行如下方:
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. set funCalc=call :_etime
  4. REM 获得100000次执行 @echo off 任务的耗时,存放在ct1变量内
  5. set t1=%time%
  6. for /l %%a in (1 1 100000)do (echo off)
  7. set t2=%time%
  8. REM pause
  9. echo t1 : %t1%    t2 : %t2%
  10. %funCalc% t1 t2 tc1
  11. echo tc1 : %tc1%
  12. set /a tc1*=10
  13. echo 10万次echo off耗时 %tc1% 毫秒
  14. REM pause
  15. REM 初始工作(该处可省略),比如定义一些变量等,以便后面高效执行你的代码
  16. rem 。。。。
  17. REM 获得500次执行“你的代码”任务的耗时,存放在ct2变量内
  18. set t1=%time%
  19. for /l %%a in (1 1 500)do (
  20.         set str=0123_ABCDXYZabcdxyz
  21.         rem 版本一代码(或者版本二代码,但相应的str值请更换)
  22.         echo do something else...>nul 2>nul
  23. )
  24. set t2=%time%
  25. echo t1 : %t1%    t2 : %t2%
  26. REM pause
  27. %funCalc% t1 t2 tc2
  28. echo tc2 : %tc2%
  29. set /a tc2*=10
  30. echo 你的代码耗时 %tc2% 毫秒
  31. REM PAUSE
  32. REM 计算执行一次“你的代码”与执行一次“@echo off”的耗时比
  33. echo.&echo.
  34. echo tc1 : %tc1% tc2 : %tc2%
  35. set/a rate=200*tc2/tc1
  36. echo 一次任务与一次“@echo off命令”耗时比=%rate%
  37. pause
  38. goto :EOF
  39. :_etime <begin> <end> <ret> //求时差
  40. (
  41.         echo %1 %2 %3
  42.         REM pause>nul
  43.         set /a "c=(!%2:~,2!-!%1:~,2!)*360000+(1!%2:~3,2!-1!%1:~3,2!)*6000+1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!,c+=-8640000*(c>>31)"
  44.         set %3=!c!
  45.                
  46.         echo %3 : !%3!
  47.         
  48.         goto :eof
  49. )
复制代码

TOP

本帖最后由 amwfjhh 于 2014-11-12 09:31 编辑

终于弄明白了。函数内set无法取得其值其本质还是在于变量延时与语句块之间的相互关系。

以下是原文:
  1. :etime <begin> <end> <ret> //求时差
  2. setlocal enabledelayedexpansion
  3. Set/a "c=(!%2:~,2!-!%1:~,2!)*360000+(1!%2:~3,2!-1!%1:~3,2!)*6000+1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!,c+=-8640000*(c>>31)"
  4. endlocal&set %3=%c%&goto:eof
复制代码
在原文中,函数体未用括号括起来,也即函数体之间每行语句是独立的,按先后顺序执行,因此set语句下行已被正确赋值,而set %3=%c%与endlocal组成语句块,处于同一执行周期,可用%c%取其更新后的值,setlocal enabledelayedexpansion仅对set 里面的!%@:~,2!之类开启变量延时扩展,如果此处仅为纯数字引用,则无需开启。

而在加了括号后的语句中:
  1. :_etime <begin> <end> <ret> //求时差
  2. (
  3.         echo %1 %2 %3
  4.         REM pause>nul
  5.         %localon%
  6.         set /a "c=(!%2:~,2!-!%1:~,2!)*360000+(1!%2:~3,2!-1!%1:~3,2!)*6000+1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!,c+=-8640000*(c>>31)"
  7.         echo c : !c!
  8.         %localoff%&set %3=%c%
  9.                
  10.         call echo %3 : %%%3%%
  11.         
  12.         goto :eof
  13. )
复制代码
由于函数体被括号引用起来,导致整个函数体组成了一个语句块,对于脚本宿主来说,它们据有相同执行周期,这样导致的结果就是,setlocal所在行与set行,endlocal行看似为三条独立语句,实则仍为一个语句块,在同一周期被执行,set /a的结果不会被其下行的%c%所接收,而由于在语句块内开启了变量延时,!c!所取的值即为实时取值,此时的实时,就严格按照语句先后顺序来执行,endlocal之后,休想再用它来得到!c!值,因此就看到如结果所显示的,在endlocal之前的echo c : !c!可以得值,但此值不可被返回给call所指定的接收变量,解决办法有两个:一是开启整个脚本的变量延时,去除函数体内的临时开关;二是去掉函数体两边的括号,将函数体内的语句由语句块变成独立的语句集合。

TOP

本帖最后由 amwfjhh 于 2014-11-11 15:29 编辑

用""或者^转义之后,在local之间能获得变量值,但就是拿不出来……
  1. :_etime <begin> <end> <ret> //求时差
  2. (
  3.         echo %1 %2 %3
  4.         REM pause>nul
  5.         %localon%
  6.         Set /a "c=(!%2:~,2!-!%1:~,2!)*360000+(1!%2:~3,2!-1!%1:~3,2!)*6000+1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!"
  7. echo c : !c!
  8.         set /a "c+=-8640000*(c>>31)"
  9.         echo c : !c!
  10.         %localoff%&set %3=%c%
  11.         call echo %3 : %%%3%%
  12.         
  13.         goto :eof
  14. )
复制代码
不管是set %3=%c%还是set %3=!c!都得不到正确值……

TOP

回复 2# apang


   
谢谢apang的解答,加深了我对“语句块”的理解,它们应该跟()作用是一样的,语句块内命令共享同一生命周期,想到这那这句应该可以被(),然后断行来替找,如下,经实验结果正常。
  1. @echo off
  2. set localon=setlocal enabledelayedexpansion
  3. set localoff=endlocal
  4. REM 获得100000次执行 @echo off 任务的耗时,存放在ct1变量内
  5. set t1=%time%
  6. for /l %%a in (1 1 100000)do @echo off
  7. set t2=%time%
  8. REM pause
  9. echo t1 : %t1%    t2 : %t2%
  10. call :_etime t1 t2 tc1
  11. set /a tc1*=10
  12. echo 10万次echo off耗时 %tc1% 毫秒
  13. REM pause
  14. REM 初始工作(该处可省略),比如定义一些变量等,以便后面高效执行你的代码
  15. rem 。。。。
  16. REM 获得500次执行“你的代码”任务的耗时,存放在ct2变量内
  17. set t1=%time%
  18. for /l %%a in (1 1 500)do (
  19.         set str=0123_ABCDXYZabcdxyz
  20.         rem 版本一代码(或者版本二代码,但相应的str值请更换)
  21.         echo do something else...>nul 2>nul
  22. )
  23. set t2=%time%
  24. echo t1 : %t1%    t2 : %t2%
  25. REM pause
  26. call :_etime t1 t2 tc2
  27. set /a tc2*=10
  28. echo 你的代码耗时 %tc2% 毫秒
  29. REM PAUSE
  30. REM 计算执行一次“你的代码”与执行一次“@echo off”的耗时比
  31. set/a rate=200*tc2/tc1
  32. echo 一次任务与一次“@echo off命令”耗时比=%rate%
  33. pause
  34. goto :EOF
  35. (
  36. :_etime <begin> <end> <ret> //求时差
  37.         echo %1 %2 %3
  38.         REM pause>nul
  39.         %localon%
  40.         Set /a c=(!%2:~,2!-!%1:~,2!)*360000+(1!%2:~3,2!-1!%1:~3,2!)*6000+1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!
  41.         echo c : %c%
  42.         set /a c+=-8640000*(c^>^>31)
  43.         echo c : %c%
  44.         (
  45.         %localoff%
  46.         set %3=%c%
  47.         )
  48.         echo c : %c%
  49.         goto :eof
  50. )
复制代码

TOP

回答第一个问题:
  1. setlocal enabledelayedexpansioon
  2. set "a=1"
  3. endlocal & echo,%a%
复制代码
第3行属于复合语句,预处理时endlocal & echo,%a% 是作为一个整体同时处理的,在此之前a已赋值为1,所以显示1
这个与
  1. set "a=1" & echo,%a%
复制代码
道理一样,这一句也是复合语句,在此之前a尚未赋值(为空),所以显示空

TOP

返回列表