Page 1 of 1

Processing all commandline parameters in a loop

Posted: 06 May 2011 10:37
by WernerGg
I am new in this forum. I read a lot on this website and learned a lot about batch programming. Thanks to everybody!

Some questions: I want to process all commandline parameters in a loop. I.e. a call like

Code: Select all

call :foo a "" c

shall detect 3 parameters. So I cannot break at an empty parameter but on the first undefined parameter.

I want to get the number cnt of parameters, a variable p1, p2, etc for each parameter and a collected string ps like "/p1/p2/p3#" for all parameters.

Without a loop one would write codepieces in sequence:

Code: Select all

...
set c1=%1
if not defined c1 goto Done
set /a cnt=3
set p3=%~1
set ps=%ps%/%~1
shift
...


Q1a: Is it true that there is no way to get the number of parameters directly? You must test each parameter individually?
Q1b: And you cannot test directly if %1 is defined? You must use:

Code: Select all

set c1=%1
if not defined c1 goto Done


I needed for a while to get that into a loop. Now it works:

Code: Select all

setlocal enabledelayedexpansion
set /a cnt=0
set ps=

for /l %%i in (1,1,20) do (
   call set "c1=%%1"
   if not defined c1 goto Done 
   call set "c1h=%%~1"
   set /a cnt=%%i
   set p%%i=!c1h!
   set ps=!ps!/!c1h!
   shift
)
:Done
set ps=%ps%#
echo We have %cnt% parameters: %ps%
echo Namely p1=%p1%, p2=%p2%, p3=%p3%, etc.
echo All in a loop:
for /l %%i in (1,1,%cnt%) do (
   echo #%%i: p%%i=!p%%i!
)


With output for call :foo a "" c:

Code: Select all

We have 3 parameters: /a//c#
Namely p1=a, p2=, p3=c, etc.
All in a loop:
#1: p1=a
#2: p2=
#3: p3=c


Q2: I dont like that fixed upper bound 20 in the loop. The body is executed only 3 times but under "echo on" one sees 20 expansions of the body. And why 20, not 47 :wink: ? Can I write a simple "do while"-type of a loop without an upper bound?

Q3: I was not able to get rid of that helper variable c1h which I need only for the %~1 expansion. Is there a way to use %1, %~1 directly within the loop body?

Thanks for help.

Re: Processing all commandline parameters in a loop

Posted: 06 May 2011 13:37
by aGerman
WernerGg wrote:I want to process all commandline parameters in a loop.

Process %* that contains all parameters.


WernerGg wrote:Q1a: Is it true that there is no way to get the number of parameters directly? You must test each parameter individually?

Right. You have to loop over *% to count all parameters.

Code: Select all

set /a counter=0
for %%i in (%*) do set /a counter+=1
echo %counter%


WernerGg wrote:Q1b: And you cannot test directly if %1 is defined? You must use:

Well the key word defined is only available for environment variables. You could use

Code: Select all

if "%1"=="" ...


WernerGg wrote:Q2: I dont like that fixed upper bound 20 in the loop. The body is executed only 3 times but under "echo on" one sees 20 expansions of the body. And why 20, not 47 :wink: ? Can I write a simple "do while"-type of a loop without an upper bound?

No need for a fixed upper bound. Do it similar to Q1a

WernerGg wrote:Q3: I was not able to get rid of that helper variable c1h which I need only for the %~1 expansion. Is there a way to use %1, %~1 directly within the loop body?

You should explain what this could be good for. The first parameter is saved in %p1%. But have a look at the code below what I did if the first parameter is h.


Hope the following code will help to understand how it could work.

Code: Select all

@echo off &setlocal
call :foo
pause
goto :eof


