Output text without linefeed, even with leading space or =

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
jeb
Expert
Posts: 1041
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Output text without linefeed, even with leading space or =

#1 Post by jeb » 15 Jan 2013 15:35

Hi,

normally the way of output text without a linefeed is to use the SET /P technic.

But there are some limitations for the text, as leading whitespaces are removed and leading equal signs causes a syntax error.
Discussed by Dave here SET /P prompt mechanics - New behavior: = makes syntax error

For simply displaying, there is a workaround to use a dummy character in front and remove it with a backspace character,
but this doesn't work for the creation of files, as the backspace will also be outputted to the file.

But there is another technic to output lines without a linefeed.
This technic uses the ability of COPY to copy only until the first SUB/EOF character.
To avoid that the SUB character itself is copied the switch /b is used for the destination file.

Code: Select all

@echo off
setlocal EnableDelayedExpansion
call :createSub
call :echoWithoutLinefeed "=hello"
call :echoWithoutLinefeed " world"
exit /b

:echoWithoutLinefeed
> txt.tmp (echo(%~1!sub!)
copy txt.tmp /a txt2.tmp /b > nul
type txt2.tmp
del txt.tmp txt2.tmp
exit /b

:createSub
copy nul sub.tmp /a > nul
for /F %%a in (sub.tmp) DO (
   set "sub=%%a"
)
del sub.tmp
exit /b


hope it helps
jeb

Sponge Belly
Posts: 216
Joined: 01 Oct 2012 13:32
Location: Ireland
Contact:

Re: Output text without linefeed, even with trailing space o

#2 Post by Sponge Belly » 15 Jan 2013 16:54

Hi Jeb and welcome back. :-)

Brilliant workaround for the "echo without linefeed" problem.
Wish I'd thought of it! ;-)

I had thought of using copy /a, but I didn't know how to get rid of the CtrlZ at eof. Never occurred to me to combine it with the /b switch for the destination file. :twisted:

One tiny suggestion: use findstr instead of type for outputting the text to make it more Unicode-friendly.

Code: Select all

findstr "^" txt2.tmp


Should do the trick.

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

Re: Output text without linefeed, even with leading space or

#3 Post by dbenham » 15 Jan 2013 17:16

Good creative solution jeb :D

If using TYPE, then the COPY is not needed. TYPE will stop at the Ctrl-Z.

Can you edit the subject and body of your post and substitute leading for trailing? - they are opposites, and we are talking about leading characters in this case. :wink:


Dave Benham

Sponge Belly
Posts: 216
Joined: 01 Oct 2012 13:32
Location: Ireland
Contact:

Re: Output text without linefeed, even with trailing space o

#4 Post by Sponge Belly » 15 Jan 2013 17:58

Oops! :oops:

I checked and find can output Unicode and findstr can't.

But find is no good either because it appends CR+LF to output.

Sorry about that... :idea:

Can type work with Unicode files from a cmd /u subshell?

I seem to recall some discussion about this before, but of course I can't remember when or where.

Wiser minds will enlighten me. ;-)

DigitalSnow
Posts: 20
Joined: 21 Dec 2012 13:36
Location: United States

Re: Output text without linefeed, even with trailing space o

#5 Post by DigitalSnow » 15 Jan 2013 20:35

This will be a very useful technique. Thank you jeb. Im gonna keep this in my notes. :D

foxidrive
Expert
Posts: 6031
Joined: 10 Feb 2012 02:20

Re: Output text without linefeed, even with trailing space o

#6 Post by foxidrive » 15 Jan 2013 21:56

In this case it works then same 'on screen' if copy has no switches.

Code: Select all

copy txt.tmp txt2.tmp  > nul


Copy defaults to binary mode for most filetypes now, whereas it had to be specified in the MSDOS and Win9x days if you wanted a binary copy.

carlos
Expert
Posts: 503
Joined: 20 Aug 2010 13:57
Location: Chile
Contact:

Re: Output text without linefeed, even with trailing space o

#7 Post by carlos » 15 Jan 2013 22:34

Thanks jeb for teach this technique.
Last edited by carlos on 16 Jan 2013 04:48, edited 1 time in total.

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

Re: Output text without linefeed, even with leading space or

#8 Post by jeb » 16 Jan 2013 00:41

dbenham wrote:Can you edit the subject and body of your post and substitute leading for trailing? - they are opposites, and we are talking about leading characters in this case. :wink:

Thanks Dave, that's my non native speaker problem: I simply translated "Anfang" to trailing.

dbenham wrote:If using TYPE, then the COPY is not needed. TYPE will stop at the Ctrl-Z.

It only seems so :), but my tests show that redirecting the output of TYPE to a file,
will copy the complete context is in the resulting file. (WIN7)

jeb

carlos
Expert
Posts: 503
Joined: 20 Aug 2010 13:57
Location: Chile
Contact:

Re: Output text without linefeed, even with leading space or

