Board logo

标题: 仿批处理命令call参数列表 调用 多参数的变量型函数 [打印本页]

作者: plp626    时间: 2011-12-20 20:31     标题: 仿批处理命令call参数列表 调用 多参数的变量型函数

最近和zm讨论中说到这个问题,之前的代码丢了,现在从自己盘里找了这个div子过程(例子比较典型)改造之:
  1. @echo off
  2. call:div 31 2003 1000 ans
  3. echo 31/2003 的前1000位小数为:
  4. echo %ans%
  5. pause&goto:eof
  6. :div <dividend> <divisor> <pre> <ret> //code by plp
  7. Setlocal Enabledelayedexpansion&set/a b=%2,R=%1%%b*10&set "dc="
  8. For /l %%z In (1 1 %3)Do set/a d=R/b,R=R%%b*10&set dc=!dc!!d!
  9. endlocal&set %4=%dc%
复制代码
这是典型的call调用,div子过程共4个参数,
第一个参数为被除数,第二个为除数,第三个为小数的位数(精度),第四个为返回的变量名
调用后子过程没有改变父环境变量值,因为有setlocal 和endlocal 。。。

讨论的问题是,如果用变量型函数像call这样“调用”多参数,代码如何写?
  1. @echo off
  2. :: 定义_div 变量型函数
  3. rem 代码。。。。。?
  4. %_div% 31 2003 1000 ans
  5. echo 31/2003 的前1000位小数为:
  6. echo %ans%
复制代码
  1. @echo off
  2. :: 定义_div 变量型函数 ;返回两个整数商的小数部分
  3. :: <dividend> <divisor> <pre> <ret> //code by plp
  4. Set "_div=setlocal enabledelayedexpansion&set n=&set argv=&for %%a in (1 2)do if defined argv ((for %%b in (^!argv^!)do set/a n+=1&set #^!n^!=%%b)&set/a b=^!#2^!,R=^!#1^!%%b*10&set dc=&(For /l %%z In (1 1 ^!#3^!)Do set/a d=R/b,R=R%%b*10&set dc=^!dc^!^!d^!)&for /f "tokens=1-2" %%A in (^"^^!#4^^! ^^!dc^^!^^")do endlocal&set %%A=%%B)else set argv="
  5. %_div% 31 2003 1000 ans
  6. echo 31/2003 的前1000位小数为:
  7. echo %ans%
  8. if "%~0"=="%~f0" set/p=
  9. goto:eof
  10. ___END___
  11. 思路大致是
  12. setlocal&set argv=
  13. for %%a in (1 2)do if defined argv (
  14.    for 获取argv的变量值用#“数组”存放,#1为第一个参数入口,#2为第二个参数入口。。。
  15.   你的变量型函数在这里展开。。。。
  16.   endlocal&退出并复制结果给返回变量名
  17. ) else set argv=
复制代码


对于变量型函数多参数的调用,有简单直接的思路(但要写代码者自己维护变量空间)
大家可参看此贴一楼的测试代码
http://www.bathome.net/viewthread.php?tid=11799

新手对变量型函数不太了解的,可参看变量型函数发源贴:
http://www.bathome.net/thread-5861-1-1.html
作者: CrLf    时间: 2011-12-20 20:52

绝了!
作者: qq2501    时间: 2011-12-20 21:34

看的我云里雾里
作者: CrLf    时间: 2011-12-20 22:05

回复 3# qq2501


    %_div% 31 2003 1000 ans 就是用第一次 for 循环将其后跟随的 31 2003 1000 ans 赋值给变量 :,再在第二次循环中引用,通过次序的颠倒,巧妙地在执行函数体之前,取得位于函数体之后的值。
    函数中不是有一句 for %%a in (1 1)do if defined : ( ... )else set :=  吗?这里的第一个次循环就是用来执行 else 进行取值的,赋值成功后,第二次循环才是执行函数内容
作者: CrLf    时间: 2011-12-21 07:00

本帖最后由 CrLf 于 2011-12-21 07:49 编辑