:foo
  if "%1"=="" echo NO PARAMETERS
  if "%~1"=="h" (
    call :help
    goto :eof
  )
  set /a counter=0
  for %%a in (%*) do (
    set /a counter+=1
    call set "string=%%string%%/%%~a"
    call set "p%%counter%%=%%~a"
  )

  echo We have %counter% parameters: %string%

  <nul set /p "=Namely "
  for /l %%a in (1,1,%counter%) do (
    call set "x=%%p%%a%%"
    call :_output1 %%a
  )
  if %counter% gtr 0 (
    for /f "delims=#" %%a in ('"prompt #$H# &echo on &for %%b in (1) do rem"') do echo(%%a%%a
  ) else (
    echo(
  )

  echo All in a loop:

  for /l %%a in (1,1,%counter%) do (
    call set "x=%%p%%a%%"
    call :_output2 %%a
  )
goto :eof

:_output1
  <nul set /p "=p%1=%x%, "
goto :eof

:_output2
  echo #%1: p%1=%x%
goto :eof

:help
  echo You need help? Don't ask me!
  pause
goto :eof


Regards
aGerman

Re: Processing all commandline parameters in a loop

Posted: 07 May 2011 07:07
by WernerGg
Thanks a lot for that hints. I was not aware that %* can be processed in a simple for loop. And I think my assumption that "is defined ..." and "%1"=="" make a difference was wrong. Finally I oversaw that %%~c strips off apostrophes just like %~1.

So my final code is now (I need the skip-game because I have to strip of a first standard parameter):

Code: Select all

setlocal enabledelayedexpansion
set /a cnt=0
set ps=
set /a skip=1  :: Skip so many parms
for %%c in (%*) do (
   if !skip! leq 0 (
      set /a cnt+=1
      set p!cnt!=%%~c
      set ps=!ps!/%%~c
   ) else set /a skip-=1
)
set ps=%ps%#


For my original question concerning the c1h-helper-variable: I just could not find a working syntax that uses %~1 directly within the for-loop. But this is not important.

That %%%%.... games for proper substitution are really tricky and sometimes mysterious. I still have no clean understanding how the command interpreter works in detail. And "echo on" is not really helpfull with for-blocks.

Can you point me to a text that explains the exact cmd.exe architecture? What is the exact processing sequence for each line and block of code?

Re: Processing all commandline parameters in a loop

Posted: 07 May 2011 12:38
by WernerGg
OMG!
I just noticed that this %* processing loop

Code: Select all

for %%c in (%*) do (...)

delivers a list of all files in current directory when the parameter lists contains * or "*"! :twisted:

This is really crazy.

Since I need the asterix as parameter in my application I cannot use this simple solution.
:cry:

Re: Processing all commandline parameters in a loop

Posted: 07 May 2011 14:03
by aGerman
WernerGg wrote:Since I need the asterix as parameter in my application I cannot use this simple solution. :cry:

Image

You should use Obelix instead :lol:

OK, back to the topic.
There is a way to solve this asterisk problem. You could use a FOR /F loop and call the sub routine recursively.

Code: Select all

@echo off &setlocal

set /a counter=0
call :proc 1 * x "*" ? "" !

echo Number of arguments = %counter%.
pause
goto :eof

:proc
for /f "tokens=1*" %%a in ("%*") do (
  set /a counter+=1
  echo(%%~a
  call :proc %%b
)
goto :eof


Regards
aGerman

Re: Processing all commandline parameters in a loop

Posted: 07 May 2011 14:43
by WernerGg
Thanks for the asterix. Unfortunatly I have no obelix on my keyboard, hence I can not use him either. Or is there another tricky recursive obelix-key simulator solution? :)

Seriously: I fell back to my original understandable
for /l %%i in (1,1,47) do
solution.

This command interpreter is really mysterious. Was it Bill Gates who once wrote it in his garage? In any case it was somebody who has never read a book about parser/compiler construction.

Anyway, it is much more powerfull than I ever thought. This is what I learned in my old days from (your?) DosTips-website.

Re: Processing all commandline parameters in a loop

Posted: 07 May 2011 15:03
by aGerman
WernerGg wrote:Thanks for the asterix. Unfortunatly I have no obelix on my keyboard, hence I can not use him either. Or is there another tricky recursive obelix-key simulator solution? :)

On my keyboard you will neither find asterix nor obelix, so I cannot help out :lol:


WernerGg wrote:Seriously: I fell back to my original understandable
for /l %%i in (1,1,47) do
solution.

Why?


WernerGg wrote:This command interpreter is really mysterious. Was it Bill Gates who once wrote it in his garage? In any case it was somebody who has never read a book about parser/compiler construction.

Who ever wrote the code of the CMD, I agree that he was drugged up to the eye balls or something similar :wink:


WernerGg wrote:Anyway, it is much more powerfull than I ever thought. This is what I learned in my old days from (your?) DosTips-website.

Well I learned a lot from DosTips as well. It's not my website, but the founder is also a german. I'm only one of the moderators.

Regards
aGerman

Re: Processing all commandline parameters in a loop

Posted: 07 May 2011 15:44
by WernerGg
On my keyboard you will neither find asterix nor obelix, so I cannot help out

Hups. You are right. What we have is a Unicode MULTIPLICATION SIGN. The U+2217 ASTERIX OPERATOR has nobody.

That is why I fell back to my 4711 solution.

No, seriously, it's a time problem. I spent far too much time allready with all these mysteries.

Re: Processing all commandline parameters in a loop

Posted: 07 May 2011 16:03
by aGerman
Just to throw light on my little joke:
Asterix vs. asterisk :wink:

Regards
aGerman

Re: Processing all commandline parameters in a loop

Posted: 07 May 2011 17:44
by WernerGg
Oh Gott, wie dumm von mir. Peinlich, peinlich.

Re: Processing all commandline parameters in a loop

Posted: 08 May 2011 11:11
by jeb
WernerGg wrote:Can you point me to a text that explains the exact cmd.exe architecture? What is the exact processing sequence for each line and block of code?


Deutsch: Die Geheimnisse des Batch Zeilen Interpreters
English: It is on this site too, but I can't find it anymore :?

jeb

Re: Processing all commandline parameters in a loop

Posted: 09 May 2011 00:35
by WernerGg
Thank you jeb for this link to "Geheimnisse ..". Very interesting. It proofes what I was afraid of.

The language has no formal definition. This cmd.exe is by no means a proper parser, but mountains of stacked if-statements. I would call it a hobby-program that grew over some years by adding further special cases. And then it got to a state of non-maintainability.

Do you know if Microsoft has ever dared to touch that code during say the last 10 or 15 years? I can image that they did not. It would be horrifying dangerous to change anything within that program.

Anyway: It's usefull and partly fun to code in that language.

Re: Processing all commandline parameters in a loop

Posted: 17 May 2011 09:18
by Ed Dyreen
I am posting the complete routine for my/your convinience but don't try to use it as is ! Look carefully at the :Call_LOOP0 (), this is where the magic happens. I used to have a routine that would process all exceptions without a glitch, but I had to abandon it because it was too much of a performance hit. The following code is a nice balance but I am not sure it works for all characters.

Code: Select all

@rem :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
@rem:: Suppress all 'direct call' related errors, all Windows OSes.
@rem :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
@rem::(
@   if "%FullPathFile.Host.SCRIPT%"=="" 2>nul goto :ENDOLDOS ()
@   if "%FullPathFile.Host.SCRIPT%"=="" 2>nul goto  ENDOLDOS ()
@rem::)
@rem :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::
::Call_STABLE ( :SubRoutine star )
::
:: SJAB v140e beta
::
:: call @SubRoutine @star
::
::INPUT:
:: @SubRoutine    STRING    Required;    SubRoutine that will be called
:: @star    STRING    Optional;    Parameters that will be passed to @SubRoutine
::
:: WARNING (
::
::    -Needs to stay on top since;
::    ;. This in no SubRoutine !
::
::    -Cannot use :Debug () since;
::    ;. This in no SubRoutine !
::
::    Only if using Call_FAST:
::    -The '*' symbol must be noted as '^*' otherwise;
::    ;. for won't detect it as an argument !
::    ;. Can be avoided by loopin however this is much of a performance hit !
::
::    -if %* contains "(" or ")" a normal if /i [""] == [""] () construction fails !
::    ;) This is solved by storing %* inside a variable first.
:: )
::
::(
   ::if /i ["!Debug.Sub!"] == [":StartSho.FAST"] set "Debug.Sub=:Call"
   ::set "Debug.Sub=:Call"
   ::(
      ::if /i ["!Debug.Sub!"] == [":Call"] echo. &echo.inside :Call star: &call echo.%*_
   ::)

   ::if /i ["!Debug.Sub!"] == [":Call"] echo.PROC PAR
   ::(
      ::if /i ["!Debug.Sub!"] == [":Call"] echo. Load called variables
      ::(
         set /a Sub.Depth += 1
         ::
         set "Par.AsIs="

         set /a Par.C = -1

         :Call_LOOP0 ()
         ::(
            call set Par=%%1
            ::
            if defined Par (
            ::
               set /a Par.C += 1

               call set Par.!Par.C!=%%1

               call set Sub.!Sub.Depth!.Par.!Par.C!=%%1

               if !Par.C! gtr 0 if defined Par.AsIs (
                  ::
                  call set Par.AsIs=!Par.AsIs! %%1

               ) else    call set Par.AsIs=%%1

               ::if /i ["!Debug.Sub!"] == [":Call"] call echo. Par.!Par.C!=%%Par.!Par.C!%%_
               echo.>nul

               shift

               goto :Call_LOOP0 "()"
            )
         ::
         ::if /i ["!Debug.Sub!"] == [":Call"] pause
         ::)

         set "Sub.!Sub.Depth!.Name=:!Par.0!"
         ::
         set "Sub.!Sub.Depth!.Par.AsIS=!Par.AsIs!"
         ::
         set "Sub.!Sub.Depth!.Par.C=!Par.C!"
      ::)

      ::if /i ["!Debug.Sub!"] == [":Call"] for %%! in ( Sub.Depth Par.AsIS Par.C Sub.!Sub.Depth!.Par.C       ::Sub.!Sub.Depth!.Name Sub.!Sub.Depth!.Par.AsIS  ) do echo. %%!=!%%!!_
      ::if /i ["!Debug.Sub!"] == [":Call"] for /l %%! in ( 0, 1, !Par.C! ) do echo. Par.%%!=!Par.%%!!_
      ::if /i ["!Debug.Sub!"] == [":Call"] for /l %%! in ( 0, 1, !Sub.%Sub.Depth%.Par.C! ) do echo.       ::Sub.!Sub.Depth!.Par.%%!=!Sub.%Sub.Depth%.Par.%%!!_
   ::)

   ::if /i ["!Debug.Sub!"] == [":Call"] echo.VALID
   ::(
      if not defined Par.0 echo. :( not defined Par.0 [ERROR] &pause &exit 1

      ::if /i ["!Debug.Sub!"] == [":Call"] for %%! in ( Sub.Depth Par.AsIS Par.C Sub.!Sub.Depth!.Par.C       ::Sub.!Sub.Depth!.Name Sub.!Sub.Depth!.Par.AsIS  ) do echo. %%!=!%%!!_
      ::if /i ["!Debug.Sub!"] == [":Call"] for /l %%! in ( 0, 1, !Par.C! ) do echo. Par.%%!=!Par.%%!!_
      ::if /i ["!Debug.Sub!"] == [":Call"] for /l %%! in ( 0, 1, !Sub.%Sub.Depth%.Par.C! ) do echo.       ::Sub.!Sub.Depth!.Par.%%!=!Sub.%Sub.Depth%.Par.%%!!_
   ::)

   ::if /i ["!Debug.Sub!"] == [":Call"] echo.Perform
   ::(
      ::if /i ["!Debug.Sub!"] == [":Call"] echo. call :!Par.0! !Par.AsIS! &pause
      ::(
         (
            ::if /i ["!Debug.Sub!"] == [":Call"]    set "Debug.Sub=:!Par.0!"
            call :!Par.0! !Par.AsIS!
            ::if /i ["!Debug.Sub!"] == [":!Par.0!"] set "Debug.Sub=%Debug.Sub%"
            echo.>nul
         )
      ::)

      ::if /i ["!Debug.Sub!"] == [":Call"] echo. Save called variables
      ::(
         for /l %%! in ( 0, 1, !Sub.%Sub.Depth%.Par.C! ) do set "Sub.!Sub.Depth!.Par.%%!="
         ::
         set "Sub.!Sub.Depth!.Par.C="

         set "Sub.!Sub.Depth!.Par.AsIS="
         ::
         set "Sub.!Sub.Depth!.Name="
         ::
         set /a Sub.Depth -= 1
      ::)

      ::if /i ["!Debug.Sub!"] == [":Call"] echo. Load caller variables
      ::(
         set "Par.AsIS=!Sub.%Sub.Depth%.Par.AsIS!"
         for /l %%! in ( 0, 1, !Par.C! ) do set "Par.%%!="
         ::
         set "Par.C=!Sub.%Sub.Depth%.Par.C!"

         :: -Set will set @ErrorLevel 1 if variable isn't found in environment therefor,
         :: ;) Using this method to preserve @ErrorLevel.
         ::(
            for /l %%! in ( 0, 1, !Par.C!
            ) do (
               if defined Sub.%Sub.Depth%.Par.%%! (
                  ::
                  set "Par.%%!=!Sub.%Sub.Depth%.Par.%%!!"

               ) else    if defined Par.%%! set "Par.%%!="
            )
         ::)

         ::if /i ["!Debug.Sub!"] == [":Call"] for %%! in ( Par.AsIS Par.C ) do echo. %%!=!%%!!_
         ::if /i ["!Debug.Sub!"] == [":Call"] for /l %%! in ( 0, 1, !Par.C! ) do echo. Par.%%!=!Par.%%!!_
      ::)
   ::)
::
::if /i ["!Debug.Sub!"] == [":Call"] echo.outside :Call () &pause
goto :eof ()
::)


