48
votes

In the following example, I want to call a child batch file from a parent batch file and pass all of the remaining parameters to the child.

C:\> parent.cmd child1 foo bar
C:\> parent.cmd child2 baz zoop
C:\> parent.cmd child3 a b c d e f g h i j k l m n o p q r s t u v w x y z

Inside parent.cmd, I need to strip %1 off the list of parameters and only pass the remaining parameters to the child script.

set CMD=%1
%CMD% <WHAT DO I PUT HERE>

I've investigated using SHIFT with %*, but that doesn't work. While SHIFT will move the positional parameters down by 1, %* still refers to the original parameters.

Anyone have any ideas? Should I just give up and install Linux?

8
That question was answered with code that didn't work. I added statements to my question to help clarify, and it looks like I got a solid answer from Johannes. - Dave
Oh, by the way, when calling batches from other batches by all maens use call. Otherwise the calling batch file won't run further after the called batch exits. (May or may not be relevant here, just some best practices :-)) - Joey
there is official bash for Windows 10, and unofficial win-bash.sourceforge.net for any windowses. Or with Cygwin, you'll get full GNU environment set up as you like. - LogicDaemon

8 Answers

79
votes

%* will always expand to all original parameters, sadly. But you can use the following snippet of code to build a variable containing all but the first parameter:

rem throw the first parameter away
shift
set params=%1
:loop
shift
if [%1]==[] goto afterloop
set params=%params% %1
goto loop
:afterloop

I think it can be done shorter, though ... I don't write these sort of things very often :)

Should work, though.

17
votes

Here's a one-line approach using the "for" command...

for /f "usebackq tokens=1*" %%i in (`echo %*`) DO @ set params=%%j

This command assigns the 1st parameter to "i" and the rest (denoted by '*') are assigned to "j", which is then used to set the "params" variable.

10
votes

Warning! this method has side-effect of expanding wildcards, if there are «*»s or «?»s in the arguments. If there is a wildcard but no corresponding files, argument is skipped (non-wildcard arguments stay as-is). If arguments must stay intact, look for another way.


the line

%CMD% <WHAT DO I PUT HERE>

shall be changed to:

(
  SETLOCAL ENABLEDELAYEDEXPANSION
  SET skip=1

  FOR %%I IN (%*) DO IF !skip! LEQ 0 (
        SET "params=!params! %%I"
    ) ELSE SET /A skip-=1
)
(
  ENDLOCAL
  SET "params=%params%"
)
%CMD% %params%

of course, you may set skip var to any number of arguments.

Explaned:

(
@rem Starting block here, because it's read once and executed as one
@rem otherwise cmd.exe reads file line by line, which is waaay slower.

SETLOCAL ENABLEDELAYEDEXPANSION
SET skip=1

@rem if value contains unquoted non-paired parenthesis, SET varname=value 
@rem confuses cmd.exe. SET "a=value" works better even if value has quotes.
  FOR %%I IN (%*) DO (
    IF !skip! LEQ 0 (
      SET "params=!params! %%I"
      @rem newline after SET to lower amount of pitfalls when arguments 
      @rem have unpaired quotes
    ) ELSE (
      SET /A skip-=1
    )
)
(
@rem get variables out of SETLOCAL block
@rem as whole block in parenthesis is read and expanded before executing,
@rem SET after ENDLOCAL in same block will set var to what it was before
@rem ENDLOCAL. All other envvars will be reset to state before SETLOCAL.
ENDLOCAL
SET "params=%params%"
)
@rem doing this outside of parenthesis block to avoid
@rem cmd.exe confusion if params contain unquoted closing round bracket
%CMD% %params%
7
votes

You can actually just do this:

%*

If that is the only thing on the line, then that expands to having the first parameter be the command executed, with all other parameters passed to it. :)

1
votes

I have improved Moshe Goren's answer because it didn't seem to work for me:

set _input=%*
call set params=%%_input:%1 =%%
@echo %params%
1
votes

Another way (almost the same as Alxander Bird's) without executing ECHO in a subshell:

FOR /F "usebackq tokens=1*" %%I IN ('%*') DO SET params=%%J

so, line

%CMD% <WHAT DO I PUT HERE>

will look like:

FOR /F "usebackq tokens=1*" %%I IN ('%*') DO %CMD% %%J

the problem is that if parameters include quoted stings with spaces inside, cmd.exe will parse them appropriately for using as numbered arguments (%1), but FOR will ignore the quotes. This specific case, it will harm if first parameter includes a space or more, which is quite possible, considering first argument can be, for example, "C:\Program Files\Internet Explorer\iexplore.exe".

So, here will be another answer.

0
votes

Although really the 'for' solution is superior in a lot of circumstances, for something simple I will frequently just save and shift the other arguments away, then use %* as usual (practically the same strategy often works for $* or $@ in {,ba,da,k,*}sh):

example:

:: run_and_time_me.cmd - run a command with the arguments passed, while also piping
::                       the output through a second passed-in executable

@echo off

set run_me=%1
set pipe_to_me=%2
shift
shift

:: or
:: set run_me=%1
:: shift
:: set pipe_to_me=%1
:: shift

%run_me% %* | %pipe_to_me%

Anyhow, I saw the question was long answered, but figured I'd drop in my two cents as it was something I didn't see, and because it was the answer I needed when I finally happened across it a few years back... and went "oh... duh." :)

0
votes

I came across this question while I was facing a similar problem, but I solved it using a different approach. Instead of re-creating the input line using a loop (as in the answer by @Joey) I simply removed the first parameter from the input string.

set _input=%*
set params=!_input:%1 =!
call OTHER_BATCH_FILE.cmd %params%

Of course, this assumes that all the parameters are different.