发一个将普通“格式化”为变量型函数的工具,格式化后的函数,只有用变量延迟直接或二次扩展才能动态地读取变量,而且原有空行、以 : 开头的行均被忽略。
但是思路比较粗糙,对 % 未进行成对判断,对开启变量延迟后含有 ! 的命令未做充分的二次转义处理(难度太大,等于要解析语法),并且不支持扩展参数,参数外的双引号必须被去除(其实也不是必须,但是条件太苛刻)。

合并行.bat :
  1. @echo off
  2. echo %*
  3. set "g=&"
  4. set "code=setlocal enabledelayedexpansion&set n=&set :=&for %%%%a in (1 1) do if defined : ((for %%%%b in (!:!) do set/a n+=1&set #!n!=%%%%b"
  5. for %%z in (%*) do (
  6. for /f "usebackeol=:tokens=*" %%b in ("%%~z") do (
  7. set str=%%b
  8. setlocal enabledelayedexpansion
  9. if !str:~^,1! neq ^) (set "str=(!str!") else set g=
  10. if !code:~-1! neq ^( (set "code=!code!)") else set g=
  11. set code=!code!!g!!str!
  12. for /f "delims=" %%c in ("!code!") do endlocal&set code=%%c
  13. )
  14. call :Test %%z
  15. )
  16. exit
  17. :Test
  18. set out=&setlocal enabledelayedexpansion
  19. set "code=!code!)) else set :="
  20. set b=!code!
  21. for /l %%a in (1 1 9) do set code=!code:%%%%a=$#%%a$!
  22. for /l %%a in (0 1 8000) do (
  23. set /a "n-=^!^!n"
  24. if !code:~%%a^,4!==$ (set "out=!out!^!"&set code=!code:~3!) else set out=!out!!code:~%%a,1!
  25. if !code:~%%a!.==. echo 合并后:!out!&echo !out!>1.cmd&endlocal&call 转义.bat 1.cmd "_%~n1"&pause&exit /b
  26. )
复制代码
转义.bat :
  1. @echo off&setlocal enabledelayedexpansion&set hh=^
  2. for %%a in ("!hh!") do (
  3. endlocal
  4. set out=
  5. set tmp=
  6. for /f "usebackdelims=" %%b in (%1) do set hs=%%b
  7. setlocal enabledelayedexpansion
  8. for /f "eol=;delims=" %%b in ("!hs:"^=%%~a"!") do (
  9. endlocal&set str=%%b&set /a n=!n
  10. setlocal enabledelayedexpansion
  11. if !n!==0 set "str=!str:^=^^!"
  12. if !n%%b neq n%%b (
  13. for /l %%c in (8101 -100 1) do if "!str:~%%c!"=="" set len=%%c
  14. for /l %%c in (0 1 !len!) do if !str:~%%c^,1!==^^! (
  15. if !n!==0 (set "tmp=!tmp!^^^^^^^!") else set "tmp=!tmp!^^^!"
  16. ) else set "tmp=!tmp!!str:~%%c,1!"
  17. set str=!tmp!
  18. )
  19. if !n!==0 for %%c in (^( ^) ^& ^| ^< ^>) do set "str=!str:%%c=^%%c!"
  20. for /f "eol=:delims=" %%c in ("!out!!str!") do endlocal&set out=%%c
  21. )
  22. )
  23. setlocal enabledelayedexpansion
  24. echo 转义后:!out!&echo set "%~2=!out!">>2.cmd
  25. pause
复制代码
样本:
  1. :strlen <stringVarName> [retvar]
  2. :: 思路: 二分回溯联合查表法
  3. :: 说明: 所求字符串大小范围 0K ~ 8K;
  4. ::    stringVarName ---- 存放字符串的变量名
  5. ::    retvar      ---- 接收字符长度的变量名
  6. :: 原帖:http://www.dostips.com/forum/viewtopic.php?f=3&t=1429
  7. setlocal enabledelayedexpansion
  8. set $=%1#
  9. set N=&for %%z in (4096 2048 1024 512 256 128 64 32 16)do if !$:~%%z!. NEQ . set/aN+=%%z&set $=!$:~%%z!
  10. set $=!$!fedcba9876543210&set/aN+=0x!$:~16,1!
  11. for /f "delims=" %%a in ("%2=!n!")do endlocal&set %%a
