4

Let's consider this FOR loop in a Windows batch script:

D:\MiLu\Dev\C++\temp :: type string.bat
@ECHO OFF
FOR %%a IN (%*) DO ECHO %%a

It echoess all the arguments, one by one. Really?

D:\MiLu\Dev\C++\temp :: string.bat foo.obj bar.obj CPPFLAGS=/EHsc
foo.obj
bar.obj
CPPFLAGS
/EHsc

It splits command-line arguments not only on spaces (good), but also on = (not good). How can I prevent this from happening?

What I want to achieve is simple: A wrapper around NMAKE.exe that specifies /nologo to nmake and also - and this is the problem - to the compiler via the environment variables CFLAGS and CPPFLAGS while at the same time including any settings for CFLAGS and CPPFLAGS supplied on the command line.

In other words, I want to have the script add /nologo to the command line input for CFLAGS and CPPFLAGS even when there is none. Always /nologo! Don't annoy me with your logo, comrade compiler!

Update

Here's what I've come up with based on Mike's answer:

@ECHO OFF
SETLOCAL
SETLOCAL ENABLEDELAYEDEXPANSION

FOR %%a IN (%*) DO (
    SET var1=%%a
    ECHO %%a - !var1! - !var1:~0,1!
    IF "!var1:~0,1!" EQU "/" (
        ECHO gefunden: %%a !var1!
    )
)

Going to continue tomorrow ...

Update 2

Okay, given that tomorrow is already here I might just as well continue ... so here's a working solution, proudly presented. Feel free to comment on how to improve it.

@ECHO OFF
SETLOCAL
SETLOCAL ENABLEDELAYEDEXPANSION

SET files=
SET   CFLAGS=/nologo %CFLAGS%
SET CPPFLAGS=/nologo %CPPFLAGS%

SET state=normal
FOR %%a IN (%*) DO (
    SET curarg=%%a
    REM ECHO %%a - !curarg! - !curarg:~0,1!
    IF /I "%%a" EQU "CFLAGS" (
        SET state=expecting_cflags
    ) ELSE IF /I "%%a" EQU "CPPFLAGS" (
        SET state=expecting_cppflags
    ) ELSE (
        IF "!curarg:~0,1!" EQU "/" (
            REM ECHO gefunden: %%a !curarg!
            IF "!state!" EQU "expecting_cflags" (
                REM ECHO expecting_cflags
                SET CFLAGS=!CFLAGS! !curarg!
            ) ELSE IF "!state!" EQU "expecting_cppflags" (
                REM ECHO expecting_cppflags
                SET CPPFLAGS=!CPPFLAGS! !curarg!
            ) ELSE (
                ECHO Logikfehler >&2
            )
        ) ELSE (
            SET files=!files! !curarg!
        )
        SET state=normal
    )
)
ECHO Dateien:  !files! >&2
ECHO CFLAGS:   !CFLAGS! >&2
ECHO CPPFLAGS: !CPPFLAGS! >&2
:: ECHO ON
nmake /nologo %files% CFLAGS="%CFLAGS%" CPPFLAGS="%CPPFLAGS%"
ENDLOCAL
Lumi
  • 14,775
  • 8
  • 59
  • 92
  • The standard delimiters for Batch file parameters and FOR sets are comma, semicolon and equal-sign, besides spaces. – Aacini Jan 30 '12 at 03:26
  • Thanks, @Aacini. Here's a [useful example page at SS64.COM](http://ss64.com/nt/syntax-esc.html). Doesn't look like there's a way to override the delimiters of the simple FOR loop (without `/F`), does it? – Lumi Jan 30 '12 at 20:30

3 Answers3

2

Have you tried the following?

string.bat foo.obj bar.obj "CPPFLAGS=/EHsc"

If you're appending the CPPFLAGS argument yourself, try enclosing it in quotes.

Reference: http://www.robvanderwoude.com/parameters.php

Dawood
  • 5,106
  • 4
  • 23
  • 27
  • Yes, I had tried enclosing `CPPFLAGS=/EHsc` in double quotes, and it does work. I didn't consider it convenient, though. Useful link, by the way. [Here's another one.](http://www.dostips.com/) – Lumi Jan 30 '12 at 00:51
2

One way to work around the problem would be to specify "CPPFLAGS=/EHsc" and then in the loop use %%~a to get rid of the double quotes.

Another way to work around the problem would be to check the first character of %%a, and if it is a / then prepend a = to it. In order to achieve this you will need to setlocal enabledelayedexpansion, assign %%a to a variable, and then use the %variable:~1,1% notation to extract the first character so you can compare it against /. For more information about this notation, type help set.

Update (after OP's update)

The following fragment appears to work, it is a bit simpler than the corresponding fragment in your solution, and it does not contain any hard-coded names of the arguments, so it is more general-purpose:

SET allargs=
FOR %%a IN (%*) DO (
    SET curarg=%%a
    IF "!curarg:~0,1!" EQU "/" (
        SET allargs=!allargs!=!curarg!
    ) ELSE (
        SET allargs=!allargs! !curarg!
    )
)
ECHO !allargs!
Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
  • Thanks, halfway there. Going to update my question with what I've achieved so far. – Lumi Jan 30 '12 at 01:09
  • I updated my answer with a suggestion for an improvement on what you have come up with. – Mike Nakis Jan 30 '12 at 14:48
  • Thanks, Mike. Unfortunately, I don't understand your improvement. It appears to simply reproduce the original `%*`. I'd need a way to merge in my default options, originally `/nologo`, but `/W4` is useful as well. I can supply `CFLAGS` or `CPPFLAGS` only once. So how would I merge in my defaults with your solution? – Lumi Jan 30 '12 at 19:24
  • Okay, I may have misunderstood what you are trying to do. I thought the only problem was that you did not want to lose the '=' sign. Apparently you want to do something more complex than that: in the command-line of the batch-file you are trying to combine parameters to CL as well as parameters to the batch file itself. So, scratch my update. Your solution is fine. – Mike Nakis Jan 30 '12 at 19:56
  • Thanks again for clarifying. Giving you the final vote as you provided the clue to solving this. Man, that batch scripting is difficult! All those cryptic `% ! %% :~`. (Same story with Bash, nearly impossible to remember that stuff.) – Lumi Jan 30 '12 at 20:34
  • Thanks. Yes, the complexity of these things is insane, and it is all in the name of not having to enclose strings in quotes. Sometimes I think it is not worth the hassle, twisting these shell languages to get them to do what you want, and it is better to just write the program in a real language. – Mike Nakis Jan 30 '12 at 22:13
2

You can use for /f in a loop to force splitting argument list by space and not by equal sign. Try something like this:

@echo off
setlocal enabledelayedexpansion

set params=%*

:loop
for /f "usebackq tokens=1,*" %%A in ('!params!') do (
  echo %%A
  set params=%%B
)
if not "!params!"=="" goto loop

endlocal
MBu
  • 2,880
  • 2
  • 19
  • 25
  • Works nicely, thanks! Had tried this approach, but on the parameter list directly (`%*`), not on a variable. And that didn't work nicely. Sort of counter-intuitive: you assign the parameter to a variable, and the behaviour changes completely. Okay, guess that's because `%*` is a list beast. – Lumi Jan 30 '12 at 13:24
  • 1
    I used `for /f` because it allows to declare the character I want to split the string by (by default it is a space, and only space - no equal sign). But `for /f` is for iterating through lines and splitting them into fields, so I had to add external loop and reformat the input string each time - that's why I used the variable. In this case `for` is just a string operation: "put the string up to the first space in %%A and the rest in %%B" - no looping. – MBu Jan 30 '12 at 13:57