EDIT: code below has changed vs. the one posted earlier today. At the time I edited, there were no followups, so I hope no one minds too much. More details at the bottom.
2nd EDIT: nested the "do rem" parts inside a "setlocal disableextensions" block to protect against accidental expansion of %~a %~dpa etc.
Note that the enclosing (...) is still evaluated at the outer level with extensions enabled, which allows %* to work correctly, but the for loop executes with extensions disabled so %~a, %~dpa etc are no longer expanded.
Code: Select all
@echo off
setlocal disableDelayedExpansion
set "tab= "
:: can't call with wacky %%*
:: must goto and rely on hardcoded return to :main
::
:: upon return, the following variables are set
:: args is the complete command line
:: argc is the number of arguments
:: arg1, arg2, etc are the individual arguments
:: argsLen, arg1len, arg2len etc are the respective string lengths
:: inside a nested setlocal enableDelayedExpansion
::
goto :getArgs
:: body of actual code would go under :main below
::
:main
echo.
echo ---- copied from %%? ----
echo.
echo args %tab% %tab%{ !args! }
for /l %%n in (1, 1, %argc%) do (
echo arg%%n %tab% %tab%{ !arg%%n! }
)
:: this part is only needed to get the original cmd line offsets
:: e.g. in order to preserve whitespace with !args:~%arg2off%!
::
:: requires the args, argc, arg?len variables be set by :getArgs
:: sets arg?off upon return to the offset of arg? within args
::
call :getOffs
:: only uses arg1, arg2 etc for verification
::
echo.
echo ---- chopped off %%* ----
echo.
@rem entire command line
echo args %tab%~0,%argsLen% %tab%{ !args! }
set /a delimOff = 0
for /l %%n in (1, 1, %argc%) do (
@rem whitespace before %%n'th argument
set /a delimLen = !arg%%noff! - !delimOff!
for /f "tokens=1*" %%p in ("!delimOff! !delimLen!") do (
echo %tab%~!delimOff!,!delimLen! %tab%{ !args:~%%p,%%q! }
)
set /a delimOff = !arg%%noff! + !arg%%nLen!
@rem %%n'th argument
for /f "tokens=1*" %%p in ("!arg%%noff! !arg%%nlen!") do (
echo arg%%n %tab%~!arg%%noff!,!arg%%nlen! %tab%{ !args:~%%p,%%q! }
@rem reality check
if not "!args:~%%p,%%q!"=="!arg%%n!" (
echo arg%%n ??? %tab%{ !args:~%%p,%%q! } ^^!= { !arg%%n! } 1>&2
)
)
)
set /a delimLen = !argsLen! - !delimOff!
if defined args (
@rem whitespace after last argument
echo %tab%~!delimOff!,!delimLen! %tab%{ !args:~%delimOff%,%delimLen%! }
)
set "delimOff="
set "delimLen="
goto :eof
::.............................................................................
:getArgs
set "remArgs=%temp%\%random%.tmp"
set prompt=@
>"%remArgs%" (
setlocal disableextensions
echo on
for %%a in (%%a) do rem { %* }
@echo off
endlocal
)
call :getFileSize "%remArgs%" sizeOld
set /a argsLen = %sizeOld% - 14
set /a argc = 1
:nextArg
>>"%remArgs%" (
setlocal disableextensions
echo on
for %%a in (%%a) do rem { %1 }
@echo off
endlocal
)
call :getFileSize "%remArgs%" sizeNew
set /a argLen = %sizeNew% - %sizeOld% - 14
if %argLen% leq 0 (
set "sizeOld="
set "sizeNew="
set "argLen="
goto :lastArg
)
set /a arg%argc%len = %argLen%
set /a sizeOld = %sizeNew%
set /a argc += 1
shift /1
goto :nextArg
:lastArg
set /a argc -= 1
prompt
setlocal enableDelayedExpansion
<"%remArgs%" (
set /p "args="
set /p "args="
set "args=!args:~7,-3!"
for /l %%n in (1, 1, %argc%) do (
set /p "arg%%n="
set /p "arg%%n="
set "arg%%n=!arg%%n:~7,-3!"
)
)
del "%remArgs%"
goto :main
:getFileSize
set /a %~2 = %~z1
goto :eof
::.............................................................................
:getOffs
set /a argsOff = 0
for /l %%n in (1, 1, %argc%) do (
call :getOff args !argsOff! arg%%noff
set /a argsOff = !arg%%noff! + !arg%%nlen!
)
set "argsOff="
goto :eof
:getOff
setlocal enableDelayedExpansion
set /a argOff = %~2
:nextOff
set "chrNext=!%~1:~%argOff%,1!"
if "!chrNext!"=="" goto :lastOff
if not "!chrNext!"==" " ^
if not "!chrNext!"=="%tab%" ^
if not "!chrNext!"=="," ^
if not "!chrNext!"==";" ^
if not "!chrNext!"=="=" goto :lastOff
set /a argOff += 1
goto :nextOff
:lastOff
endlocal & set /a %~3 = %argOff%
goto :eof
Still don't like it completely, because it shifts parameters out thus destroying the original %1/%2/etc. It also leaves all variables stuck in a setlocal block without an easy way to send them back to the enclosing context. But, other than that, it appears to be working as advertised.
Liviu
EDIT: I tidied up some code (the 'for' loop when reading "%remArgs%" back), and added some inline comments (since I seem to be misplacing/forgetting web references way more often than I lose local batch files).
Also added the :getOffs counterpart, which essentially recovers the separators that cmd ignores from the full command line. Nice thing about it is that it basically tokenizes the command line while leaving the actual token parsing to cmd itself. In other words, as long as (1) cmd uses the same escaping logic between %* and %1/%2/etc, and (2) the delimiters remain the known space/tab/comma/semicolon/equal ones, this "manual reparsing" should match cmd's own, regardless of whatever the rules are inside a token.
Below is a sample output using the updated code.
Code: Select all
C:\>eccoargs ; %a %0 %~1 %cd^% !cd! a^|b a^&b "a&b" ^<"&"^> ^^;,=
---- copied from %? ----
args { ; %a %0 %~1 %cd% !cd! a|b a&b "a&b" <"&"> ^;,= }
arg1 { %a }
arg2 { %0 }
arg3 { %~1 }
arg4 { %cd% }
arg5 { !cd! }
arg6 { a|b }
arg7 { a&b }
arg8 { "a&b" }
arg9 { <"&"> }
arg10 { ^ }
---- chopped off %* ----
args ~0,46 { ; %a %0 %~1 %cd% !cd! a|b a&b "a&b" <"&"> ^;,= }
~0,2 { ; }
arg1 ~2,2 { %a }
~4,1 { }
arg2 ~5,2 { %0 }
~7,1 { }
arg3 ~8,3 { %~1 }
~11,1 { }
arg4 ~12,4 { %cd% }
~16,1 { }
arg5 ~17,4 { !cd! }
~21,1 { }
arg6 ~22,3 { a|b }
~25,1 { }
arg7 ~26,3 { a&b }
~29,1 { }
arg8 ~30,5 { "a&b" }
~35,1 { }
arg9 ~36,5 { <"&"> }
~41,1 { }
arg10 ~42,1 { ^ }
~43,3 { ;,= }
C:\>