|
bagpipe
银牌会员
DOS联盟捡破烂的
积分 1144
发帖 425
注册 2005-10-20 来自 北京
状态 离线
|
『楼 主』:
[推荐][讨论]K&H home - NT/Win2k scripting
标题:command line parameter parsing
链接:http://www.differentchairs.com/cmdtail.htm
标题:NT/Win2k scripting
链接:http://www.differentchairs.com/ntscript.html
Quote: | Commands
Perhaps the handiest of enhancements is in the FOR command. This now provides extended functionality to support things like list processing, loop counting and directory recursion. In addition, FOR variable references (and command line argument references) support an extended syntax for modifying their value. For example, %~pi resolves to the path for this iteration of the i variable in the loop, and %~f2 evaluates to the fully qualified filespec of the second command line argument for the batch. FOR can be used, for example, to manipulate dates:
FOR /f "tokens=2-4 delims=/ " %%a in ('DATE/T') do SET date=%%c-%%a-%%b
My DATE/T output looks like:
Wed 05/27/1998
And the above command sets the corresponding "date" variable to 1998-05-27. (This would override the default behavior of %date%, if that name happens to be magic in your version of Windows.)
(Remember to enter the FOR replaceable parameter in a batch program, as opposed to from the command line, as %%a instead of %a.)
Another useful change appeared in the form of an extension to the SET command. Set can now evaluate arithmetic expressions.
(The following will display the number of files in a directory hierarchy)
FOR /f %a in ('DIR/s ^|FIND " File(s) " /c') do @FOR /f %b in ('SET /a dummy^=%a-1') do @(DIR/s/-c |FIND " File(s) "|MORE /e +%b)
Linebreaks can be used with parentheses to make complex commands more readable.
FOR /f %a in ('DIR/s ^|FIND " File(s) " /c') do @(
FOR /f %b in ('SET /a dummy^=%a-1') do @(
DIR/s/-c |FIND " File(s) "|MORE /e +%b))
IF statements support a form of ELSE clause. Note that, though this was introduced in version 4.0, it doesn't seem to be officially documented anywhere in that version. The feature is fully documented in Win2000 (NT5.0), and so it should be safe in 4.0 scripts, even where forward compatibility is a concern.
IF defined mytest1 (
rem /* -- rem comments and blank lines work -- */
set tested=1
) ELSE IF errorlevel 1 (
rem /* -- note that an ELSE must be on the same line as the closing paren -- */
echo There was an error
set tested=2
) ELSE (
set tested=0
)
Environment variables can now be evaluated to support better string manipulation:
FOR /F %%a IN ('dir %temp%*^|find "<" ') DO SET aDate=%%a
:: pick out a substring
SET DateSeparator=%aDate:~2,1%
for /f "tokens=*" %%a in ( 'time/t' ) do set time=%%a
:: change spaces to zeros
SET ZeroPaddedTime=%time: =0%
Environment variable that get evaluated within a FOR loop get expanded only at the beginning of the loop. So the following snippet does not produce the expected result:
set count=1
FOR /f "tokens=*" %%a in ('dir /b') do (
echo %count%: %%a
set /a count+=1)
To force the variable to get evaluated at the proper iteration, use the new subroutine call mechanism to take the evaluation outside of the loop.
set count=1
FOR /f "tokens=*" %%a in ('dir /b') do (
call :exec echo %%count%%: %%a
set /a count+=1)
goto :EOF
:exec
%*
goto :EOF
The above can be replaced with the following equivalent script (But be aware, for when it matters, that the "call call" replacement can be relatively slow. The :exec method is more than seven times faster on the XP pro box I'm currently using).
set count=1
FOR /f "tokens=*" %%a in ('dir /b') do (
call call echo %%count%%: %%a
set /a count+=1)
goto :EOF
Newer versions of Windows also support a DELAYEDEXPANSION property to address the variable evaluation issue. I think the syntax is too quirky to be usable, but it may solve a problem for you. See the help associated with SET and SETLOCAL for further info.
Another change worth noting is the new FINDSTR filter utility, which among other capabilities, can do a regular expression search.
The following command will create a text file (CMD_HELP.TXT) that contains usage notes for most of the NT commands that were extended in 4.0
(Help &echo ****** &(FOR %a in (
CMD DEL COLOR CD FOR IF MD PROMPT PUSHD POPD SET SETLOCAL ENDLOCAL CALL SHIFT GOTO START ASSOC FTYPE MORE FINDSTR TITLE AT) do @(
echo * %a
echo ******
(CMD /C%a /?) & echo.&echo ******)))>CMD_HELP.TXT
Miscellany
Redirection
Error output can be redirected.
DIR *.txt > out.txt
sends a directory listing to the file out.txt, while
DIR *.txt 2> out.err
sends any error messages from the command to the file out.err output can be combined using syntax borrowed from unix.
DIR nonexistant > out.all 2>&1
sends the directory listing and any error messages to the file out.all
Conditional Processing
An ampersand on the command line can be used to unconditionally combine commands on a single line. Use conditional symbols (&& and ||) to combine commands in such a way that the result of the preceeding command determines whether the following command gets executed. For example, the "del file2" command below only executes if "dir file1" returns 0
dir file1 >nul && del file2
The above is approximately equivalent to: IF EXIST file1 DEL file2
The "del fileB" below only executes if "dir fileA" returns nonzero.
dir fileA >nul || del fileB
The above is approximately equivalent to: IF NOT EXIST fileA DEL fileB
You can also handle more complex processing:
[editors note: the following example used to contain a ridiculously complex statement littered with the conditional symbols. It turned out to be buggy. Take heed. Don't do a thing just because you can. (Not even to show off...) Do it if the result is cleaner than with another method. Complex conditions will often be best written as explicit IF/ELSE statements, as is now done below.]
::::::::::::::::
@echo off
:: File: MYBAT.bat
:: Description: Demo. Shows how one might iterate over the items in a
:: list (defined in an environment variable) and do
:: some operation on each item. Can optionally specify
:: which items to start and stop on in the list.
setlocal
:nxtarg0
shift
:: any more arguments?
if %0.==. goto parsed
if /i %0==/StartAt goto setstart
if /i %0==/StopAt goto setstop
goto help
:setstart
set StartAt=%1
shift
goto nxtarg0
:setstop
set StopAt=%1
shift
goto nxtarg0
:parsed
if not defined PROJECTLIST set PROJECTLIST=P1;P2;P3;P4;P5;P6
echo on
set PastStart=
if not defined StartAt set PastStart=1
for %%p in (%PROJECTLIST%) do (
(if not defined PastStart (
if /I "%StartAt%"=="%%p" (
set PastStart=1)))
if defined PastStart call :ShowProject %%p
if /I "%StopAt%"=="%%p" goto DoneWithList
)
:DoneWithList
goto cleanup
::
::
::
:help
echo.
echo.
echo usage: MYBAT [/StartAt startprj] [/StopAt stopprj]
echo.
echo (e.g., MYBAT /StartAt P2)
echo.
echo purpose: just a demo...
echo.
:cleanup
endlocal
goto :EOF
:ShowProject
echo Project: %1
goto :EOF
Note that the above wouldn't work as expected if any of the PROJECTLIST items contained spaces (e.g., "P 2"). For clues on how this might be remedied, consider the following
::::::::::::::::
call :listtokens "%PROJECTLIST%"
goto :EOF
:listtokens
if not %1.==. (
for /f "tokens=1* delims=;" %%a in (%1) do (
echo %%a & call :listtokens "%%b"))
goto :EOF
::::::::::::::::
The above would list all items in PROJECTLIST, even those with spaces.
The following tests a collection of environment variable and command parameter utility functions.
::::::::::::::::::::::::::::::::::::
@echo off
set test=
echo -[%test%]-
call :testcaseChk blank test
set test=" "
echo -[%test%]-
call :testcaseChk QuotedSpaceTest test
set test=%test:"=%
echo -[%test%]-
call :testcaseChk spaceTest test
set test=x
echo -[%test%]-
call :testcaseChk simple test
echo **[12345678901234567890123]**
set test=" ab c def 123 "
echo **[%test%]**
call :testcaseChk quoted test
set test=%test:"=%
echo ** unquoted [%test%]
call :testcaseChk unquoted test
call :rtrimvar test
echo ** trimmed [%test%]
call :testcaseChk trimmed test
:: reinit our unquoted test
set test=" ab c def 123 "
set test=%test:"=%
echo ltrim test -[%test%]-
call :ltrimvar test
echo ** ltrimmed [%test%]
call :testcaseChk ltrimmed test
goto :EOF
::::::::::::::::
::--------------
:: %1 is testcase name
:: %2 is env var name to test
::--------------
:testcaseChk
call :varLength %2
echo %1 %2 length is (%varLength%)
call call :argCt %%%2%%
echo %2 argCt is %argCt%
call call :firstArg %%%2%%
echo %2 firstArg is %firstArg%
call call :lastArg %%%2%%
echo %2 lastArg is %lastArg%
echo.
goto :eof
::--------------
:: Trim leading and trailing spaces in an environment variable.
:: 1st (and only) parameter is the name of an evironment variable
:: that holds the unquoted value to be trimmed in place
::--------------
:trimvar
call :rtrimvar %1
call :ltrimvar %1
goto :EOF
::--------------
:: Trim (in place, from right end) trailing spaces in an environment variable.
:: 1st (and only) parameter is the name of an evironment variable
:: that holds the *unquoted* value to be trimmed in place
::--------------
:rtrimvar
if not defined %1 goto :eof
setlocal
rem deReference
call set rtv_trimbuf=%%%1%%
:chkTrim
if not defined rtv_trimbuf goto rtrimDone
if not "%rtv_trimbuf:~-1%"==" " if not "%rtv_trimbuf:~-1%"==" " (
goto rtrimDone
)
set rtv_trimbuf=%rtv_trimbuf:~0,-1%
goto chkTrim
:rtrimDone
endlocal & set %1=%rtv_trimbuf%
goto :EOF
::--------------
:: Trim (in place, from left end) leading spaces in an environment variable.
:: 1st (and only) parameter is the name of an evironment variable
:: that holds the *unquoted* value to be trimmed in place
::--------------
:ltrimvar
if not defined %1 goto :eof
setlocal
rem deReference
call set rtv_ltrimbuf=%%%1%%
:chkLTrim
if not defined rtv_ltrimbuf goto ltrimDone
if not "%rtv_ltrimbuf:~0,1%"==" " if not "%rtv_ltrimbuf:~0,1%"==" " (
goto ltrimDone
)
set rtv_ltrimbuf=%rtv_ltrimbuf:~1%
goto chkLTrim
:ltrimDone
endlocal & set %1=%rtv_ltrimbuf%
goto :EOF
::--------------
:: argCt(...) = number of arguments in the given list
::--------------
:argCt
set argCt=0
:argCtChk
if %1.==. goto :eof
set /a argCt+=1
shift
goto argCtChk
::--------------
:: varLength(*var) = length of vlaue in given env variable
::--------------
:varLength
setlocal
set vl_buf=
set vl_len=0
if not defined %1 goto lenDone
call set vl_buf=%%%1%%
:chkLen
if not defined vl_buf goto lenDone
set vl_buf=%vl_buf:~0,-1%
set /a vl_len+=1
goto chkLen
:lenDone
endlocal & set varLength=%vl_len%
goto :EOF
::--------------
:: lastArg(...) = value of last argument in the given list
::--------------
:lastArg
set lastArg=
:nxtLastArg
if %1.==. goto :eof
shift
set lastArg=%0
goto nxtLastArg
::--------------
:: firstArg(...) = value of first argument in the given list
::--------------
:firstArg
set firstArg=%1
goto :eof
::::::::::::::::::::::::::::::::::::
Lets finish up with some more complete examples.
:: HowConnected.bat
:: tell me the username with which i'm connected to a remote NT computer.
:: (assumes the local scheduler is running, and messaging is active on both machines)
@echo off
setlocal
set remotepc=%1
if not defined remotepc set remotepc=%computername%
call :rsoon cmd.exe "/c for /f \"tokens=1-2\" %%%%a in ('net session') do if /i %%%%a==\\%remotepc% NET SEND %remotepc% %%%%b"
endlocal
goto :EOF
:: soon.exe from the reskit strips out quotes, so we roll our own
:: (hardwired to 50 second bias)
:rsoon
setlocal
:: my time fmt [The current time is: 5:23:42.49]
for /f "tokens=1* delims=:" %%a in ('echo.^|time^|findstr "[0-9]"') do (
set rawtime=%%b)
:: a stab at portability...
set delim1=%rawtime:~3,1%
set delim2=%rawtime:~9,1%
for /f "tokens=1-3 delims=%delim1%%delim2% " %%a in ('echo %rawtime%') do (
set hr=%%a
set min=%%b
set sec=%%c)
if %sec:~0,1%==0 set sec=%sec:~1%
if %min:~0,1%==0 set min=%min:~1%
if %hr:~0,1%==0 set hr=%hr:~1%
set /a sec+=50
if %sec% GTR 59 (
set /a sec%%=60
set /a min+=1
if %min% GTR 59 (
set /a min%%=60
set /a hr+=1
set /a hr%%=24))
at.exe \\%remotepc% %hr%:%min%:%sec% %*
endlocal
@echo off
:: here's an nt batch file that works as a simple cgi server
:: blame: steve hardy
:: shardy@differentchairs.com
if %REQUEST_METHOD%.==GET. goto GET
if %REQUEST_METHOD%.==POST. goto POST
GOTO fini
:: i have to cheat for the POST method, since batch doesn't want to read
:: stdin without newline, which is what the web server provides.
:: I do it with a line of perl, though of course that begs the question - if
:: you have perl, why bother with batch...?
:POST
::for /f %%a in ('getstrn.exe %HTTP_CONTENT_LENGTH%') DO SET QUERY_STRING=%%a
for /f %%a in ('perl -e "my $s; read STDIN,$s,%HTTP_CONTENT_LENGTH%; print $s"') DO SET QUERY_STRING=%%a
:GET
if defined QUERY_STRING call :parseQS "%QUERY_STRING:+= %"
:fini
echo Content-Type: text/html
echo.
echo.
:: caller can define page title...
ifndef title goto body
echo.
echo.
echo ^<head^>
echo ^<TITLE^>%title%^</TITLE^>
echo ^</head^>
:body
echo.
echo ^<body^>
echo ^<PRE^>
:: insert text/functionality here
:: any output shows up on the page.
date/t
time/t
:: uncomment the following for debugging
::set
echo ^</PRE^>
echo ^</body^>
exit
:: put QUERY_STRING format string into environment variable(s)
:: (unescapes left as an exercise...)
:parseQS
if %1.==. goto :EOF
FOR /f "tokens=1* delims=&" %%a in (%*) DO (
CALL :setval "%%a"
CALL :parseQS "%%b")
goto :EOF
:setval
FOR /f "tokens=1,2 delims==" %%c in (%1) DO (
SET %%c=%%d
IF %%d.==. SET %%c=+)
goto :EOF
::*************************************
:: Sub: relpath
::
:: descr: Get relative path spec
:: usage: relpath rtnvar fspec [basedir]
::
:: e.g.: relpath mypath \a\b\c.txt \a\z
:: sets mypath to .\..\b\c.txt
::*************************************
@echo off
:relpath
if %1.==/?. goto rphelp
if %2.==. goto rphelp
if not %4.==. goto rphelp
setlocal
set basedir=%~f3
:: basedir defaults to current directory
if %3.==. for /f "tokens=*" %%a in ('cd') do set basedir=%%a
set fspec=%~f2
for %%a in (%fspec%) do if %%~da==\\ goto uncpath
:localpath
for /f "delims=\" %%a in ("%basedir%") do (
for /f "delims=\" %%b in ("%fspec%") do (
if /i not "%%a"=="%%b" goto diffdrverr
)
)
goto drvok
:uncpath
for /f "tokens=1,2 delims=\" %%a in ("%basedir%") do (
for /f "delims=\" %%y in ("%fspec%") do (
if /i not "%%a"=="%%y" goto diffdrverr
if /i not "%%b"=="%%z" goto diffdrverr
)
)
:drvok
set basedir=%basedir:"=%
set fspec=%fspec:"=%
set bkp=.
set fspec0=%fspec%
set try0=%basedir%
:trybase
set try=%try0%
call :replstr fspec0 "%try%"
if not defined fspec0 goto gotbase
if /i not "%fspec%"=="%fspec0%" goto gotbase
set bkp=%bkp%\..
for %%a in ("%try0%") do set try0=%%~dpa
:: trim trailing backslash, except on x:\
call :varLength try0
set /a try0end=varLength-1
if %varLength% GTR 3 (
call :exec if "\"=="%%try0:~%try0end%%%" set try0=%%try0:~0,%try0end%%%
)
goto trybase
:gotbase
set sep=
if defined fspec0 call :exec if not %fspec0:~0,1%==\ set sep=\
:: undocumented cleanup trick (exports %1 from local context)...
endlocal & set %1=%bkp%%sep%%fspec0%
goto :EOF
:replstr
:: %1->sourcvar %2->string to replace/del [%3]->optional string to replace with
set p2=%2
if defined p2 set p2=%p2:"=%
set p3=%3
if defined p3 set p3=%p3:"=%
call :exec set %1=%%%1:%p2%=%p3%%%
goto :EOF
::--------------
:: varLength(*var) = length of vlaue in given env variable
::--------------
:varLength
setlocal
set vl_buf=
set vl_len=0
if not defined %1 goto lenDone
call set vl_buf=%%%1%%
:chkLen
if not defined vl_buf goto lenDone
set vl_buf=%vl_buf:~0,-1%
set /a vl_len+=1
goto chkLen
:lenDone
endlocal & set varLength=%vl_len%
goto :EOF
:exec
%*
goto :EOF
:diffdrverr
echo.
echo fspec and base aren't on same drive. Aborting. >&2
echo.
goto :EOF
:rphelp
echo usage: relpath rtnvar fspec [basedir] >&2
goto :EOF
::*************
:: numdir.bat
:: print numbered list of files in current directory
:: (one way to count while iterating)
::*************
@echo off
setlocal
set _i=0
for %%a in (*) do (
call :exec set /a _i+=1
call :exec echo %%_i%%: %%a
)
endlocal
goto :eof
:exec
%*
goto :eof
::**************
:: NT4 batch environment based stack operations
:: blame: steve hardy
:: shardy@differentchairs.com
:: a stack is a (LIFO) list of semicolon (;) delimited
:: strings stored in an environment variable.
:: New strings are added (push'd) on, or removed (pop'd)
:: from, the front of the list.
:: limitation:
:: beware the cmd.exe bug (gpf) that manifests if FOR/f
:: evaluates a command that retuns a line of length greater
:: than 265. (e.g., when our stack exceeds this length)
::
:: reproduce the bug via: for /f %a in ('echo x..x') do ver
:: (where x..x is at least 266 characters)
::
:: The bug is fixed under the NT5/Win2k CMD.exe shell,
:: which can be coaxed to run under NT4 (sp4 and better), and
:: provides additional functionality
::**************
@echo off
:teststack
set mystack=
call :pushtoken mystack "first token=1"
call :pushtoken mystack "2nd token"
call :pushtoken mystack "3rd"
call :printstack mystack
call :poptoken mystack topval
call :poptoken mystack
call :poptoken mystack topval
goto :EOF
::--------------
:pushtoken
:: %1 -> stackvarname %2 -> value to push
if %2.==. goto :EOF
if not defined %1 goto newstack
::everything after the first '='
for /f "tokens=1* delims==" %%x in ('set %1') do (
for /f "delims=;" %%z in (%2) do if /I %%x==%1 (
call :exec if /I "%%x" EQU "%1" set "%1=%%z;%%y"))
goto :EOF
:newstack
for /f "delims=;" %%y in (%2) do (
set %1=%%y)
goto :EOF
::--------------
:poptoken
:: %1 -> stackvarname [%2 varname to receive pop'd val]
if %1.==. goto :EOF
:: set output value, if called for
if not %2.==. (
for /f "tokens=1* delims==" %%w in ('set "%1"') do (
for /f "tokens=1* delims=;" %%y in ("%%x") do (
call :exec if /I %%w EQU %1 set "%2=%%y")))
:: delete top value
for /f "tokens=1* delims==" %%w in ('set "%1"') do (
for /f "tokens=1* delims=;" %%y in ("%%x") do (
call :exec if /I %%w EQU %1 set "%1=%%z" ))
goto :EOF
::--------------
:printstack
:: %1 -> stackvarname
:: dereference the argument, add quotes and pass along
FOR /f "tokens=1* delims==" %%y IN ('set %1') DO (
call :exec if /I "%%y" EQU "%1" call :printtokens "%%z")
goto :EOF
::--------------
:printtokens
:: %1 -> quoted list of tokens
if not %1.==. (
for /f "tokens=1* delims=;" %%y in (%1) do (
echo %%y & call :printtokens "%%z"))
goto :EOF
::--------------
:exec
:: can use this to run commands that FOR won't handle inline
%*
goto :EOF
::**************
:: np.bat
:: here's my single most frequently used batch file.
:: (a wrapper to add wildcard/multi-file support to notepad commandline)
::**************
@echo off
:top
if %1.==. goto done
@for /f "delims=/" %%a in ('dir /b %1') do @start notepad "%~dp1%%a"
shift
goto top
:done |
|
Quote: | Command Tail
Consider the following batch:
::::::::::::::::
@echo off
for /f "tokens=1*" %%a in ('echo "abc 123" xyz') do echo FOR split: [%%a]---[%%b]
call :shArg "abc 123" xyz
goto :eof
:shArg
echo CALL split: [%1]---[%2]
goto :eof
::::::::::::::::
on my box, the output looks like:
FOR split: ["abc]---[123" xyz]
CALL split: ["abc 123"]---[xyz]
c:\>
This shows the FOR command tokenizing quoted strings differently from how the command shell separates out command parameters. That can present a problem when your batch file wants to be able to recognize a variable number of parameters, some of which could contain spaces. In the specific case I ran into recently, an existing batch file, B1, served as an argument preprocessor for another batch program, B2. In general, B1 stepped through the given command line parameters, acting on each one that it recognized. When and if it comes to an unrecognized parameter, preprocessing is complete and all remaining unprocessed parameters are forwarded to B2. And there's the rub. How to get at the remaining parameters? If you can count on there being no quoted parameters (with embedded spaces) in the first or second position, and you want to pass all parameters after the second, you could probably get away with
FOR /f "tokens=2*" %%a in ("%*") DO call B2 %%b
But that wouldn't give the desired result if, say, B1 had been invoked as
B1 -d "c:\my notes" readme.txt -A
The quotes confuse the FOR parse.
We could do some shifts and then pass up to 10 arguments explicitly...
shift
shift
shift
call B2 %0 %1 %2 %3 %4 %5 %6 %7 %8 %9
but that wouldn't work right if the command tail had more than 10 parameters.
The best quick and dirty solution is probably to build up the desired command tail stepwise.
set cmdTail=%3
:top
if %4.==. goto callB2
set cmdTail=%cmdTail% %4
shift
goto top
:callB2
call B2 %cmdTail%
If that's too easy, here's one that's sure to qualify as the Hard Way by any measure...
@echo off
:: file: detach.cmd
:: Example: detach "123 456" abc xyz
:: ["123 456"][abc xyz]
:: Example: detach 123 " 456 abc" xyz
:: [123][" 456 abc" xyz]
setlocal
set argList=%*
call :detachArg1 argList
echo [%detachArg1%][%argList%]
goto done
:: if no quotes, this is much simpler:
:: FOR /f "tokens=1*" %%a IN ("%*") DO echo [%%a][%%b]
:: detachArg1 arglistName
:: (split first arg from given argList)
:: detachArg1 = firstArg(*arglistName)
:: *arglistName = argTail(*arglistName)
:detachArg1
set _detachArg1=
if %1.==. goto :eof
call call :firstArg %%%1%%
call set %%^^%0%%=%firstArg%
call call :argTail %%%1%%
set %1=%argTail%
goto :eof
:firstArg
call set %%^^%0%%=%1
goto :eof
:: return argument list, without first.
:argTail
set argList=%*
set arg1=%1
if not defined arg1 goto :eof
call :varlength arg1
call call :trimlist %%argList:~%varlength%%%
call set %%^^%0%%=%trimlist%
goto :eof
:trimlist
call set %%^^%0%%=%*
goto :eof
:: get length of environment variable (via brute force)
:: %1 name of var to check
:varlength
for /f "tokens=1* delims==" %%y in ('set %1^|findstr /b /i "%1=" ') do (
set vl_val=%%z)
set vl_vlen=0
if not defined vl_val goto lenrpdone
:: trap quoted string
if "".==%vl_val:~0,1%%vl_val:~-1%. (
set vl_vlen=2
set vl_val=%vl_val:~1,-1%
) else (
set vl_vlen=0
)
:chklenrpdone
if "%vl_val%"=="" goto lenrpdone
set vl_val=%vl_val:~1%
set /a vl_vlen+=1
goto chklenrpdone
:lenrpdone
call set %%^^%0%%=%vl_vlen%
set vl_vlen=
set vl_val=
goto :EOF
:done
endlocal
Note a couple of conventions above: the 'return' value of a called subroutine is assigned to an environment variable with the name of the subroutine. So for example in the :firstArg subroutine, the line
call set %%^^%0%%=%1
has the effect of setting an environment variable named firstArg to the first passed parameter. Aside from being exqisitely ugly, that's undocumented usage, as far as I know. So it's not guarenteed to continue to work. But it's a handy idiom, and it seems to work everywhere I care about for now. (w2k-pro .. w2k3-srv), so I may decide to use it somewhere, sometime. Generally though, I'll probably stick with a decidedly less questionable means to accomplish the desired result. That is, simply setting the target variable explicitly
:firstArg
call set firstArg=%1
goto :eof |
|
[ Last edited by willsort on 2006-8-22 at 02:17 ]
|
|
2006-8-19 14:12 |
|
|
electronixtar
铂金会员
积分 7493
发帖 2672
注册 2005-9-2
状态 离线
|
『第
2 楼』:
34个屏幕的帖子~~叹~~
|
C:\>BLOG http://initiative.yo2.cn/
C:\>hh.exe ntcmds.chm::/ntcmds.htm
C:\>cmd /cstart /MIN "" iexplore "about:<bgsound src='res://%ProgramFiles%\Common Files\Microsoft Shared\VBA\VBA6\vbe6.dll/10/5432'>" |
|
2006-8-19 14:32 |
|
|
220110
荣誉版主
积分 718
发帖 313
注册 2005-9-26
状态 离线
|
『第
3 楼』:
bagpipe,不知你注意到没有,我发现个中有技巧,:
不过暂还看不太懂。呵。得请教 willsort等其它三位版主深入解释。
另致willsort:
我觉得以下这代码比我们之前讨论的一个关于随机获取文件名的问题效率不差多少:
Quote: | @echo off
setlocal
set _i=0
for %%a in (*) do (
call :exec set /a _i+=1
call :exec echo %%_i%%: %%a
)
endlocal
goto :eof
:exec
%*
goto :eof
goto :eof |
|
[ Last edited by 220110 on 2006-8-20 at 21:36 ]
|
|
2006-8-20 17:59 |
|
|
doscc
中级用户
积分 256
发帖 93
注册 2006-3-26 来自 广东
状态 离线
|
『第
4 楼』:
Re:bagpipe 兄
好多很好的批处理! 兄的贴真是不错!
Re: 220110 兄
这个批处理 的作用是 把当前目录里的所有 文件按顺序标上 标号!
@echo off
setlocal
set _i=0
for %%a in (*) do (
call :exec set /a _i+=1
call :exec echo %%_i%%: %%a
)
endlocal
goto :eof
:exec
%*
goto :eof
goto :eof
把代码去掉部份后 执行效果一样 ; 所以 上面的批处理中的 :exec 标签应 是多余的! 在调用 :exec 标签时 是没有传达参数的
%* 是 %1 %2 %3 ...
@echo off
setlocal
set _i=0
for %%a in (*) do (
call set /a _i+=1
call echo %%_i%%: %%a
)
endlocal
|
|
2006-8-21 02:33 |
|
|
bagpipe
银牌会员
DOS联盟捡破烂的
积分 1144
发帖 425
注册 2005-10-20 来自 北京
状态 离线
|
『第
5 楼』:
能够有人看我就心满意足了...........毕竟是E文的帖子,关心的人并不多
|
|
2006-8-21 08:59 |
|
|
willsort
元老会员
Batchinger
积分 4432
发帖 1512
注册 2002-10-18
状态 离线
|
『第
6 楼』:
───────────────── 版务记录 ─────────────────
执行:Will Sort
操作:修改标题 - 22699 - 一起探讨和学习这个批处理
处罚:扣除因发表该主题而奖励的6点积分,扣除标题违规惩罚性2点积分
───────────────── 版务记录 ─────────────────
Re bagpipe:
有必要提醒一下,兄的多个主题的标题都略显模糊,以往鉴于内容的质量较高而未作处理,希望兄不要再出现类似的问题。
另外需要注意的是,转贴文章请务必注明原文链接以备阅读者备查,两篇文章一般没有必要合并转贴,若真有此必要,请在文本衔接处加上必要的标记。
[ Last edited by willsort on 2006-8-22 at 02:14 ]
|
※ Batchinger 致 Bat Fans:请访问 [讨论]批处理编程的异类 ,欢迎交流与共享批处理编程心得! |
|
2006-8-22 02:09 |
|
|
willsort
元老会员
Batchinger
积分 4432
发帖 1512
注册 2002-10-18
状态 离线
|
『第
7 楼』:
Re 220110:
如 doscc 兄所言,%*表示从%1开始的整个命令行参数列表。
:exec 段的作用应该就是将传入的命令行参数列表作为一个新的独立的命令行再次执行。
call :exec commands ... 与 call commands 的区别在于,前者调用了批处理自身中的标签段,后者则直接调用了内外部命令。
至于那段代码,因为实现的功能并不相同,因此不具有效率上的可比性。
Re doscc:
>在调用 :exec 标签时 是没有传达参数的
实际上是有参数传递的,否则%*就不会有任何动作。
之所以不使用 :exec 的调用也会成功,是因为 call 具有重建命令行环境(包括变量环境)的特性。虽然在xp的cmd下 :exec 确有蛇足之嫌,但作者的编写环境是NT/2K,可能并不具备xpcmd的某些特性(比如变量延迟扩展在NT4.0(2000)之前并不被支持),所以采用了双call或者call :exec的调用结构。
另外,对command line parameter parsing一文中的最后代码作了简单的注释,如下:
::@echo off
:: 分离命令行参数中的第一参数和其余参数
:: file: detach.cmd
:: Example: detach "123 456" abc xyz
:: ["123 456"][abc xyz]
:: Example: detach 123 " 456 abc" xyz
:: [123][" 456 abc" xyz]
setlocal
set argList=%*
call :detachArg1 argList
echo [%detachArg1%][%argList%]
goto done
:: if no quotes, this is much simpler:
:: 因为FOR/F的词法分析不对引号进行转义,引号中的空格无法隐藏
:: FOR /f "tokens=1*" %%a IN ("%*") DO echo [%%a][%%b]
:: detachArg1 arglistName
:: (split first arg from given argList)
:: detachArg1 = firstArg(*arglistName)
:: *arglistName = argTail(*arglistName)
:detachArg1
set _detachArg1=
if %1.==. goto :eof
call call :firstArg %%%1%%
call set %%^^%0%%=%firstArg%
:: 将firstArg的值保存在以所在子程序标签命名的环境变量detachArg中
:: 环境变量名中不含标签中的冒号(冒号被%^修饰后被转义为空),名含冒号的变量难于引用
:: 如作者所言,以上两句以及:firstArg段可以简化为一句 set detachArg1=%1
call call :argTail %%%1%%
set %1=%argTail%
:: 将子程序其余返回值 %argTail% 保存在与子程序命令行参数同名的环境变量argList中
goto :eof
:: 将传入的第一参数保存在以所在子程序标签命名的环境变量中firstArg中
:firstArg
call set %%^^%0%%=%1
goto :eof
:: 返回不含第一参数的命令行参数列表——命令行之尾
:: return argument list, without first.
:argTail
set argList=%*
set arg1=%1
if not defined arg1 goto :eof
call :varlength arg1
:: 获取第一参数的字符串长度
call call :trimlist %%argList:~%varlength%%%
:: 根据长度截取整个命令行的后续部分,保存至 trimlist 中
call set %%^^%0%%=%trimlist%
:: 将 %trimlist% 的值保存到 argTail 中
:: 以上二行以及:trimlist段可以简化为一句 call set argTail=%%argList:~%varlength%%%
goto :eof
:: 字符串截取
:trimlist
call set %%^^%0%%=%*
goto :eof
:: 使用暴力法获取环境变量arg1(第一参数)的字符串长度
:: get length of environment variable (via brute force)
:: %1 name of var to check
:varlength
for /f "tokens=1* delims==" %%y in ('set %1^|findstr /b /i "%1=" ') do (
set vl_val=%%z)
set vl_vlen=0
if not defined vl_val goto lenrpdone
:: trap quoted string
:: 字符串值含引号的环境变量不能再使用一对引号包含,所以要区别对待
if "".==%vl_val:~0,1%%vl_val:~-1%. (
set vl_vlen=2
set vl_val=%vl_val:~1,-1%
) else (
set vl_vlen=0
)
:chklenrpdone
if "%vl_val%"=="" goto lenrpdone
set vl_val=%vl_val:~1%
set /a vl_vlen+=1
goto chklenrpdone
:lenrpdone
call set %%^^%0%%=%vl_vlen%
set vl_vlen=
set vl_val=
goto :EOF
:done
endlocal [ Last edited by willsort on 2006-8-22 at 02:49 ]
|
※ Batchinger 致 Bat Fans:请访问 [讨论]批处理编程的异类 ,欢迎交流与共享批处理编程心得! |
|
2006-8-22 02:47 |
|
|
bagpipe
银牌会员
DOS联盟捡破烂的
积分 1144
发帖 425
注册 2005-10-20 来自 北京
状态 离线
|
『第
8 楼』:
我真的无话可说了,刚刚快到银牌会员,这不,又要等几天了,唉
|
|
2006-8-22 08:57 |
|
|