复制代码
拖动样本到  合并.bat  上的处理结果:
  1. set "_%%:=setlocal enabledelayedexpansion&set n=&set :=&for %%a in (1 1)do if defined : ((for %%b in (^!:^!)do set/a n+=1&set #^!n^!=%%b)&(setlocal enabledelayedexpansion)&(set $=^!#1^!#)&(set N=&for %%z in (4096 2048 1024 512 256 128 64 32 16)do if ^!$:~%%z^!. NEQ . set/aN+=%%z&set $=^!$:~%%z^!)&(set $=^!$^!fedcba9876543210&set/aN+=0x^!$:~16,1^!)&&(for /f "delims=" %%a in ("^^^!#2^^^!=^^^!n^^^!")do endlocal&set %%a))else set :="
复制代码

作者: CrLf    时间: 2011-12-21 08:07

DIY 一个定义全局函数的模板,部分靠先前的代码转换,部分靠手工(搞死人了...),变量型函数的全局声明和调用:
  1. @echo off
  2. call :dim strlen checkdate count
  3. setlocal enabledelayedexpansion
  4. %_strlen% basfdglksdfjlsdkj n
  5. echo "test测试Hello World" 中共有 %n% 个字符
  6. echo;
  7. echo 检查日期是否正确
  8. %_checkdate% 2011 2 29
  9. echo 2011年 2月 29日的检查结果如上
  10. echo;
  11. %_count% a ahahahahahahhhahahhhaha n1
  12. %_count% h ahahahahahahhhahahhhaha n2
  13. echo ahahahahahahhhahahhhaha 中有 %n1% 个 a、 %n2% 个 h
  14. pause&exit
  15. :dim
  16. for %%: in (%*)do (
  17. if /i %%:==strlen set "_%%:=setlocal enabledelayedexpansion&set n=&set :=&for %%a in (1 1)do if defined : ((for %%b in (^!:^!)do set/a n+=1&set #^!n^!=%%b)&setlocal enabledelayedexpansion&set $=^!#1^!#&(set N=&for %%z in (4096 2048 1024 512 256 128 64 32 16)do if ^!$:~%%z^!. NEQ . set/aN+=%%z&set $=^!$:~%%z^!)&set $=^!$^!fedcba9876543210&set/aN+=0x^!$:~16,1^!&&for /f "delims=" %%a in ("^^!#2^^!=^^!n^^!")do endlocal&set %%a)else set :="
  18. if /i %%:==checkdate set "_%%:=setlocal enabledelayedexpansion&set n=&set :=&for %%a in (1 1)do if defined : ((for %%b in (^!:^!)do set/a n+=1&set #^!n^!=%%b)&setlocal disabledelayedexpansion&set/a"y=#1,m=#2,d=#3,test=^^!^(y%%4^|^^!^(y%%100^)*^^!^^!^(y%%400^)^)*^^!^(m^^^^2^)+^(m+m/8^)%%2-2*^^!^(m^^^^2^)+30,0/^(test/d*^^!^(m/13^)^)"2>nul&&echo Right||echo Wrong)else set :="
  19. if /i %%:==count set "_%%:=setlocal enabledelayedexpansion&set n=&set :=&for %%a in (1 1)do if defined : ((for %%b in (^!:^!)do set/a n+=1&set #^!n^!=%%b)&setlocal enabledelayedexpansion&set key=:^!#1^!&set str=^!#2^!&set ret=^!#3^!&(for %%a in ("^^!_hh^^!")do for /f "delims=" %%b in ("^^!key^^!")do for /f "delims=" %%c in ("[^^!str%%b=%%~a]^^!")do set /a n+=1)&for /f "delims=" %%a in ("^^!ret^^!=^^!n^^!")do endlocal&set %%a)else set :="&set _hh=^
  20. )
