9

While answering this question I found some strange behavior for which I have no explanation

for /f "delims=" %a in ('(for /l %z in (1,1,10^) do @echo %z^)') do @echo %a0

You'll see numbers 10..100, now just add the pipe, e.g. to sort or more, whatever:

for /f "delims=" %a in ('(for /l %z in (1,1,10^) do @echo %z^)^|sort') do @echo %a0

There'll be spaces added between %a and 0! Looks like echo-ing something through a pipe adds a trailing space, it can be easily seen:

>_tempfile echo no space here
>_tempfile echo and here's a space|more

and even

>_tempfile <nul set /p =also a space|sort

(probably uses echo to print the prompt)

This doesn't happen when there's no output redirection (whether into a file or for command). Is it a bug or am I missing something? How do I get rid of the space? (besides the dirty hack of stripping the last character with var:~0,-1)

Community
  • 1
  • 1
panda-34
  • 4,089
  • 20
  • 25

2 Answers2

8

Excellent and interesting question (+1)

The space is introduced by the pipe mechanism of the CMD parser, not by SORT.

When you execute a command using FOR /F, the command is executed in its own CMD shell. Also, each side of a pipe is executed in its own CMD shell. See Why does delayed expansion fail when inside a piped block of code? for more info.

So your command actually instantiates 3 CMD shells, one for the FOR /F command, which in turn creates 2 for each side of the pipe.

You can see how the commands get parsed and fed into the CMD shell using the %CMDCMDLINE% dynamic variable. Because we are executing the command from the command line, we need to escape at least one character in the variable name twice so that it doesn't get expanded until it reaches the inner most CMD shell.

Here is the command with the results (the leading > is my command prompt):

>for /f "delims=" %a in ('(echo %^^^cmdcmdline%^&for /l %z in (1,1,10^) do @echo %z^)^|sort') do @echo %a0
1 0
10 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
9 0
C:\Windows\system32\cmd.exe  /S /D /c" ( echo %cmdcmdline% & FOR /L %z in (1 1 10) do @ echo %z )" 0

The last line of output is the command line used for the left side of the pipe. You can see how the parser added spaces in a number of places.

You can circumvent the problem by using a simple batch script to echo the value instead of the ECHO command.

echoArgs.bat

@echo(%*

Now when you run this command you get the desired result

>for /f "delims=" %a in ('(for /l %z in (1,1,10^) do @echoArgs %z^)^|sort') do @echo %a0
10
100
20
30
40
50
60
70
80
90

Another method to circumvent the problem is to create a variable with your ECHO command and escape the expansion of the variable appropriately.

>set cmd=@(echo %z)

>for /f "delims=" %a in ('(for /l %z in (1,1,10^) do %^^^cmd%^)^|sort') do @echo %a0
10
100
20
30
40
50
60
70
80
90

EDIT

The >_tempfile echo and here's a space|more example is also interesting. There is an extra space at the end of the output file. But Chad Nouis is correct that nothing is being sorted because of the redirection of the left side. Any command could be used on the right side and the result would be the same.

The source of the problem is still the parser, but the way the parser restructures the commmand is interesting.

>>_tempfile echo %^cmdcmdline%|rem

>type _tempfile
C:\Windows\system32\cmd.exe  /S /D /c" echo %cmdcmdline% 1>_tempfile"

Notice how the redirection is moved from the beginning to the end of the command, and the file handle of 1 is explicitly added. You can certainly see where the extra space comes from.

Community
  • 1
  • 1
dbenham
  • 127,446
  • 28
  • 251
  • 390
  • Ah, _he that increaseth knowledge increaseth sorrow_. Never thought about pipe as more than stdin/stdout connection. Accepted your answer for explanation but not for solution. Here's the simple one: `for /f "delims=" %a in ('cmd /c "for /l %z in (1,1,10) do @echo %z"^|sort') do @echo %a0` – panda-34 Jun 08 '12 at 17:11
  • @panda-34 - That certainly is another solution. – dbenham Jun 08 '12 at 17:22
  • +1, I'm too late, I like this type of questions, but you answered it already with your good analsysis – jeb Jun 08 '12 at 22:07
0

Redirection operators apply to the command they are closest to--not the entire line.

In your three test cases, you're redirecting the stdout of the echo and set commands to a file. By redirecting stdout, there is nothing left to pipe to more or sort.

In your nested for loop example, I think there are some unneeded parentheses causing the headaches. Try this instead:

for /f "delims=" %a in ('for /l %z in (1,1,10^) do @echo %z^|sort') do @echo %a0
Chad Nouis
  • 6,861
  • 1
  • 27
  • 28
  • But if you look at the content of _tempfile when it was created on the left side of a pipe you will see an extra space at the end. As I explained in [my answer](http://stackoverflow.com/a/10950927/1012053), the pipe parser is introducing the space. But you have a valid point in that the contents of the temp file did not pass through SORT. Your solution for the FOR case works. But there may be times when the left side of the pipe is a block of commands that must be in parens. – dbenham Jun 08 '12 at 15:51
  • 2
    Did you try to execute your command? Do the results appear sorted to you? – panda-34 Jun 08 '12 at 16:46
  • I see what panda-34 is getting at. The parens are required so that the entire FOR output is sorted as one set. Without the parens the sort is applied to each line individually, which of course does nothing useful. *(this is a corrected version of my prior deleted comment)* – dbenham Jun 08 '12 at 22:56