#9 Post by carlos » 16 Jan 2013 04:57

I redirect the output of the jeb script and it is clean, the file have not the 26 ascii character. I posted a simplified version avoid a copy, but i removed it because without the copy jeb trick and only using the type command that stop when it found the SUB character the binary ouput will have the 26 ascii character.

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

Re: Output text without linefeed, even with leading space or

#10 Post by dbenham » 17 Oct 2013 16:21

jeb's code fails if the string contains the ! character. That can be fixed easily enough.

But I found another method:

Code: Select all

@echo off
call :echoWithoutLinefeed "=hello"
call :echoWithoutLinefeed " world!"
exit /b
 
:echoWithoutLinefeed
setlocal disableDelayedExpansion
set "ln=%~1"
for /f %%A in ('copy /Z "%~dpf0" nul') do set EOL=%%A^


setlocal enableDelayedExpansion
<nul set /p "=x!EOL!!ln!" >ln.tmp
findstr /v $ ln.tmp
del ln.tmp
exit /b

The temp file is only needed because FINDSTR appends <CR><LF> to piped input if the last line does not end with <LF>. If FINDSTR didn't append <CR><LF>, then I would have replaced the temp file with a pipe.

UPDATE
There is one additional limit for XP: FINDSTR on XP will display most control characters and some extended ASCII characters as dots. See What are the undocumented features and limitations of the Windows FINDSTR command? for more info.


Dave Benham

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

Re: Output text without linefeed, even with leading space or

#11 Post by dbenham » 19 Oct 2013 10:37

I like the SET /P + FINDSTR method, but it has problems on XP in that most control characters and many extended ASCII characters display as dots on XP.

jeb's COPY method works with any set of characters (after modifying it to used delayed expansion for the string). The only character it cannot display is the SUB character (<CTRL-Z>, 0X1a, decimal 26). But that is easily solved by adding a SET /P to print the balance of the string from the first occurrence of SUB to the end.

The :writeVar routine below should be able to print absolutely any valid batch string value stored in a variable (within typical 8k line length limits of course). The :write routine is an entry point that allows printing of most string literals. The routines should work on any Windows machine from XP onward.

Code: Select all