The following code is the one I used before, it should catch all parameters but is more outdated

Code: Select all

::Call ( :Suboutine star )
::
:: SJAB v1.35 beta
::
:: if /i ["@Suboutine"] neq [":CallMySub"] call @Suboutine @star
::
::INPUT:
:: @SubRoutine    STRING    Required;    SubRoutine that will be called
:: @star    STRING    Optional;    Parameters that will be passed to @SubRoutine
::
:: WARNING (
::
::    -Needs to stay on top of :CallMySub () since;
::    ;. This in not a SubRoutine !
::
::    -Disables error handling for @SubRoutine since;
::    ;. Speed is important here !
::
::    -Cannot use :Debug since;
::    ;. This in not a SubRoutine !
::
::    -if %* contains "(" or ")" a normal if /i [""] == [""] () construction fails !
::    ;) This is solved by using if /i [""] == [""]
:: )
::
::(
   ::set "Debug.Sub=:Call"
   ::(
      ::%Debug.Define% echo. &echo.inside ::Call star: &echo.%*
   ::)

   ::%Debug.Define% echo.PROC PAR
   ::(
      @set "FirstPARAM=%1"

      ::%Debug.Define% echo. FirstPARAM=!FirstPARAM!_
   ::)

   ::%Debug.Define% echo.VALID
   ::(
      @if not defined FirstPARAM echo. :( not defined %%1 [ERROR] &pause &exit 1

      ::%Debug.Define% echo. FirstPARAM=!FirstPARAM!_
   ::)

   ::%Debug.Define% echo.Perform
   ::(
      shift &if /i ["!FirstPARAM!"] neq ["CallMySub"] (
         ::
         echo.>nul

         set /a Sub.Depth += 1
         call set Sub.!Sub.Depth!.Name=:%%0

         ::%Debug.Define% echo. call :%* &%Debug.Pause%
         echo.>nul
      )
      if /i ["!FirstPARAM!"] neq ["CallMySub"] call :%* &set /a Sub.Depth -= 1 &goto :eof
   ::)
::)

:CallMySub ( :Suboutine star )
::
:: SJAB v1.35 beta
::
:: call @Suboutine @star with error handling
::
::INPUT:
:: @SubRoutine    STRING    Required;    SubRoutine that will be called
:: @Parameters    STRING    Optional;    Parameters that will be passed to @SubRoutine
::
:: WARNING (
::
::    -Cannot use :Debug since;
::    ;( :CallMySub is a dependency of :Debug !
::
::    -Cannot use %Debug.Equals% since;
::    ;( :CallMySub is a dependency of %Debug.Equals% !
::
:: )
::
::(
   ::set "Debug.Sub=:CallMySub"
   ::(
      ::if /i ["!Debug.Sub!"] == [":CallMySub"] echo. &echo.inside :CallMySub star: &echo.%*
   ::)

   set /a Sub.Depth += 1

   set "Par.AsIS=" &( for /l %%! in ( 0, 1, !Par.C! ) do set "Par.%%!=" ) &set /a Par.C = -1

   :CallMySub.LOOP_0 ()
   ::(
      call set CallMySub.b=%%1
      shift

      ::%Debug.Define% echo. CallMySub.b=!CallMySub.b!_
      if defined CallMySub.b (
         ::
         set /a Par.C += 1

         if !Par.C! gtr 0 if defined Par.AsIS (
            ::
            set "Par.AsIS=!Par.AsIS! !CallMySub.b!"

         ) else    set "Par.AsIS=!CallMySub.b!"

         set Par.!Par.C!=!CallMySub.b!

         ::%Debug.Define% echo. Par.!Par.C!=!CallMySub.b!_
         echo.>nul

         goto :CallMySub.LOOP_0
      )
   ::)

   set "Sub.!Sub.Depth!.Name=:!Par.0!"
   set "Sub.!Sub.Depth!.Par.C=!Par.C!"

   for /l %%! in ( 1, 1, !Par.C!
   ) do (
      set "Sub.!Sub.Depth!.Par.%%!=!Par.%%!!"
      ::
      if defined Sub.!Sub.Depth!.Par.AsIS (
         ::
         set "Sub.!Sub.Depth!.Par.AsIS=!Sub.%Sub.Depth%.Par.AsIS! !Par.%%!!"

      ) else    set "Sub.!Sub.Depth!.Par.AsIS=!Par.%%!!"
   )
   ::if /i ["!Debug.Sub!"] == [":CallMySub"] for /l %%! in ( 1, 1, !Par.C! ) do echo. Sub.!Sub.Depth!.Par.%%!=!Sub.%Sub.Depth%.Par.%%!!_

   ::if /i ["!Debug.Sub!"] == [":CallMySub"] echo. call !Sub.%Sub.Depth%.Name! !Sub.%Sub.Depth%.Par.AsIS! &%Debug.Pause%
   call !Sub.%Sub.Depth%.Name! !Sub.%Sub.Depth%.Par.AsIS!

   ( for %%! in ( Par.AsIS Use ) do set "Sub.!Sub.Depth!.%%!=" ) &for /l %%! in ( 1, 1, !Sub.%Sub.Depth%.Par.C! ) do set "Sub.!Sub.Depth!.Par.%%!="

   set /a Sub.Depth -= 1
::
::if /i ["!Debug.Sub!"] == [":CallMySub"] %Debug% "outside :CallMySub" &%Debug.Pause%
goto :eof
::)


But the answer is YES it is possible to process all parameters correctly I am sure of it, it must be ! What i've posted here is not the solution just some hints on how it could be done.

Re: Processing all commandline parameters in a loop

Posted: 17 May 2011 09:52
by Ed Dyreen
@WernerGg
No, seriously, it's a time problem. I spent far too much time allready with all these mysteries.

Frustrated already? You make me :lol:

If you don't wan't to spend much time on it, abandon the ship, cause it floats like a rock.

Most of the experts spend too much time with all these mysteries.
I spended 3years with an unattended installation of XP &3th party apps based on a CMD script.:evil:

Most of the experts started out when they were relatively young, optimistic &naive about DOS, most of the experts have gotten really good at it &really frustrated with it.

I don't know of a language that frustrates people more, even assembly produces better results more quickly.
The good thing is that DOS/CMD has become quite irrelevant these days.
I believe Microsoft has written such a buggy app like CMD on purpose, to frustrate wannabee script programmers.

Re: Processing all commandline parameters in a loop

Posted: 17 May 2011 11:15
by WernerGg
@Ed

Thanks for your advice and consolation. I am an old battlehorse as well. I have not touched .bat files for many years. But currently I am in Saudi Arabia and they are still on a much lower level than we use to be. Hence I wrote an administration bat-script for file distribution of server files to local machines during logon. That is how I got into this funny bat-language again. But now its done.