4

If I have run set as follows:

C:\Users\vagrant>SET UNDEFINED=foo

C:\Users\vagrant>SET UNDEFINED
UNDEFINED=foo

C:\Users\vagrant>SET UNDEFINED  
Environment variable UNDEFINED   not defined

C:\Users\vagrant>SET UNDEFINED | more
UNDEFINED=foo

C:\Users\vagrant>SET UNDEFINED >nul

C:\Users\vagrant>SET UNDEFINED >nul 
Environment variable UNDEFINED   not defined

C:\Users\vagrant>SET UNDEFINED  | more
Environment variable UNDEFINED   not defined

C:\Users\vagrant>SET UNDEFINED >nul | more
Environment variable UNDEFINED   not defined

C:\Users\vagrant>SET UNDEFINED >nul| more

C:\Users\vagrant>SET UNDEFINED 2>nul | more

C:\Users\vagrant>SET UNDEFINED 2>nul| more
UNDEFINED=foo

Note, the 2nd command above is SET UNDEFINED , there is two space followed. And SET UNDEFINED >nul , SET UNDEFINED >nul | more, SET UNDEFINED 2>nul | more with one more space before |. In those commands, set parse the variable with two more spaces. So how set parse the variable names. I have also found cmd parse scripts, but here how the variable name is tokenized?

Edit The issue occurs when spaces are use pre file redirect. In other words adding spaces after the string and redirect. Example: echo foo>bar.txt The spaces preceding bar.txt are appended to the file as well as foo .

Here are examples of this:

Example:

enter image description here

Results in:

enter image description here

AcK
  • 2,063
  • 2
  • 20
  • 27