@echo off
setlocal disableDelayedExpansion
call :writeInitialize
call :write "=hello"
call :write " world!%$write.sub%OK!"
echo(
setlocal enableDelayedExpansion
set lf=^


set "str= hello!lf!world^!!!$write.sub!hello!lf!world"
echo(
echo str=!str!
echo(
call :write "str="
call :writeVar str
echo(
exit /b

:write  Str
::
:: Write the literal string Str to stdout without a terminating
:: carriage return or line feed. Enclosing quotes are stripped.
::
:: This routine works by calling :writeVar
::
setlocal disableDelayedExpansion
set "str=%~1"
call :writeVar str
exit /b


:writeVar  StrVar
::
:: Writes the value of variable StrVar to stdout without a terminating
:: carriage return or line feed.
::
:: The routine relies on variables defined by :writeInitialize. If the
:: variables are not yet defined, then it calls :writeInitialize to
:: temporarily define them. Performance can be improved by explicitly
:: calling :writeInitialize once before the first call to :writeVar
::
if not defined %~1 exit /b
setlocal enableDelayedExpansion
if not defined $write.sub call :writeInitialize
>"%$write.temp%_1.txt" (echo !str!!$write.sub!)
copy "%$write.temp%_1.txt" /a "%$write.temp%_2.txt" /b >nul
type "%$write.temp%_2.txt"
del "%$write.temp%_1.txt" "%$write.temp%_2.txt"
set "str2=!str:*%$write.sub%=%$write.sub%!"
if "!str2!" neq "!str!" <nul set /p "=!str2!"
exit /b


:writeInitialize
::
:: Defines 2 variables needed by the :write and :writeVar routines
::
::   $write.temp - specifies a base path for temporary files
::
::   $write.sub  - contains the SUB character, also known as <CTRL-Z> or 0x1A
::
set "$write.temp=%temp%\writeTemp%random%"
copy nul "%$write.temp%.txt" /a >nul
for /f "usebackq" %%A in ("%$write.temp%.txt") do set "$write.sub=%%A"
del "%$write.temp%.txt"
exit /b


The only problem with the above is that it wastes time mucking with temp files, even when dealing with strings that don't cause problems with SET /P. Below is an optimized variation that only uses the COPY technique if the leading character causes problems with SET /p. Note that the problem characters vary between Windows versions. I took the approach that if the leading character can cause a problem for SET /P on any Windows version, then I resort to using jeb's COPY method.

Note that the :writeInitialize routine has a string literal that contains characters that do not post well to the bulletin board. A remark explains what the proper character sequence should be.

I've tested on two different machines. This optimized version prints non-problematic strings 50 to 100% faster than the prior simpler method. The extra code causes the problem strings to be insignificantly slower than before (at worst, 10% slower). On balance, I think this is a better method.

Code: Select all

@echo off
setlocal disableDelayedExpansion
call :writeInitialize
call :write "=hello"
call :write " world!%$write.sub%OK!"
echo(
setlocal enableDelayedExpansion
set lf=^


set "str= hello!lf!world^!!!$write.sub!hello!lf!world"
echo(
echo str=!str!
echo(
call :write "str="
call :writeVar str
echo(
exit /b

:write  Str
::
:: Write the literal string Str to stdout without a terminating
:: carriage return or line feed. Enclosing quotes are stripped.
::
:: This routine works by calling :writeVar
::
setlocal disableDelayedExpansion
set "str=%~1"
call :writeVar str
exit /b


:writeVar  StrVar
::
:: Writes the value of variable StrVar to stdout without a terminating
:: carriage return or line feed.
::
:: The routine relies on variables defined by :writeInitialize. If the
:: variables are not yet defined, then it calls :writeInitialize to
:: temporarily define them. Performance can be improved by explicitly
:: calling :writeInitialize once before the first call to :writeVar
::
if not defined %~1 exit /b
setlocal enableDelayedExpansion
if not defined $write.sub call :writeInitialize
set $write.special=1
if "!%~1:~0,1!" equ "^!" set "$write.special="
for /f delims^=^ eol^= %%A in ("!%~1:~0,1!") do (
  if "%%A" neq "=" if "!$write.problemChars:%%A=!" equ "!$write.problemChars!" set "$write.special="
)
if not defined $write.special (
  <nul set /p "=!%~1!"
  exit /b
)
>"%$write.temp%_1.txt" (echo !str!!$write.sub!)
copy "%$write.temp%_1.txt" /a "%$write.temp%_2.txt" /b >nul
type "%$write.temp%_2.txt"
del "%$write.temp%_1.txt" "%$write.temp%_2.txt"
set "str2=!str:*%$write.sub%=%$write.sub%!"
if "!str2!" neq "!str!" <nul set /p "=!str2!"
exit /b


:writeInitialize
::
:: Defines 3 variables needed by the :write and :writeVar routines
::
::   $write.temp - specifies a base path for temporary files
::
::   $write.sub  - contains the SUB character, also known as <CTRL-Z> or 0x1A
::
::   $write.problemChars - list of characters that cause problems for SET /P
::      <carriageReturn> <formFeed> <space> <tab> <0xFF> <equal> <quote>
::      Note that <lineFeed> and <equal> also causes problems, but are handled elsewhere
::
set "$write.temp=%temp%\writeTemp%random%"
copy nul "%$write.temp%.txt" /a >nul
for /f "usebackq" %%A in ("%$write.temp%.txt") do set "$write.sub=%%A"
del "%$write.temp%.txt"
for /f %%A in ('copy /z "%~f0" nul') do for /f %%B in ('cls') do (
  set "$write.problemChars=%%A%%B     ""
  REM the characters after %%B above should be <space> <tab> <0xFF>
)
exit /b


Dave Benham

zoeyku
Posts: 3
Joined: 20 Oct 2013 19:59
Location: usa
Contact:

Re: Output text without linefeed, even with leading space or

#12 Post by zoeyku » 20 Oct 2013 20:02

Brilliant workaround for the "echo without linefeed" problem.

carlos
Expert
Posts: 503
Joined: 20 Aug 2010 13:57
Location: Chile
Contact:

Re: Output text without linefeed, even with leading space or

#13 Post by carlos » 30 Jan 2014 09:33

I found a Little problem with the jeb code with string beginning with character ! & >
This is my fix for it:

Code: Select all

@echo off
setlocal DisableDelayedExpansion
call :echoWithoutLinefeed "=hello"
call :echoWithoutLinefeed " world"
call :echoWithoutLinefeed "!>"
pause
exit /b

:echoWithoutLinefeed
setlocal DisableDelayedExpansion
> txt.tmp cmd /a /v:on /c set /p "=_%~1" <nul
copy /y txt.tmp /b + nul /a txt.tmp >nul
type txt.tmp | (pause>nul&findstr "^">txt2.tmp)
copy /y txt2.tmp /a txt2.tmp /b >nul
type txt2.tmp
del txt.tmp txt2.tmp
endlocal
exit /b


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

Re: Output text without linefeed, even with leading space or

#14 Post by dbenham » 30 Jan 2014 09:54

@Carlos - yes, that is true.

The code from two posts before yours also addresses the issue, and is optimized to print more quickly if there are no problem characters.


Dave Benham

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

Re: Output text without linefeed, even with leading space or =

#15 Post by jeb » 11 Jul 2021 03:26

I started this thread, now I will finish it with a really simple method, shown by sst at How do I add a space on this line?

Code: Select all

@echo off

setlocal
set "prompt=  Hello"
cmd /d /k < nul
echo  World

endlocal
It's so horrifying simple, I can't understand why nobody found that before.

jeb

Post Reply