on my box, the output looks like::::::::::::::::: @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 ::::::::::::::::
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 withFOR split: ["abc]---[123" xyz] CALL split: ["abc 123"]---[xyz] c:\>
But that wouldn't give the desired result if, say, B1 had been invoked asFOR /f "tokens=2*" %%a in ("%*") DO call B2 %%b
The quotes confuse the FOR parse.B1 -d "c:\my notes" readme.txt -A
We could do some shifts and then pass up to 10 arguments explicitly...
but that wouldn't work right if the command tail had more than 10 parameters.shift shift shift call B2 %0 %1 %2 %3 %4 %5 %6 %7 %8 %9
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...
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@echo off :: file: detach.cmd :: Example: detach "123 456" abc xyz :: ["123 456"][abc xyz] :: Example: detach 123 " 456 abc" xyz :: [" 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
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 explicitlycall set %%^^%0%%=%1
:firstArg call set firstArg=%1 goto :eof