46

In my batch file on Windows XP, I want to use %* to expand to all parameters except the first.
Test file (foo.bat):

@echo off
echo %*
shift
echo %*

Call:

C:\> foo a b c d e f

Actual result:

a b c d e f
a b c d e f

Desired result:

a b c d e f
b c d e f

How can I achieve the desired result? Thanks!!

barfuin
  • 16,865
  • 10
  • 85
  • 132

7 Answers7

22

Wouldn't it be wonderful if CMD.EXE worked that way! Unfortunately there is not a good syntax that will do what you want. The best you can do is parse the command line yourself and build a new argument list.

Something like this can work.

@echo off
setlocal
echo %*
shift
set "args="
:parse
if "%~1" neq "" (
  set args=%args% %1
  shift
  goto :parse
)
if defined args set args=%args:~1%
echo(%args%

But the above has problems if an argument contains special characters like ^, &, >, <, | that were escaped instead of quoted.

Argument handling is one of many weak aspects of Windows batch programming. For just about every solution, there exists an exception that causes problems.

dbenham
  • 127,446
  • 28
  • 251
  • 390
  • I write a simple one, can process any ascii characters. Including `\t` `\r` `\n` `^` `Back Slash` and `quote`. https://github.com/zhanhb/kcptun-sip003-wrapper/blob/v0.1/src/kcptun.cmd – martian May 19 '19 at 04:08
  • Given the typical bash and powershell script use of incoming arguments shift is focused on separating out first argument from the remaining ones, and fact that with cmd scripts there are only 1 - 9 argument index pointers, why not just write one liner that does `set arg1=%1 & set argsRemainder=%2 %3 %4 %5 %6 %7 %8 %9`. At which point the most command use case is straight forward, e.g. `echo executing some command that needs arg1 = %arg1% and argRemainder = %argsRemainder% references` ? – myusrn Aug 25 '22 at 15:53
  • 1
    @myusrn - Sure, your proposal works for some situations, but it is far from a general purpose solution, and I strive for solutions with minimal constraints. First off, I don't agree that separating the 1st argument from the others solves most use cases. My solution is easily extended by storing 1 through N, and then doing N shifts before entering the loop to populate ARGS. Second - batch is not limited to 9 arguments, but rather it only allows access to up to 9 at one time (actually 10 with `%0`). `SHIFT` is used to access arguments beyond `%9`. – dbenham Aug 26 '22 at 18:53
6

That´s easy:

setlocal ENABLEDELAYEDEXPANSION
  set "_args=%*"
  set "_args=!_args:*%1 =!"

  echo/%_args%
endlocal

Same thing with comments:

:: Enable use of ! operator for variables (! works as % after % has been processed)
setlocal ENABLEDELAYEDEXPANSION
  set "_args=%*"
  :: Remove %1 from %*
  set "_args=!_args:*%1 =!"
  :: The %_args% must be used here, before 'endlocal', as it is a local variable
  echo/%_args%
endlocal

Example:

lets say %* is "1 2 3 4":

setlocal ENABLEDELAYEDEXPANSION
  set "_args=%*"             --> _args=1 2 3 4
  set "_args=!_args:*%1 =!"  --> _args=2 3 4

  echo/%_args%
endlocal

Remarks:

  • Does not work if any argument contains the ! or & char
  • Any extra spaces in between arguments will NOT be removed
  • %_args% must be used before endlocal, because it is a local variable
  • If no arguments entered, %_args% returns * =
  • Does not shift if only 1 argument entered
cyberponk
  • 1,585
  • 18
  • 19
  • 1
    You need to remove the %1 argument, not the %0 (which is the name of the script) – Keith Twombley Nov 04 '15 at 14:51
  • Indeed, you are correct. I have edited %0 to %1 now. Thanks. – cyberponk Nov 05 '15 at 15:44
  • 5
    As written, this is dangerously wrong, because the value for the first arg could be repeated in a subsequent arg, and your code will remove both. That could be fixed by using `*` to remove everything through the first instance: `set _args=!_args:*%1=!`. Other problems that cannot be fixed is it will fail if `%1` contains `!` or `=`. – dbenham May 04 '16 at 22:07
  • Thank you dbenham, I have reviewed the code based on your good comment. – cyberponk May 10 '16 at 20:33
  • Just nitpicking here, @cyberponk, but that's not that easy... Great job! – Tarc May 06 '20 at 11:39
3

Don't think there's a simple way to do so. You could try playing with the following workaround instead:

@ECHO OFF
>tmp ECHO(%*
SET /P t=<tmp
SETLOCAL EnableDelayedExpansion
IF DEFINED t SET "t=!t:%1 =!"
ECHO(!t!

Example:

test.bat 1 2 3=4

Output:

2 3=4
Andriy M
  • 76,112
  • 17
  • 94
  • 154
  • I don't see the benefit of the temp file - `set t=%*` would work just as well. This answer will fail if %1 contains `=` or starts with `*` or `~`. Also will have problems if args are delimited with `,` or `;` instead of spaces. Better to only remove %1 and leave the delimiter(s) in place. – dbenham Feb 20 '12 at 18:26
  • Killer problem - this answer will give wrong answer if args are `A A B`. Could be improved with `set t=!t:*%1=!` – dbenham Feb 20 '12 at 18:28
  • Using `set "t=!t:*%1=!` modification, this answer will still fail if %1 contains `=`, but starting with `*` or `~` is ok. – dbenham Feb 20 '12 at 19:09
  • 1
    @dbenham: Thanks for the feedback! I think with `set /p` I was just being overcautious about something, not sure now about what exactly, and so you may well be right about `set t=%*` being no worse than using a temp file. And I agree with you on your other points. Basically, it turns out that despite batch scripting being already weak in argument handling, I managed to add even more restraint with my suggestion. :) There's a couple of (minor) advantages of my script over yours, though: `=`s and `,` are preserved if they are not part of the first argument: `1 2,3` -> `2,3` and `1 2=3` -> `2=3`. – Andriy M Feb 20 '12 at 19:48
  • Thank you for your expert advice and discussion of the pros and cons. I appreciate it! Both answers are great, but I may only accept one. I will go with the other solution because it seems to me to be more easily understandable to less proficient readers, and ease of maintenance is very important in my case. – barfuin Feb 20 '12 at 21:46
  • 1
    Preserving `2=3` is kind of pointless if it is passed on to another batch script since batch will parse it as 2 arguments `2` and `3`. The batch token delimiters are `,`, `;`, `=`, ``, ``. If you pass on `2=3` to something other than batch, then yes it could be important. – dbenham Feb 20 '12 at 22:03
1

Another easy way of doing this is:

set "_args=%*"
set "_args=%_args:* =%"

echo/%_args%

Remarks:

  • Does not work if first argument (%1) is 'quoted' or "double quoted"
  • Does not work if any argument contains the & char
  • Any extra spaces in between arguments will NOT be removed
cyberponk
  • 1,585
  • 18
  • 19
1

I had to do this recently and came up with this:

setlocal EnableDelayedExpansion

rem Number of arguments to skip
set skip=1

for %%a in (%*) do (
  if not !position! lss !skip! (
    echo Argument: '%%a'
  ) else (
    set /a "position=!position!+1"
  )
)

endlocal

It uses loop to skip over N first arguments. Can be used to execute some command per argument or build new argument list:

setlocal EnableDelayedExpansion

rem Number of arguments to skip
set skip=1

for %%a in (%*) do (
  if not !position! lss !skip! (
    set args=!args! %%a
  ) else (
    set /a "position=!position!+1"
  )
)

echo %args%

endlocal

Please note that the code above will add leading space to the new arguments. It can be removed like this:

Community
  • 1
  • 1
beatcracker
  • 6,714
  • 1
  • 18
  • 41
1

Yet another obnoxious shortcoming of DOS/Windows batch programming...

Not sure if this is actually better than some of the other answers here but thought I'd share something that seems to be working for me. This solution uses FOR loops rather than goto, and is contained in a reusable batch script.

Separate batch script, "shiftn.bat":

@echo off
setlocal EnableDelayedExpansion
set SHIFTN=%1
FOR %%i IN (%*) DO IF !SHIFTN! GEQ 0 ( set /a SHIFTN=!SHIFTN! - 1 ) ELSE ( set SHIFTEDARGS=!SHIFTEDARGS! %%i ) 
IF "%SHIFTEDARGS%" NEQ "" echo %SHIFTEDARGS:~1%

How to use shiftn.bat in another batch script; in this example getting all arguments following the first (skipped) arg:

FOR /f "usebackq delims=" %%i IN (`call shiftn.bat 1 %*`) DO set SHIFTEDARGS=%%i 

Perhaps someone else can make use of some aspects of this solution (or offer suggestions for improvement).

0

Resume of all and fix all problems:

set Args=%1
:Parse
shift
set First=%1
if not defined First goto :EndParse
  set Args=%Args% %First%
  goto :Parse
:EndParse

Unsupport spaces between arguments: 1 2 3 4 will be 1 2 3 4