找回密码
 注册
搜索
[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程[批处理精品]批处理版照片整理器
[批处理精品]纯批处理备份&还原驱动[批处理精品]CMD命令50条不能说的秘密[在线下载]第三方命令行工具[在线帮助]VBScript / JScript 在线参考
查看: 72894|回复: 17

仿批处理命令call参数列表 调用 多参数的变量型函数

[复制链接]
发表于 2011-12-20 20:31:28 | 显示全部楼层 |阅读模式
最近和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

评分

参与人数 1PB +10 技术 +2 收起 理由
CrLf + 10 + 2 原来可以这样,很巧妙!

查看全部评分

发表于 2011-12-20 20:52:12 | 显示全部楼层
绝了!
发表于 2011-12-20 21:34:24 | 显示全部楼层
看的我云里雾里
发表于 2011-12-20 22:05:31 | 显示全部楼层
回复 3# qq2501


    %_div% 31 2003 1000 ans 就是用第一次 for 循环将其后跟随的 31 2003 1000 ans 赋值给变量 :,再在第二次循环中引用,通过次序的颠倒,巧妙地在执行函数体之前,取得位于函数体之后的值。
    函数中不是有一句 for %%a in (1 1)do if defined : ( ... )else set :=  吗?这里的第一个次循环就是用来执行 else 进行取值的,赋值成功后,第二次循环才是执行函数内容
发表于 2011-12-21 07:00:47 | 显示全部楼层
本帖最后由 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 :="
复制代码

评分

参与人数 1技术 +1 收起 理由
plp626 + 1 不错,细节问题再优化

查看全部评分

发表于 2011-12-21 08:07:20 | 显示全部楼层
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. )
复制代码
发表于 2011-12-21 08:33:53 | 显示全部楼层
本帖最后由 netbenton 于 2011-12-21 10:06 编辑

晕倒,我发错地方了,当灌水了。
研究了一下,真的不错
 楼主| 发表于 2011-12-21 09:40:17 | 显示全部楼层
本帖最后由 plp626 于 2011-12-21 10:05 编辑

回复 7# netbenton

我这是拾人牙慧;
话说很少见netben兄发帖哈,
---------
忽然觉得此帖合并到兄的那个发源贴后有利于坛友讨论,和阅读。
申请合并。
发表于 2011-12-21 11:36:42 | 显示全部楼层
不错呀~~
这样可以增加了通用性和可读性


::水话::
没有那么多时间玩BAT了,看到强铁,不得不顶~~
::水话::
发表于 2011-12-21 15:45:16 | 显示全部楼层
2011年国际Batch混乱代码大赛第一名。
发表于 2011-12-21 16:22:19 | 显示全部楼层
回复 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 先执行后面的代码(进行赋值),再回过头来执行前面的代码(具体操作)。
这样解释不知还有难理解的地方吗?
发表于 2011-12-22 22:56:38 | 显示全部楼层
天马行空,眼花缭乱。我看看看!
发表于 2011-12-23 23:20:08 | 显示全部楼层
偶也这么用过,嘻嘻。但个人对批处理函数不怎么感兴趣(如果bat稍微复杂,我往往用其他脚本)。

依照C语言,这种形式叫“宏函数”比较合适。
发表于 2012-1-7 09:42:22 | 显示全部楼层
这个从后面获得参数的方法确实巧妙,高!
发表于 2012-6-11 11:45:15 | 显示全部楼层
这方法很巧妙!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|批处理之家 ( 渝ICP备10000708号 )

GMT+8, 2026-3-16 21:43 , Processed in 0.011353 second(s), 8 queries , File On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表