42

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
  • 15,975
  • 10
  • 85
  • 128

7 Answers7

21

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
  • 123,415
  • 27
  • 239
  • 376
  • 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
4

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,343
  • 15
  • 18
  • 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
  • 2
    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
  • 73,804
  • 16
  • 91
  • 150
  • 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,343
  • 15
  • 18
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,344
  • 1
  • 17
  • 38
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