echo multiple lines without an ending of space new line

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
taripo
Posts: 228
Joined: 01 Aug 2011 13:48

Re: echo multiple lines without an ending of space new line

#16 Post by taripo » 12 Sep 2014 05:12

I see it requires EnableDelayedExpansion and !VAR!

But why "usebackq"?
And why(for the do nothing), the copy /z.
I just made the part between 'for' and 'do' a bit shorter and simpler and simpler looking.

I know the importance of LF but didn't include it here 'cos you've shown how to do that now. So, this is just for the CR.. I know that when using CR you're meant to use LF with it. But this is just regarding your proof of concept for producing a CR. A question regarding shortening (why not?) that which is between the For and the Do.

Code: Select all

C:\blah>cmd /v:on<ENTER>

C:\blah>for /f %a in ('REM') DO (set cr=%a)<ENTER>

C:\blah><nul set /p=z!cr!!cr!!cr!>aa.txt<ENTER>

C:\blah>xxd -p aa.txt<ENTER>
7a0d0d0d

jeb
Expert
Posts: 1055
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: echo multiple lines without an ending of space new line

#17 Post by jeb » 12 Sep 2014 05:29

taripo wrote:But why "usebackq"?

To use the quotes for the this part "%~dpf0".
taripo wrote:And why(for the do nothing), the copy /z.

To create a single CR character.

Your code works ... But only if you start my code before :!: :wink:

Try your code from a new cmd window and it fails, because it simply does nothing :D

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: echo multiple lines without an ending of space new line

#18 Post by einstein1969 » 12 Sep 2014 06:20

jeb wrote:

Code: Select all

set ^"LF=^

" Do not remove the blank line
set "caret=^"
(<nul set /p ^^"=abc%%caret%%%%LF%%%%LF%%def^" ) | more


jeb


Hi Jeb,

is the double percent used because we are in a sub-thread?

With one LF don't work, why? why use double LF?

what is the role of %%caret%% ?

einstein1969

taripo
Posts: 228
Joined: 01 Aug 2011 13:48

Re: echo multiple lines without an ending of space new line

#19 Post by taripo » 12 Sep 2014 08:25

whoops.. ok

I added set CR= and del a.txt to the top.

I see it does work without usebackq though.

Code: Select all

C:\blahh>type a.bat
:: using cmd /v:on

set CR=
del a.txt

for /F %%a in ('copy /z %~dpf0 nul') DO ( set "cr=%%a" )

<nul set /p "=abc!CR!!CR!!CR!def" > a.txt
C:\blahh>xxd -p a.txt
6162630d0d0d646566

C:\blahh>



After some investigating, I can get a good idea of what is happening with the copy /z

It produces a line like thing that starts with 0d has some text that is irrelevant and ignored then has an 0d, then has some text. And when you do a for which sets a variable equal to that line, it just takes the 0d.

Who discovered that?! (and came up with the technique?) copy /z within a for, is so incredibly awesomely obscure!

I'm sure you're aware of the above and below already, but I'll include below how I found that..

So, below is for the benefit of others.

Code: Select all

C:\blahh>copy /z a.bat nul
100% copied         1 file(s) copied.

C:\blahh>copy /z a.bat nul>a

C:\blahh>xxd -p a
0d2020302520636f70696564200d3130302520636f706965642020202020
20202020312066696c6528732920636f706965642e0d0a

^^ That sparked the question.. 0d 20 20 30 25 20 63 is {CR}{Space}{Space}0% c" So where's the 100%


Code: Select all

C:\blahh>xxd a
0000000: 0d20 2030 2520 636f 7069 6564 200d 3130  .  0% copied .10
0000010: 3025 2063 6f70 6965 6420 2020 2020 2020  0% copied
0000020: 2020 3120 6669 6c65 2873 2920 636f 7069    1 file(s) copi
0000030: 6564 2e0d 0a                             ed...

C:\blahh>

after some more tests and a quick check in cygwin..and of course cmd (cmd doesn't do any funny business here thankfully)

Code: Select all

$ echo 0d6162630d6465660d0a|xxd -r -p
def

or cmd

C:\>echo 0d6162630d6465660d0a|xxd -r -p
def

C:\>



I see anything between the 0d..0d(including the 0ds) are ignored when displaying that hex as characters.

Clearly the cmd.exe FOR statement setting the variable equal to that output, sets the variable to CR

as this code shows

Code: Select all

C:\blah>copy /z a.bat nul>aa

C:\blah>xxd -p aa
0d2020302520636f70696564200d3130302520636f706965642020202020
20202020312066696c6528732920636f706965642e0d0a

C:\blah>for /f %f in (aa) do set var=%f

 :\blah>set var=

C:\blah><nul set/p=abc!var!abc
abc
C:\blah><nul set/p=abc!var!!var!!var!abc
abc
C:\blah><nul set/p=abc!var!!var!!var!abc>fff

ok so windows doesn't print a CR alone fine but the point is it's there.  and it got there from the funny line produced by copy /z with the 0d....0d.......0d0a  and setting a variable to it.

C:\blah>xxd -p fff
6162630d0d0d616263

C:\blah>


So that's all fine

The question remains though..

Who discovered that technique with the setting the variable and the copy /z and I suppose, the funny output 0d...0d..0d0a

It's one thing to see it done and reverse engineer it figuring out how it was done, but another thing to come up with it in the first place 'cos I guess you'd have to know copy /z has that funny line. I guess it was a bug somebody ran into that somebody realised could be utilized.

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: echo multiple lines without an ending of space new line

#20 Post by dbenham » 13 Sep 2014 08:51

There is a very simple solution. But to understand it, you must first understand the source of the problem. (jeb already knows this, as he explained it to me)

As explained by the link that jeb provided, each side of the pipe is executed via CMD /C "command(s) here". So each side of the pipe must be parsed and packaged into a form that will work within the /C argument to CMD.EXE. Unfortunately, the batch parser introduces an unwanted space before each &. This can be seen by ECHOing the %CMDCMDLINE% value as part of the command. CMDCMDLINE is a dynamic variable that shows the command line passed to CMD.EXE. I use FINDSTR "^" to simply parrot the output of the left side of the pipe.

Code: Select all

@echo off
(
  echo abc
  echo def
  echo %%cmdcmdline%%)|findstr "^"
) || findstr "^"
-- OUTPUT --

