批处理之家's Archiver

plp626 发表于 2011-12-20 20:31

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

最近和zm讨论中说到这个问题,之前的代码丢了,现在从自己盘里找了这个div子过程(例子比较典型)改造之:[code]@echo off
call:div 31 2003 1000 ans
echo 31/2003 的前1000位小数为:
echo %ans%
pause&goto:eof

:div <dividend> <divisor> <pre> <ret> //code by plp
Setlocal Enabledelayedexpansion&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!
endlocal&set %4=%dc%[/code]这是典型的call调用,div子过程共4个参数,
第一个参数为被除数,第二个为除数,第三个为小数的位数(精度),第四个为返回的变量名
调用后子过程没有改变父环境变量值,因为有setlocal 和endlocal 。。。

讨论的问题是,如果用变量型函数像call这样“调用”多参数,代码如何写?[code]@echo off
:: 定义_div 变量型函数
rem 代码。。。。。?

%_div% 31 2003 1000 ans
echo 31/2003 的前1000位小数为:
echo %ans%[/code][color=LemonChiffon][code]@echo off

:: 定义_div 变量型函数 ;返回两个整数商的小数部分
:: <dividend> <divisor> <pre> <ret> //code by plp
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="


%_div% 31 2003 1000 ans
echo 31/2003 的前1000位小数为:
echo %ans%

if "%~0"=="%~f0" set/p=
goto:eof
___END___

思路大致是
setlocal&set argv=
for %%a in (1 2)do if defined argv (
   for 获取argv的变量值用#“数组”存放,#1为第一个参数入口,#2为第二个参数入口。。。
  你的变量型函数在这里展开。。。。
  endlocal&退出并复制结果给返回变量名
) else set argv=
[/code][/color]

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