zhenguoli
  • 2,268
  • 1
  • 14
  • 31
  • 3
    When you add the additional spaces, you are effectively telling set to do show the value of a variable called "UNDEFINED "` or `%UNDEFINED %` which is in fact undefined. You can see that the spaces will no longer be a concern if you do `set "UNDEFINED" ` with the spaces after the double quotes. It is the same as doing `set UNDEFINED =foo` which will not return a variable for `%UNDEFINED%` bu instead for `%UNDEFINED %` – Gerhard Oct 29 '20 at 07:15
  • 2
    Please read my answer on [Why is no string output with 'echo %var%' after using 'set var = text' on command line?](https://stackoverflow.com/a/26388460/3074564) The simple solution is `set "UNDEFINED"` to get output a list of environment variables of which name starts case-insensitive with the word `UNDEFINED` independent on further spaces on the command line. – Mofi Oct 29 '20 at 07:18
  • @Mofi. Thanks. Quoting the variable is a good method the avoid the problem. But what bothers me is why the output of command `SET UNDEFINED >nul | more` is strange. – zhenguoli Oct 29 '20 at 07:35
  • @zhenguoli, the same applies there as I mentioned in the answer below. See the edit I made to the answer. – Gerhard Oct 29 '20 at 07:42
  • @zhenguoli I explained in detail in [this answer](https://stackoverflow.com/a/46972524/3074564) what happens with a space left to `>nul` on using commands like `echo` or `set` which do not interpret a space as argument separator. This can be seen on creating a batch file with just the two lines `set UNDEFINED=foo` and `set UNDEFINED >nul | more` and execute this batch file from within a command prompt window. The second command line really executed is `set UNDEFINED   1>nul  | more`. Please note the extra spaces inserted by `cmd.exe`. One solution is `set "UNDEFINED" >nul | more`. – Mofi Oct 29 '20 at 15:43
  • @Mofi, thanks. I'll take some time to read. – zhenguoli Oct 30 '20 at 01:05

2 Answers2

5

When you add the additional spaces, you are effectively telling set to do show the value of a variable called "UNDEFINED " or %UNDEFINED % which is in fact undefined. You can see that the spaces will no longer be a concern if you do set "UNDEFINED" with the spaces after the double quotes. It is the same as doing set UNDEFINED =foo which will not return a variable for %UNDEFINED% bu instead for %UNDEFINED %

Here are examples of this:

set UNDEFINED=foo
set UNDEFINED =foo

when then run, without any additional spaces:

set UNDEFINED The result is as expected.

UNDEFINED=foo
UNDEFINED =foo

but when you run it with the additional spaces set UNDEFINED the result no longer matches %UNDEFINED% due to the additional spaces you have given.

UNDEFINED =foo

Here we can show more how the matching works

set UNDEF=foo
set UNDEFI=foo
set UNDEFIN=foo
set UNDEFINE=foo
set UNDEFINED=foo
set UNDEFINED =foo
set UNDEFINED  =foo
set UNDEFINED   =foo

now see the results of all of these

set UN
set UNDEF
set UNDEFINE
set UNDEFINED

and obviously by adding the spaces: set UNDEFINED

but if we add 4 spaces, representing a variable we've never set, then we will get un undefined variable result. set UNDEFINED

Finally, to overcome this is to ensure we set variables correctly and double quote them to get the desired results.

set "UNDEFINED=foo"
set "UNDEFINED"

The latter set command will not care how many spaces you give after the double quotes ended, it will return any variable that has the the word UNDEFINED in it. i.e set "UNDEFINED"

The same goes for results using redirects > or pipe |. Specifically using redirect. everything before the > is seen as the string you want to redirect. Example:

echo foo >out.txt

Will result in out.txt containing foo including the space. Therefore excluding the space is required, but that becomes an issue, should your string end with a number. i.e

echo foo2>out.txt

Which will redirect stderr to file. So we overcome that again, by parenthesizing the code:

(echo foo)>out.txt

Therefore, given your examples, though piping to nul will return no result, unless you specify the type (stdout/stderr)

(SET UNDEFINED)2>nul|more
(SET UNDEFINED)1>nul|more
(SET UNDEFINED)>nul|more

EDIT

Again. Additional spaces, preceding the strings with spaces, will add spaces to the output. Again using echo

(echo foo | more)>out.txt

There is a space between foo and | if you look at the results in out.txt you will notice the space. The same applies for redirect > Hence the parenthesized blocks used in these instances to eliminate whitespace.

EDIT2

As per the screenshots you have provided. cmd uses the redirect in a way that it does not really care where you place it.

>out.txt echo foo

will result in:

foo

in the file. where adding spaces, will be appended to the file

>out.txt echo foo will result in foo

Similarly to your example, which will echo anything you give in the line to the redirect.

echo foo>out.txt

or

echo foo>out.txt . will append to the file as redirect is really the key function here where it will append what you give it.

You will however notice that the redirect on its own, then giving the additional spaces before the command will not do this. This is simply because you ask the system to redirect a string to file where the spaces then become the command separators. So this:

>out.txt echo foo will simply result in only foo in your output file.

So.. The best method to ensure we do not add these unwanted whitespace is to do: >out.txt(echo foo)

Gerhard
  • 22,678
  • 7
  • 27
  • 43
  • Thanks. But for the command `SET UNDEFINED >nul | more`. The name ends with two spaces. It is so strange as if the space before `|` operator is appended to the name `UNDEFINED `. – zhenguoli Oct 29 '20 at 07:32
  • `echo` may be a special command which not taking space as delimiters. (I'm not sure.) But for `SET UNDEFINED >nul ` (which ends with a space), it will see the variable name `UNDEFINED ` (ends with two space). So the ending space is appended to the variable name. I wonder why the ending space is appended to the name. – zhenguoli Oct 29 '20 at 07:52
  • because the spaces are all passed as part of the string to the redirect or pipe. so 2 additional spaces will be added when 2 is given. Hence the parenthesis and no space `(SET UNDEFINED)>nul|more` – Gerhard Oct 29 '20 at 07:59
  • I may know what you mean. `echo UNDEFINED>out.txt ` will write `UNDEFINED ` to `out.txt` though the 2 more space is at the end of the command line. But the behavior is odd. Linux shell never works as this. – zhenguoli Oct 29 '20 at 08:16
1

The syntax set VAR seems to only tolerate a single trailing SPACE (though when VAR is undefined, the SPACE is included in the error message: Environment variable VAR not defined). This is even true when you use quotation marks, hence set "VAR" and set "VAR " both return the value of VAR.

Even more strange is the fact that an additional character behind the trailing SPACE, like in set VAR X or set "VAR X", still returns the variable value.

This is caused by the specific handling and parsing of the command line by the set command. I cannot answer the question "why" because I am not one of the developers of cmd.exe.


When using redirection (like in set VAR > con) the Command Interpreter removes the redirection expression (> con) at one point (during Phase 2 as described in How does the Windows Command Interpreter (CMD.EXE) parse scripts?), independent on the command, leaving behind the remaining command line, including potential SPACEs. So set VAR > con then becomes set VAR > con + SPACE, which returns the value of VAR when it is defined, and set VAR > con + SPACE becomes set VAR + SPACE + SPACE, which fails in returning the value of VAR. Also echo text> con + SPACEs maintains the trailing SPACEs behind the redirection part, which become then echoed.

For set, using quotation like set "VAR" > con does not care about how many trailing SPACEs there are and therefore works as expected. For echo, an option is to use parentheses like (echo text) > con, and another one is to move the redirection part to the front like > con echo text (but obviously avoiding trailing SPACEs).

Take a look at this closely related question-and-answer thread of mine: Conditional execution behind set VAR 2> nul fails.

aschipfl
  • 33,626
  • 12
  • 54
  • 99
  • 2
    No, what you are referring to is about `:Labels`; you need to refer to the statements *»If one of the special characters `&` `|` `<` or `>`, […]«* and *»In the case of `<`, `<<`, `>`, or `>>` redirection, the redirection clause is parsed, temporarily removed, […]«* (in phase 2), and also phase 5.5 (execution of redirection)… – aschipfl Oct 29 '20 at 12:05
  • Thanks. The problem cames from checking if the variable is defined with command `SET VAR 2>nul | findstr /I "."`, but it fails. The parsing process is a bit harder for me to understand, I may need some time to dig into it. – zhenguoli Oct 29 '20 at 12:17