64

Can you redirect the output of a command to a variable with pipes?

I haven't tried much as I haven't been able to think of anything to try, but I have tried one method (with two variations)...

For example:

echo Hello|set text=

Didn't work, neither did:

echo Hello | set text=

I know you can do it fairly easily with the FOR command, but I think it would look "nicer" with a pipe.

And if you're wondering, I don't have a specific reason I'm asking this other than I'm curious and I can't find the answer.

viggity
  • 15,039
  • 7
  • 88
  • 96
BDM
  • 3,760
  • 3
  • 19
  • 27
  • 1
    Check dolphy's answer here: http://stackoverflow.com/questions/6359820/batch-files-how-to-set-commands-output-as-a-variable - if you really don't want to use for you can use a temporary file. There's no way to do it with a pipe, though. – Vicky Feb 19 '13 at 10:35
  • 1
    This problem is described with detail here: http://stackoverflow.com/questions/8192318/why-does-delayed-expansion-fail-when-inside-a-piped-block-of-code – Aacini Feb 19 '13 at 12:48
  • http://superuser.com/a/1007898/429721 perfected for pipes, stdout/err redirection, multiple executables, output storing and processing value as number and other manipulations... works quite well.. –  Dec 02 '15 at 00:37

6 Answers6

56

Your way can't work for two reasons.

You need to use set /p text= for setting the variable with user input.
The other problem is the pipe.
A pipe starts two asynchronous cmd.exe instances and after finishing the job both instances are closed.

That's the cause why it seems that the variables are not set, but a small example shows that they are set but the result is lost later.

set myVar=origin
echo Hello | (set /p myVar= & set myVar)
set myVar

Outputs

Hello
origin

Alternatives: You can use the FOR loop to get values into variables or also temp files.

for /f "delims=" %%A in ('echo hello') do set "var=%%A"
echo %var%

or

>output.tmp echo Hello
>>output.tmp echo world

<output.tmp (
  set /p line1=
  set /p line2=
)
echo %line1%
echo %line2%

Alternative with a macro:

You can use a batch macro, this is a bit like the bash equivalent

@echo off

REM *** Get version string 
%$set% versionString="ver"
echo The version is %versionString[0]%

REM *** Get all drive letters
`%$set% driveLetters="wmic logicaldisk get name /value | findstr "Name""
call :ShowVariable driveLetters

The definition of the macro can be found at
SO:Assign output of a program to a variable using a MS batch file

jeb
  • 78,592
  • 17
  • 171
  • 225
  • 3
    So, there is no way around the limitations without a temp file or the "for" command? Even if there isn't, thanks anyway. – BDM Feb 20 '13 at 05:38
  • Obviously you can use `FOR` but then you don't need a pipe. The same apply to temp files – jeb Feb 20 '13 at 05:45
  • 2
    btw, it appears in my tests that you can only use 1 command in a for statement. You cannot chain more commands together with &. – end-user Oct 04 '13 at 18:52
  • @end-user You can use more than one command, but it's better to combine them with parenthesis `for %%a in (a b c) do (echo %%a echo ----)` – jeb Oct 05 '13 at 07:44
  • No, I meant in the evaluation. But it turns out I can if I escape the & sign: `for /f %%i in ('first command ^&^& second command') do echo result: %%i` – end-user Oct 07 '13 at 11:26
  • You tmp file does nto work for me? It writes the correct data to a file for me. But it does not read it back into the variables correctly. I changed my command to `wmic os get osarchitecture` – Zapnologica Mar 03 '14 at 10:01
  • Thank you for a complete discussion of the alternatives. It is unfortunate that the for statement is so obtuse, but it worked for me – Jim Robinson Dec 08 '21 at 06:19
15

The lack of a Linux-like backtick/backquote facility is a major annoyance of the pre-PowerShell world. Using backquotes via for-loops is not at all cosy. So we need kinda of setvar myvar cmd-line command.

In my %path% I have a dir with a number of bins and batches to cope with those Win shortcomings.

One batch I wrote is:

:: setvar varname cmd
:: Set VARNAME to the output of CMD
:: Triple escape pipes, eg:
:: setvar x  dir c:\ ^^^| sort 
:: -----------------------------

@echo off
SETLOCAL

:: Get command from argument 
for /F "tokens=1,*" %%a in ("%*") do set cmd=%%b

:: Get output and set var
for /F "usebackq delims=" %%a in (`%cmd%`) do (
     ENDLOCAL
     set %1=%%a
)

:: Show results 
SETLOCAL EnableDelayedExpansion
echo %1=!%1! 

So in your case, you would type:

> setvar text echo Hello
text=Hello 

The script informs you of the results, which means you can:

> echo text var is now %text%
text var is now Hello 

You can use whatever command:

> setvar text FIND "Jones" names.txt

What if the command you want to pipe to some variable contains itself a pipe?
Triple escape it, ^^^|:

> setvar text dir c:\ ^^^| find "Win"
Community
  • 1
  • 1
antonio
  • 10,629
  • 13
  • 68
  • 136
  • What does the `usebackq delims=` part do there? – Richard Le Mesurier May 10 '15 at 12:31
  • 1
    @RichardLeMesurier: `usebackq` specifies to use a linux-like semantics, where a back quoted string is executed as a command and the output parsed into the `for` variables (here `%%a`). `delims=x` specifies the delimiter to be used in parsing (here no delimiters). – antonio May 11 '15 at 22:17
  • Thx for clarification. I used this pattern to great success in a build tool. This answer should be the accepted one. – Richard Le Mesurier May 12 '15 at 06:42
8

I find myself a tad amazed at the lack of what I consider the best answer to this question anywhere on the internet. I struggled for many years to find the answer. Many answers online come close, but none really answer it. The real answer is

(cmd & echo.) >2 & (set /p =)<2

The "secret sauce" being the "closely guarded coveted secret" that "echo." sends a CR/LF (ENTER/new line/0x0D0A). Otherwise, what I am doing here is redirecting the output of the first command to the standard error stream. I then redirect the standard error stream into the standard input stream for the "set /p =" command.

Example:

(echo foo & echo.) >2 & (set /p bar=)<2

Charles
  • 338
  • 3
  • 8
  • 6
    I'm sorry to spoil your dreams, but this doesn't work via STDERR, but via a file with the name `2` (check with `dir`) – Stephan May 07 '20 at 19:51
  • Oh. Thank you Stephan. It appears I still do not know if it is possible to stream the output from one command to the input of another without a file as an intermediate, apart from the rare except of pipe to more. It seems odd that in Windows and DOS that more seems one of only a few commands that can have input piped to it. After hours over the span of over a decade, I am yet to have seen anyone explain this satisfactorily. – Charles May 07 '20 at 21:15
  • 1
    there are a few more commands, that take input through a pipe: more, sort, find, findstr, clip, choice, date, time, diskpart, wmic, schtask, pause and (not verified but probably yes): ftp, telnet, ssh and maybe a few more. – Stephan May 08 '20 at 08:09
  • Very interesting. Thank you for sharing Stephan. – Charles May 11 '20 at 04:34
  • 1
    You have saved me next 10-20 years of looking for this solution!!! – Alexander Jul 11 '21 at 20:54
  • I recommend adding an UPDATE to this answer with: `(cmd & echo.) >temp & (set /p =) – MatrixRonny Jul 05 '23 at 11:10
