54

Duplicate:

Clarification: I knew of the looping approach - this worked even before Command Extensions; I was hoping for something fun and undocumented like %~*1 or whatever - just like those documented at http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/percent.mspx?mfr=true.


In a Windows batch file (with the so called "Command Extensions" on), %1 is the first argument, %2 is the second, etc. %* is all arguments concatenated.

My question: is there a way to get everything AFTER %2, for example?

I couldn't find such a thing, and it would be helpful for something I'm working on.

Community
  • 1
  • 1
noamtm
  • 12,435
  • 15
  • 71
  • 107

7 Answers7

56

There is a shorter solution (one-liner) utilizing the tokenization capabilities of for loops:

:: all_but_first.bat
echo all: %*
for /f "tokens=1,* delims= " %%a in ("%*") do set ALL_BUT_FIRST=%%b
echo all but first: %ALL_BUT_FIRST%

output:

> all_but_first.bat foo bar baz
all: foo bar baz
all but first: bar baz

Footnote: Yes, this solution has issues. Same as pretty much anything written with batch files. It's 2021. Use Powershell or literally any other actual scripting language.

Max Truxa
  • 3,308
  • 25
  • 38
  • 4
    For 'all-but-n', replace `tokens=1` with `tokens=n`. The remaining arguments will still be in `%%b` – Chris Watts Apr 03 '15 at 10:43
  • In some cases, you may need to use !ALL_BUT_FIRST! instead of %ALL_BUT_FIRST% (for instance if you're inside some other loop); see http://stackoverflow.com/questions/12423238/windows-cmd-set-within-for-loop-is-not-working – FremyCompany May 18 '15 at 10:19
  • Don't forget to use `setlocal enabledelayedexpansion` if you are working with `!var!`. – Max Truxa May 18 '15 at 13:02
  • 14
    Doesn't work when a quoted parameter contains spaces, eg. `all_but_first.bat "first arg" "second arg"` – EM0 Feb 22 '16 at 15:14
  • Works for me even with spaces, treats all `=` and `,` as arg separators – iki Apr 06 '21 at 11:05
30

I am not sure if there is a direct command but you can always use a simple loop and shift to get the result in a variable. Something like:

@echo off
set RESTVAR=
shift
:loop1
if "%1"=="" goto after_loop
set RESTVAR=%RESTVAR% %1
shift
goto loop1

:after_loop
echo %RESTVAR%

Let me know if it helps!

Samuel
  • 1,949
  • 4
  • 18
  • 30
  • Yes, but your answer is the most complete and is what I did eventually. – noamtm Jun 03 '09 at 10:27
  • 4
    There's a small problem with this answer – `RESTVAR` will contain a leading space. Here's a slightly better version http://stackoverflow.com/a/761658/1488656 – Livven Jan 19 '14 at 13:28
  • 2
    Doesn't work when an argument contains an '=' ie debug=1 becomes debug 1. – LINEMAN78 Dec 05 '17 at 18:39
  • 1
    Good answer. But there is an error on statement. It worked when i change "%1" to "%~1". – HelloWorld Mar 10 '19 at 17:11
19

The following will work for args with ", =, ' '. Based on Dmitry Sokolov answer. Fixed issue when second arg is the same as first arg.

@echo off
echo %*
set _tail=%*
call set _tail=%%_tail:*%1=%%
echo %_tail%
Raman Zhylich
  • 3,537
  • 25
  • 23
  • 1
    I think this is the only fail-safe answer and one of the simplest. It should be the accepted one. – cdlvcdlv Jun 03 '18 at 17:10
  • 2
    Please explain how this only replaces **1** instance of the string because the replacement portion of the string replace starts with `*` which makes it magical: https://gist.github.com/binki/a96bbf9a282616671ecd7386a9f17510 https://ss64.com/nt/syntax-replace.html – binki Mar 28 '19 at 20:38
  • 1
    For me, `%_tail%` has a leading space. – bers Sep 01 '19 at 08:21
  • Nice one, has leading space (just know about it, should not be an issue), and returns `*=` for no args, so you better check if they are not empty `if not "%~1"=="" call set _tail=%%_tail:*%1=%%` – iki Apr 06 '21 at 10:59
  • Also treats all `=` and `,` as arg separators for me – iki Apr 06 '21 at 11:06
  • For what is the call keyword? Would simply "set.." without "call" not work? – iuzuz Nov 17 '21 at 12:19
7

The following will work for args with ", =, ' ' (as compared to @MaxTruxa answer)

echo %*
set _all=%*
call set _tail=%%_all:*%2=%%
set _tail=%2%_tail%
echo %_tail%

Test

> get_tail.cmd "first 1" --flag="other options" --verbose
"first 1" --flag="other options" --verbose
--flag="other options" --verbose
Dmitry Sokolov
  • 3,118
  • 1
  • 30
  • 35
  • 1
    I really want an ELI5 version of that CALL SET line. – Mr. Smythe Jul 18 '17 at 16:17
  • 1
    [Advanced usage : CALLing internal commands](https://ss64.com/nt/call.html#advanced). It works like `setlocal EnableDelayedExpansion` + `set _tail=!_all:*%2=!`. – Dmitry Sokolov Jul 21 '17 at 10:28
  • 1
    It will fail on this one: get_tail.cmd -s -s arg1 – Raman Zhylich Aug 30 '17 at 20:27
  • This is the best solution for me, because the other solutions cropped my commas in the parameters. – David Gausmann Mar 15 '18 at 14:15
  • It looks like you’re doing a string replacement of the first argument against the entire set of arguments. That will fail, like @RamanZhylich said, if your first argument happens to be substring of any of your subsequent arguments. This is bad in the same way as @MaxTruxa’s answer but almost worse because it isn’t as obviously wrong. – binki Mar 28 '19 at 20:27
5

You can use SHIFT for this. It removes %1 and shifts all other arguments one lower. This script outputs all the arguments after %2 (so it outputs %3, %4...) until one of them is empty (so it's the last one):

@echo off

SHIFT
SHIFT

:loop
if "%1" == "" goto end
echo %1
SHIFT
goto loop

:end

EDIT: Removed example using %* as this doesn't work - %* always outputs all of the parameters

schnaader
  • 49,103
  • 10
  • 104
  • 136
  • Just like in sh? Great! +1 . Do you happen to know what's the powershell option? – OscarRyz Jun 01 '09 at 17:23
  • Sorry, haven't used powershell yet. – schnaader Jun 01 '09 at 17:34
  • 3
    I naively assumed this approach would work, realized that `%*` doesn’t update when using `SHIFT`, and **then** came to this SO question searching for a solution. Looks like you did the opposite :-p – binki Mar 28 '19 at 20:31
4

Building on schnaader's answer, I think this does it if you want everything after %1 concatenated.

@echo off

SHIFT

set after1=

:loop
if "%1" == "" goto end
set after1=%after1% %1
SHIFT
goto loop


:end

echo %after1%
Scott Langham
  • 58,735
  • 39
  • 131
  • 204
2

Sebi, here's the Syntax! There is a behavior, batch eating the equal signs which is not double quoted, it cause trouble in the scripts above. If you wan't to skip, i've made a modification, based on Raman Zhylich answer and strlen.cmd:

@ECHO OFF
SETLOCAL enableDelayedExpansion

SET _tail=%*
SET "_input="
SET /A _len=0

:again
SET "_param=%1"
SET "_input=%_input%%1"
FOR /L %%i in (0,1,8191) DO IF "!_param:~%%i,1!"=="" (
    REM skip param
    SET /A _len+=%%i
    REM _len can't be use in substring
    FOR /L %%j in (!_len!,1,!_len!) DO (
        REM skip param separator
        SET /A _len+=1
        IF "!_tail:~%%j,1!"=="=" (SET "_input=%_input%=" & SHIFT & goto :again)
    )
) & goto :next
:next
IF %_len% NEQ 0 SET _tail=!_tail:~%_len%!

ENDLOCAL & SET "_input=%_input%" & SET "_tail=%_tail%"