复制代码

作者: netbenton    时间: 2011-12-21 08:33

本帖最后由 netbenton 于 2011-12-21 10:06 编辑

晕倒,我发错地方了,当灌水了。
研究了一下,真的不错
作者: plp626    时间: 2011-12-21 09:40

本帖最后由 plp626 于 2011-12-21 10:05 编辑

回复 7# netbenton

我这是拾人牙慧;
话说很少见netben兄发帖哈,
---------
忽然觉得此帖合并到兄的那个发源贴后有利于坛友讨论,和阅读。
申请合并。
作者: netbenton    时间: 2011-12-21 11:36

不错呀~~
这样可以增加了通用性和可读性


::水话::
没有那么多时间玩BAT了,看到强铁,不得不顶~~
::水话::
作者: Demon    时间: 2011-12-21 15:45

2011年国际Batch混乱代码大赛第一名。
作者: CrLf    时间: 2011-12-21 16:22

回复 3# qq2501


回复 10# Demon


原文跨度太大,直接从普通函数成型为改进的变量型函数了,缺乏过渡确实不太容易理解,我还是“翻译”一下吧,希望有助于大家的理解。

当原文中声明了 _div 这个变量型函数之后,运行以下代码:
  1. %_div% 31 2003 1000 ans
复制代码
的实际效果等同于执行了以下代码:
  1. setlocal enabledelayedexpansion
  2. set n=
  3. set argv=
  4. for %%a in (1 2) do (
  5.     if defined argv (
  6.         rem 因为此时 argv 为空,所以先执行的是 else,即 set argv= 31 2003 1000 ans
  7.         for %%b in (!argv!)do set/a n+=1&set #!n!=%%b
  8.         rem 第 2 步,将获取的参数分别设为 #1、#2 一直到 #N
  9.         rem 在例子中,共有四个参数:#1=31,#2=2003,#3=1000,#4=ans
  10.         set/a b=!#2!,R=!#1!%%b*10&set dc=
  11.         For /l %%z In (1 1 !#3!)Do set/a d=R/b,R=R%%b*10&set dc=!dc!!d!
  12.         rem 第 3 步,调用函数主体,针对参数进行操作
  13.         for /f "tokens=1-2" %%A in ("!#4! !dc!")do endlocal&set %%A=%%B
  14.         rem 第 4 步,结束上一个 setlocal 并利用 for 保留变量
  15.     )else (
  16.         set argv= 31 2003 1000 ans
  17.         rem 第 1 步:将参数赋值
  18.     )
  19. )
  20. rem 最终结果存储在 ans 变量中。
复制代码
核心思想就是利用 for+if 先执行后面的代码(进行赋值),再回过头来执行前面的代码(具体操作)。
这样解释不知还有难理解的地方吗?
作者: cjiabing    时间: 2011-12-22 22:56

天马行空,眼花缭乱。我看看看!
作者: powerbat    时间: 2011-12-23 23:20

偶也这么用过,嘻嘻。但个人对批处理函数不怎么感兴趣(如果bat稍微复杂,我往往用其他脚本)。

依照C语言,这种形式叫“宏函数”比较合适。
作者: wankoilz    时间: 2012-1-7 09:42

这个从后面获得参数的方法确实巧妙,高!
作者: garyng    时间: 2012-6-11 11:45

这方法很巧妙!
作者: 爱小宝儿    时间: 2012-6-18 16:59

呵呵,谢谢楼主了~~~~~~~~~~~
作者: a415987611    时间: 2012-7-14 19:39

函数什么的,总感觉会影响效率,
函数内部多出了for、if、set,又用setlocal enabledelayedexpansion、endlocal

有时还是宁愿复制、粘贴
作者: zh_1452    时间: 2014-6-21 07:09

  1. echo 这个内容太长了一时半伙看不懂拿回家好好研究一下
复制代码





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