Code: Select all

abc
def
C:\Windows\system32\cmd.exe  /S /D /c" ( echo abc & echo def & echo %cmdcmdline% )"


Perhaps surprisingly, the following form with everything on the same line gives the exact same result.

Code: Select all

@echo off
(echo abc&echo def&echo %%cmdcmdline%%)|findstr "^"
--OUTPUT--

Code: Select all

abc
def
C:\Windows\system32\cmd.exe  /S /D /c" ( echo abc & echo def & echo %cmdcmdline% )"


The solution is to somehow avoid parsing of the commands before they are embedded in the /C argument. One way is to explicitly use our own CMD /C command :!: The parser that packages up the commands only sees a single command. The commands we want are safely embedded in a quoted string argument that does not get modified.

Code: Select all

cmd /c "echo abc&echo def" | xxd -p


But there is an even simpler solution. :)

Code: Select all

echo off
echo abc^&echo def| xxd -p
Note that it is critical that there not be any space before the pipe symbol.

But perhaps it is not obvious why this works ;)

When the parser sees only one command as part of a pipe, it does not introduce any extra space. The ambersand is escaped, so it is taken as a literal, and the parser sees only a single ECHO command. But the escape is consumed, so the /C argument becomes "echo abc&echo def", and all is good. :) This can be seen by employing the CMDCMDLINE trick again.

Code: Select all

@echo off
echo abc^&echo def^&echo %%cmdcmdline%%|findstr "^"
--OUTPUT--

Code: Select all

abc
def
C:\Windows\system32\cmd.exe  /S /D /c" echo abc&echo def&echo %cmdcmdline%"


Dave Benham

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: echo multiple lines without an ending of space new line

#21 Post by einstein1969 » 13 Sep 2014 10:12

Thanks Dave!

taripo
Posts: 228
Joined: 01 Aug 2011 13:48

Re: echo multiple lines without an ending of space new line

#22 Post by taripo » 13 Sep 2014 12:46

thanks, that is impressive.
You've 'rescued' echo, showing amongst other things, how it can be piped to xxd -p in a batch file or by using cmd /c

But is it possible to do the same for set (that'd be useful as set is so flexible in not putting an 0d0a at the end and set can be used with the !LF! and !CR! variables that jeb showed how to produce)

(this works, but no pipe)
using cmd /v:on

Code: Select all

C:\><nul set/p=abc!LF!abc>a.a

C:\blah>xxd -p a.a
6162630a616263


Trying to do that in one line skipping the file step using the pipe.. I can't see how to do it

Code: Select all

C:\blah>(<nul set/p=abc!LF!abc)|xxd -p<ENTER>
616263214c46216162632020

C:\>


Code: Select all

C:\>cmd /c <nul set/p=abc!LF!abc|xxd -p
616263

C:\>

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: echo multiple lines without an ending of space new line

#23 Post by dbenham » 13 Sep 2014 15:42

Each side of the pipe is executed in its own command session via CMD.EXE with command line semantics instead of batch semantics. Delayed expansion and command extensions are set to the default values as specified by the registry. Normally, that means delayed expansion will be disabled and extensions enabled, regardless what the state was in the parent batch.

The trick is to use the CMD.EXE /V:ON option to enable delayed expansion.

If delayed expansion is OFF within your batch file:

Code: Select all

@echo off
setlocal disableDelayedExpansion
set ^"LF=^

^"
cmd /v:on /c "<nul set/p=abc!LF!abc"|xxd -p

If delayed expansion is ON within your batch file, then the exclamation points must be escaped to delay expansion until within the new CMD session:

Code: Select all

@echo off
setlocal enableDelayedExpansion
set ^"LF=^

^"
cmd /v:on /c "<nul set/p=abc^!LF^!abc"|xxd -p


Dave Benham

taripo
Posts: 228
Joined: 01 Aug 2011 13:48

Re: echo multiple lines without an ending of space new line

#24 Post by taripo » 13 Sep 2014 15:55

Thanks

And I see outside of a batch file, this works - as you say, escaping the exclamation marks

No doubt you know this but i'll mention this for others

Regarding outside of a batch file

this works whether the cmd window you are in is cmd /v:on or not

Code: Select all

C:\>cmd /v:on /c "<nul set /p=abc^!LF^!abc"|xxd -p
6162630a616263

C:\>



If the cmd window you are in is not cmd /v:on then either way works - escaping the exclamation marks or not.

If the cmd window you are in is cmd /v:on then you have to escape the exclamation marks.

so escaping them works for whether the cmd window you are in is cmd /v:on or not.


Code: Select all

C:\blah>type lf.bat
set ^"LF=^

" asdf

C:\>

Post Reply