Why doesn't "set -P" work after a pipe?

The pipe seems to create a new CMD instance to carry out the next command that is receiving the pipe data. So when the pipe has concluded, that CMD instance exits and the variable is lost.


I don't know what doco you've seen for the set command but the output of set /? clearly states:

The /P switch allows you to set the value of a variable to a line of input entered by the user.

(my italics). I think set /p is getting its input from the console regardless of what you're trying to pipe in through standard input. Why it's not waiting, I'm not sure. echo xxx | set /p xx= also fails to set the variable.

But, if you want to set a variable from a single line file, you can just use one of these:

for /f "delims=" %%i in (c:\output.txt) do set V1=%%i
set /p V1=<c:\output.txt

That second one is the simplest but it doesn't help much if you want to grab the output of an arbitrary command but you may well have to direct it to a file first.

The first one allows you to execute arbitrary commands without temporary files:

for /f "delims=" %%i in ('echo AAA') do set xx=%%i

There's an interesting snippet on this page which suggests it has to do with contexts:

Ok, I found out why myself. It's because the | creates a new context so the variable never makes it out to the rest of the current context. Proof:
> set bar=
> echo aaa | (set /p bar= && set bar)
bar=aaa
> set bar
Environment variable bar not defined

although I decline to comment on the veracity of that conclusion. I don't know what contexts are in this sense, I'm just bringing it to your attention for completeness.


This is not a direct answer to the original question, which asked Why doesn't [..] work?, but this answer can be useful to people looking to a̲c̲h̲i̲e̲v̲e̲ what would be the logical result of something like this:

echo:somevalue|set /p somevar=

The normal workaround, for example for the code above, would be:

echo:somevalue>tempfile.txt&set /p somevar=<tempfile.txt

which does the work perfectly, except that it creates a new file which has to be dealt with afterwards (on top of the fact that we need to take extra steps to make sure this new file doesn't overwrite an existing one we didn't create or never intended to replace).

While it is true there is no way around using a file stream to bridge the gap across both sides of the pipe | operator as far as environment variables are concerned, being under a Windows NT environment, and provided the volume the script resides on is using the NTFS filesystem, one can use something far more elegant than temporary files: alternate data streams

Let's jump right into an example illustrating how one might use them:

echo:somevalue>".\%~nx0":temp&set /p somevar=<".\%~nx0":temp

Here %~nx0 stands for, and gets expanded to, the filename (base name + extension) of our batch file, enclosed in quotes in case it contains spaces.

Note: While one could use %0 directly (without quotes) instead to refer to the current script, .\%~nx0 is more manageable if you ever need to look at the expanded value of the command while debugging your script, especially if the full path of your script is quite long.

As such, ".\%~nx0":temp refers to an alternate data stream (ADS) of our own script named temp, for example myscript.cmd:temp, that we create (or overwrite) with the output of the echo command. Since ADS behave the same way as the default stream of a file, the echo and set commands don't see any difference.

A complete script using the method could look like this:

@echo off
set __self=".\%~nx0"
set __adsname=test.dat

:: Using some input...
set [email protected],-23000
echo:Input: %l_input%
echo:

:: ...we process it...
expand_str_res_id.exe %l_input%>%__self%:%__adsname%&set /p l_output=< %__self%:%__adsname%

:: ...and show the output, e.g. "File and Printer Sharing"
echo:Output: %l_output%
echo:

:cleanup
set l_input=
set l_output=
type nul> %__self%:%__adsname%
set __self=
set __adsname=
pause

Here, at the end of the script, the line type nul> %__self%:%__adsname, which could be expanded to something like type nul> ".\myscript.cmd":test.dat, is used to clear the content of the alternate data stream we just used. Although this sets the size of the alternate data stream to zero, it does not erase it (see notes below).

Some final notes:

  1. Most commands cannot understand or work with alternate data streams when they are provided as source files. This means the following ones cannot be used:

    • type, e.g. type myscript.cmd:data > myscript_data.txt
    • copy, e.g. copy myscript.cmd:data myscript_data.txt
    • move, e.g. move myscript.cmd:data myscript_data.txt
    • del/erase, e.g. del myscript.cmd:data
    • ren/rename, e.g. ren myscript.cmd:data myscript.cmd:data.txt
    • and so on

    In fact, it's really only file redirection that supports alternate data streams with one exception (that I can think of): the start command.

  2. Although we can quite easily clear/erase the content of an alternate data stream, deleting one is quite trickier since copying a file or renaming it does not involve any streams (well :$DATA ones), and editing/changing the content of a file only affects the default data stream. Still, we have a few options depending on what we're trying to achieve:
    • If we only have a single alternate data stream or don't mind deleting all of them at once, and the content of the file is only text, then we can create a new file by keeping only the default data stream of the original file using the following command type myscript.cmd > myscript_clean.cmd, after which the original file can be deleted.
    • If we have administrative rights on the volume and operate under Windows Vista or a later version of the OS, we can use MKLINK to create a symbolic link to one alternate data stream, which will then provide us with a standard filename to be used with the usual file management commands.
    • Alternatively, one can use one of the many tools available to manipulate alternate data streams, like Streams, LADS, or AlternateStreamView.
  3. A useful command to list regular files including alternate data streams from the command line is dir /a-d /r. You can then use any program to access the names (alternate) data stream of a file, e.g. notepad.exe myscript.cmd:data

I hope this helps!