Question

Okay, I learned that one can use & or && to combine multiple commands into a single line, but it seems that variables that are set aren't actually available for interpolation in the same line:

C:\Users\Andrew>set foo=hello, world!&& echo %foo%
%foo%

C:\Users\Andrew>echo %foo%
hello, world!

Why can't I make this work, and is there any way to make it work in a single line?

The reason I need a one-liner is that an external program I'm working with accepts a single command as a pre-run hook, and, of course, I need to run multiple commands.


Preemptive Defenses

  1. "hello, world! should be surrounded in double-quotes!" Actually, doing so seems to store literal double-quotes in the variable, which I do not want, e.g.

    C:\Users\Andrew>set bar="hello, world!"&& echo %bar%
    %bar%
    
    C:\Users\Andrew>echo %bar%
    "hello, world!"
    
  2. "There should be a space before the &&!" Actually, doing so seems to store a trailing space in the variable, which I do not want, e.g.

    C:\Users\Andrew>set bar="hello, world!"&& echo %bar%
    %bar%
    
    C:\Users\Andrew>echo %bar%
    "hello, world!"
    
  3. "Both!" >:(

    C:\Users\Andrew>set mu="hello, world!" && echo %mu%
    %mu%
    
    C:\Users\Andrew>echo (%mu%)
    ("hello, world!" )
    
Was it helpful?

Solution

You can do it in the same line, but I would recommend to use a batch file like preHook.bat.

As one liner

set "mu=hello, world!" && call echo %^mu%

At first you can see that the quotes are different than yours.


Here's why:

set test1="quotes"
set "test2=no quotes" with comment

In the first test, the quotes will be part of the test1 variable, as well as all characters after the last quote.

In the second test, it uses the extended syntax of the SET command.
So the content will be no quotes, as only the content to the last quote is used; the rest will be dropped.

To echo the variable content, I use call echo %^mu%, as percent expansion will expand it when the line is parsed, before any of the commands are executed.

But the call command will be executed later, and it restarts the parser, which, when used at the command line, uses different expansion rules than the batch parser: an empty variable (in this case, %^mu%, in the first time) stays unchanged in the line; but, in the next parser phase, the ^ caret will be removed.

In your case, call echo %mu% would also work, but only when mu is always empty. The caret variant also works when mu has content before the line is executed.

More about the parser at SO: How does the Windows Command Interpreter (CMD.EXE) parse scripts? And about variable expansion at SO: Variable expansion

OTHER TIPS

Though I accepted @jeb's answer, ultimately I had to go another route, because I needed to pipe the output of my command, and doing so on call led to mangled results. Indeed, the documentation for call itself remarks,

Do not use pipes and redirection symbols with call.

Instead, reminded by @Blorgbeard of cmd's /v option (and I believe that should be lowercase, not uppercase), I realized I could simply start a subprocess:

C:\Users\Andrew>cmd /v /c "set foo=hello, world!&& echo !foo!"
hello, world!

(For some reason, /v must appear before /c.) Within the quotes, I was able pipe my output to other utilities. One tip for those taking this path: If you find yourself needing to use quotes within those quotes, I suggest avoiding them altogether and trying character codes, e.g. \x20 for space, \x22 for double quotes, and so on.

For example, this was the eventual solution to my problem (warning: may cause eyes to bleed):

C:\Users\Andrew>cmd /v /c "set source=C:\source& set target=C:\target& set archive=C:\archive& robocopy.exe !source! !target! /l /e /zb /xx /xl /fp /ns /nc /ndl /np /njh /njs | sed -e s/^^[\t\x20]\+// | sed -e /^^$/d | sed -e s/^!source:\=\\!// | sed -e s/.*/xcopy\x20\/Fvikrhyz\x20\x22!source:\=\\!^&\x22\x20\x22!archive:\=\\!^&\x22/"

Try the following:

cmd.exe /v /c "set foo=hello, world & echo !foo!"

The /v argument enables delayed variable expansion. This allows you to access variables' values at execution time rather than at parse time (the default). You do this by writing !foo! instead of %foo% (with this /v option turned on).

Instead of passing /v, you can also turn delayed variable expansion on permanently via the registry, which obviously affects the entire system:

[HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor]
"DelayedExpansion"= (REG_DWORD)
1=enabled 0=disabled (default)

I could not get an IF command to work with either call or cmd /v /c so I would like to offer another option for those who may need it: use FOR /F to declare and use a variable within a single command. For example:

FOR /F %i IN ('echo 123') DO (IF %i==123 echo done)

The %i is the variable that is set with the result of the IN command, which in this example is 'echo 123'.

For values with single quotes or spaces, use the "usebackq tokens=*" flag to put the command in backquotes:

FOR /F "usebackq tokens=*" %i IN (`echo "hello, ' ' world!"`) DO (IF %i=="hello, ' ' world!" echo done)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top