5

THIS DOESN'T USE PIPEs, but requires a single tempfile
I used this to put simplified timestamps into a lowtech daily maintenance batfile

We have already Short-formatted our System-Time to HHmm, (which is 2245 for 10:45PM)
I direct output of Maint-Routines to logfiles with a $DATE%@%TIME% timestamp;
. . . but %TIME% is a long ugly string (ex. 224513.56, for down to the hundredths of a sec)

SOLUTION OVERVIEW:
1. Use redirection (">") to send the command "TIME /T" everytime to OVERWRITE a temp-file in the %TEMP% DIRECTORY
2. Then use that tempfile as the input to set a new variable (I called it NOW)
3. Replace

echo $DATE%@%TIME% blah-blah-blah >> %logfile%
      with
echo $DATE%@%NOW% blah-blah-blah >> %logfile%


====DIFFERENCE IN OUTPUT:
BEFORE:
SUCCESSFUL TIMESYNCH 29Dec14@222552.30
AFTER:
SUCCESSFUL TIMESYNCH 29Dec14@2252


ACTUAL CODE:

TIME /T > %TEMP%\DailyTemp.txt
SET /p NOW=<%TEMP%\DailyTemp.txt
echo $DATE%@%NOW% blah-blah-blah >> %logfile%


AFTERMATH:
All that remains afterwards is the appended logfile, and constantly overwritten tempfile. And if the Tempfile is ever deleted, it will be re-created as necessary.

Zip Zinzel
  • 69
  • 1
  • 3
4

You can set the output to a temporary file and the read the data from the file after that you can delete the temporary file.

echo %date%>temp.txt
set /p myVarDate= < temp.txt
echo Date is %myVarDate%
del temp.txt

In this variable myVarDate contains the output of command.

Gerhard
  • 22,678
  • 7
  • 27
  • 43
3

In a batch file I usually create a file in the temp directory and append output from a program, then I call it with a variable-name to set that variable. Like this:

:: Create a set_var.cmd file containing: set %1=
set /p="set %%1="<nul>"%temp%\set_var.cmd"

:: Append output from a command
ipconfig | find "IPv4" >> "%temp%\set_var.cmd"
call "%temp%\set_var.cmd" IPAddress
echo %IPAddress%