Echoing Multiple Lines into a Pipe

If you’ve been batch scripting on Windows for a while, you probably know the way to echo a blank line: a period with no space in front of it.

echo.

And the way to chain commands together:

  • A || B runs B if A failed
  • A && B runs B if A succeeded
  • A & B runs B regardless

It may not be obvious, but you can echo multiple lines into a pipe without resorting to a temp file.

(echo def& echo abc) | sort

You will have to butt the ampersand against the end of each echo, or you’ll be sending spaces at the end of each line.

If you make a lot of lines, mashing them together gets hard to read. The solution to that obscure situation is below.
 
Edit (2010-04-25): Argh! Ampersand butting works for redirecting to a file, and FOR loops (as you’ll see in a later post), but plain ol’ pipes still get trailing spaces!
Example: Pipe into sort, get spaces. Dump to tmp.txt, no spaces.
My “Right Way”, below the fold, somehow only got a space on the final line.
 

The Broken Way

Finding the magic syntax at the end of the post was tricky. Forgive the plain code sections; had to avoid justified word spacing.

My first attempt was an equally tricky variable that contained a newline to concatenate. Did not work.


REM Filling %N% with a newline (\r\n aka <0x0D,0x0A>)
SET NLM=^
 
 
SET N=^^^%NLM%%NLM%^%NLM%%NLM%
REM The above two lines must be blank

* When you copy/paste, remove the lone spaces on empty lines.
 

  • Cmd.exe’s escape char is “^”. That will be important later.
  • Technically NLM holds a genuine newline, which would be like hitting return halfway through writing a command (bad).
  • N actually contains “^{0x0D,0x0A}{0x0D,0x0A}” aka an escape and two genuine newlines.
    (after Cmd interprets that SET).
    As crazy as it looks, that’s the safe sequence of chars to have in a cmd line, for one round of interpretation at least.
  • If you type ^{enter}{enter} at a real live prompt in the middle of writing an echo, you’ll see a confusing “More?” message, but when you finish the command, you’ll get a newline in there.

 


ECHO def%N%abc| sort
REM Gets interpreted to pipe "def\r\nabc" into sort.
REM This is fine, so long as Cmd never sees that N again.

REM In other words, don't use N to set another variable!
REM SET zzz=abc%N%def
REM ECHO %zzz%
REM When zzz gets parsed, it's like hitting return after 'c'.

REM So this is all kinds of broken...
SET msg=This is the first line.
SET msg=%msg%%N%This is the second line.
SET msg=%msg%%N%This is the third line.
SET msg=%msg%%N%This is the fourth line.
ECHO %msg%|sort

Unlike most other languages, every time you use a variable in batch, its contents get interpreted again. And if you try to concatenate, the oldest segments will be interpreted more times than the youngest bits. Madness.
 
 

The Right Way

Now the way that really works.


@ECHO OFF
(ECHO This is the first line.^
& ECHO This is the second line.^
& ECHO This is the third line.^
& ECHO Your name is %username%.^
& ECHO This line has ^^^(some^^^) special ^^^> characters.^
& ECHO This is the sixth line.) | sort

Each line ends with a caret, which escapes the newline. It looks simple, but if you stray from that arrangement, Cmd will silently die without giving any hints.
Cmd seems to be interpreting these lines twice-over, which is why I had to escape the caret.

  • Original: “^^^(“
  • Pass one: “^(“
  • Pass two: “(“

When you need to do this? It comes in handy when you need to echo source code of some other language into an interpreter (sqlcmd/osql, spidermonkey), and you’re generating the text on-the-fly to substitute batch arguments and variables.

Or to feed netcat as it spoofs a text protocol client (POP, HTTP, etc), though you’ll want to set netcat’s line-by-line delay to give remote servers time to chat back.

Tags:

Leave a Reply