新手对变量型函数不太了解的,可参看变量型函数发源贴:
[url=http://www.bathome.net/thread-5861-1-1.html]http://www.bathome.net/thread-5861-1-1.html[/url]

CrLf 发表于 2011-12-20 20:52

绝了!

qq2501 发表于 2011-12-20 21:34

看的我云里雾里

CrLf 发表于 2011-12-20 22:05

[b]回复 [url=http://bbs.bathome.net/redirect.php?goto=findpost&pid=99115&ptid=15051]3#[/url] [i]qq2501[/i] [/b]


    %_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

[i=s] 本帖最后由 CrLf 于 2011-12-21 07:49 编辑 [/i]

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

合并行.bat :[code]@echo off
echo %*
set "g=&"
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"
for %%z in (%*) do (
        for /f "usebackeol=:tokens=*" %%b in ("%%~z") do (
                set str=%%b
                setlocal enabledelayedexpansion
                if !str:~^,1! neq ^) (set "str=(!str!") else set g=
                if !code:~-1! neq ^( (set "code=!code!)") else set g=
                set code=!code!!g!!str!
                for /f "delims=" %%c in ("!code!") do endlocal&set code=%%c
        )
        call :Test %%z
)
exit
:Test
set out=&setlocal enabledelayedexpansion
set "code=!code!)) else set :="
set b=!code!
for /l %%a in (1 1 9) do set code=!code:%%%%a=$#%%a$!
for /l %%a in (0 1 8000) do (
        set /a "n-=^!^!n"
        if !code:~%%a^,4!==$ (set "out=!out!^!"&set code=!code:~3!) else set out=!out!!code:~%%a,1!
        if !code:~%%a!.==. echo 合并后:!out!&echo !out!>1.cmd&endlocal&call 转义.bat 1.cmd "_%~n1"&pause&exit /b
)[/code]转义.bat :[code]@echo off&setlocal enabledelayedexpansion&set hh=^


for %%a in ("!hh!") do (
        endlocal
        set out=
        set tmp=
        for /f "usebackdelims=" %%b in (%1) do set hs=%%b
        setlocal enabledelayedexpansion
        for /f "eol=;delims=" %%b in ("!hs:"^=%%~a"!") do (
                endlocal&set str=%%b&set /a n=!n
                setlocal enabledelayedexpansion
                if !n!==0 set "str=!str:^=^^!"
                if !n%%b neq n%%b (
                        for /l %%c in (8101 -100 1) do if "!str:~%%c!"=="" set len=%%c
                        for /l %%c in (0 1 !len!) do if !str:~%%c^,1!==^^! (
                                if !n!==0 (set "tmp=!tmp!^^^^^^^!") else set "tmp=!tmp!^^^!"
                        ) else set "tmp=!tmp!!str:~%%c,1!"
                        set str=!tmp!
                )
                if !n!==0 for %%c in (^( ^) ^& ^| ^< ^>) do set "str=!str:%%c=^%%c!"
                for /f "eol=:delims=" %%c in ("!out!!str!") do endlocal&set out=%%c
        )
)
setlocal enabledelayedexpansion
echo 转义后:!out!&echo set "%~2=!out!">>2.cmd
pause[/code]样本:[code]:strlen <stringVarName> [retvar]
:: 思路: 二分回溯联合查表法
:: 说明: 所求字符串大小范围 0K ~ 8K;
::    stringVarName ---- 存放字符串的变量名
::    retvar      ---- 接收字符长度的变量名
:: 原帖:http://www.dostips.com/forum/viewtopic.php?f=3&t=1429

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[/code]拖动样本到  合并.bat  上的处理结果:[code]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 :="[/code]

CrLf 发表于 2011-12-21 08:07

DIY 一个定义全局函数的模板,部分靠先前的代码转换,部分靠手工(搞死人了...),变量型函数的全局声明和调用:[code]@echo off
call :dim strlen checkdate count
setlocal enabledelayedexpansion
%_strlen% basfdglksdfjlsdkj n
echo "test测试Hello World" 中共有 %n% 个字符
echo;
echo 检查日期是否正确
%_checkdate% 2011 2 29
echo 2011年 2月 29日的检查结果如上
echo;
%_count% a ahahahahahahhhahahhhaha n1
%_count% h ahahahahahahhhahahhhaha n2
echo ahahahahahahhhahahhhaha 中有 %n1% 个 a、 %n2% 个 h
pause&exit
:dim
for %%: in (%*)do (
        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 :="
        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 :="
        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=^


)[/code]

netbenton 发表于 2011-12-21 08:33

[i=s] 本帖最后由 netbenton 于 2011-12-21 10:06 编辑 [/i]

晕倒,我发错地方了,当灌水了。
研究了一下,真的不错

plp626 发表于 2011-12-21 09:40

[i=s] 本帖最后由 plp626 于 2011-12-21 10:05 编辑 [/i]

[b]回复 [url=http://www.bathome.net/redirect.php?goto=findpost&pid=99169&ptid=15051]7#[/url] [i]netbenton[/i] [/b]

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

netbenton 发表于 2011-12-21 11:36

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


::水话::
没有那么多时间玩BAT了,看到强铁,不得不顶~~
::水话::

Demon 发表于 2011-12-21 15:45

2011年国际Batch混乱代码大赛第一名。

CrLf 发表于 2011-12-21 16:22

[b]回复 [url=http://bbs.bathome.net/redirect.php?goto=findpost&pid=99115&ptid=15051]3#[/url] [i]qq2501[/i] [/b]


[b]回复 [url=http://bbs.bathome.net/redirect.php?goto=findpost&pid=99194&ptid=15051]10#[/url] [i]Demon[/i] [/b]


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

当原文中声明了 _div 这个变量型函数之后,运行以下代码:[code]%_div% 31 2003 1000 ans[/code]的实际效果等同于执行了以下代码:[code]setlocal enabledelayedexpansion
set n=
set argv=
for %%a in (1 2) do (
    if defined argv (
        rem 因为此时 argv 为空,所以先执行的是 else,即 set argv= 31 2003 1000 ans
        for %%b in (!argv!)do set/a n+=1&set #!n!=%%b
        rem 第 2 步,将获取的参数分别设为 #1、#2 一直到 #N
        rem 在例子中,共有四个参数:#1=31,#2=2003,#3=1000,#4=ans
        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!
        rem 第 3 步,调用函数主体,针对参数进行操作
        for /f "tokens=1-2" %%A in ("!#4! !dc!")do endlocal&set %%A=%%B
        rem 第 4 步,结束上一个 setlocal 并利用 for 保留变量
    )else (
        set argv= 31 2003 1000 ans
        rem 第 1 步:将参数赋值
    )
)
rem 最终结果存储在 ans 变量中。[/code]核心思想就是利用 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]

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