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

后端 未结 4 1819
予麋鹿
予麋鹿 2021-02-14 09:13
C:\\>type c:\\output.txt
abcd
C:\\>type c:\\output.txt | set /p V1=

C:\\>set
... A bunch of junk, NOT seeing \"V1\"

What happened? According

4条回答
  •  臣服心动
    2021-02-14 10:06

    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=

    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 l_input=@FirewallAPI.dll,-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!

提交回复
热议问题