Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

echo is adding space when used with a pipe

Tags:

batch-file

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)

like image 490
panda-34 Avatar asked Jun 08 '12 12:06

panda-34


1 Answers

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.

like image 185
dbenham Avatar answered Sep 29 '22 02:09

dbenham