50

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?

Dave
  • 1,081
  • 1
  • 9
  • 18
  • Exact duplicate? http://stackoverflow.com/questions/382587/how-to-get-batch-file-parameters-from-nth-position-on – Rob Kennedy Apr 17 '09 at 18:54
  • 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 Apr 17 '09 at 23:29
  • 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 Apr 18 '09 at 06:44
  • 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 May 30 '19 at 09:22

8 Answers8

81

%* 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.

Joey
  • 344,408
  • 85
  • 689
  • 683
  • 1
    As Tbee said, this doesn't work if you have `=` within your remaining arguments. Seems like the only real way to solve this is to write a program to correctly extract your arguments - the cmd shell is just too sloppy. – Merlyn Morgan-Graham Aug 20 '11 at 00:08
  • 4
    This approach fails if you have assignments in the parameters, e.g. "a=b". Then you'll get two separate parameters "a" and "b", losing the =. – Tbee Sep 03 '10 at 09:22
  • 2
    In that case, quote the argument. `cmd` considers a few more characters to be argument separators as well, such as `;` or `,`. – Joey Aug 20 '11 at 12:11
  • This completely fails when there are two or more consecutive spaces between two arguments, if you want to preserve those spaces. They get shortened to one space! I wish SHIFT command shifted for %* as well :( – ADTC Feb 27 '12 at 17:49
17

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.

Alexander Bird
  • 38,679
  • 42
  • 124
  • 159
max_diff
  • 179
  • 1
  • 2
  • 4
    This fails when the first argument contains a space (quoted), e.g. `"first argument" second third` sets params to `argument" second third` – EM0 Apr 13 '16 at 09:12
10

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%
LogicDaemon
  • 466
  • 10
  • 22
7

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. :)

Alexander Bird
  • 38,679
  • 42
  • 124
  • 159
2

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

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.

LogicDaemon
  • 466
  • 10
  • 22
0

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." :)

shelleybutterfly
  • 3,216
  • 15
  • 32
  • 4
    shift does not affect %* so this passes *all* original arguments to %run_me%, including %1 and %2. As the accepted answer says "%* will always expand to all original parameters, sadly." – Naaff Jan 12 '16 at 18:23
0

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.