Functional carriage return literal within a batch script!

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
dbenham
Expert
Posts: 2283
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Functional carriage return literal within a batch script!

#1 Post by dbenham » 27 Mar 2015 13:02

Normally the only way to work with carriage return characters within batch is via FOR variables or delayed expansion. This is because the batch parser strips all carriage returns after the normal expansion phase, as documented in phase 1.5 in jeb's StackOverflow post - http://stackoverflow.com/a/4095133/1012053

But I was curious to see if I could demonstrate that carriage returns truly are stripped after normal expansion. So I did an experiment.

The goal is to define a variable that contains a carriage return, and then use normal expansion to expand the variable with find and replace operation that transforms the carriage return into something else. The find uses a carriage return literal within the script :!:

Embedding a carriage return within a text file can be tricky, so I first wrote the script with the carriage return encoded as \r.

test.bat

Code: Select all

@echo off
setlocal enableDelayedExpansion

:: Define CR to contain a single carriage return character (\r).
:: This also demonstrates that \r is stripped before FOR variable expansion
for /f %%a in ('copy /Z "%~dpf0" nul') do (
  set "CR=%%a"
  echo     FOR variable%%aOK
)
 
:: Normal expansion seems to fail because all carriage returns
:: are stripped from the line after expansion
echo     Normal expansion%CR%FAIL

:: Delayed expansion works just fine
echo     Delayed expansion!CR!OK

:: Demonstrate that Normal expansion actually works by replacing \r
:: with some other text
     echo Normal expansion %CR:\r=OK%

I then used my JREPL.BAT utility to transform \r into a carriage return literlal:

Code: Select all

jrepl "\\r" "\r" /x /f test.bat /o -

Here is what the final code looks like (note the appearance of the last line):

Code: Select all

D:\test>type test.bat
@echo off
setlocal enableDelayedExpansion

:: Define CR to contain a single carriage return character.
:: This also demonstrates that carriage return is stripped before
:: FOR variable expansion
for /f %%a in ('copy /Z "%~dpf0" nul') do (
  set "CR=%%a"
  echo     FOR variable%%aOK
)

:: Normal expansion seems to fail because all carriage returns
:: are stripped from the line after expansion
echo     Normal expansion%CR%FAIL

:: Delayed expansion works just fine
echo     Delayed expansion!CR!OK

:: Demonstrate that Normal expansion actually works by replacing the
:: carriage return with some other text
=OK% echo Normal expansion %CR:

And here is the output:

Code: Select all

D:\test>test
OK  FOR variable
    Normal expansionFAIL
OK  Delayed expansion
Normal expansion OK


I don't see how this is really of any use, but I thought it was interesting. It also provides evidence to back up the phase rules developed by jeb. It is pretty amazing how jeb was able to derive all those rules, and we are all the better for it.


Dave Benham

Liviu
Expert
Posts: 470
Joined: 13 Jan 2012 21:24

Re: Functional carriage return literal within a batch script

#2 Post by Liviu » 28 Mar 2015 22:01

dbenham wrote:But I was curious to see if I could demonstrate that carriage returns truly are stripped after normal expansion. So I did an experiment.

Nicely done. Also, obvious as it may be, here is a quick experiment to verify that CRs are stripped before special characters are processed.

Code: Select all

@echo off & setlocal disableDelayedExpansion

for /f %%a in ('copy /Z "%~dpf0" nul') do set "CR=%%a"

:: outputs '>nul |more line continuation'
echo ^%CR%>nul ^%CR%%CR%|more line ^%CR%%CR%%CR%
continuation

Liviu

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

Re: Functional carriage return literal within a batch script

#3 Post by jeb » 29 Mar 2015 07:14

And here is a sample that shows that the CR's are stripped after the percent expansion phase, they are not stripped while expanding variables.

Code: Select all

@echo off
setlocal
for /f %%a in ('copy /Z "%~dpf0" nul') do (
  set "CR=%%a"
)

setlocal EnableDelayedExpansion
set "L=."
for /L %%n in (1 1 13) do set "L=!L:~,4000!!L:~,4000!"
for %%C in ("!CR!") do set "L=!L:.=%%~C!"

(echo 111 !L!#)
(echo 222 %L%#)

prompt=#
echo on
REM # 3333 %CR% ^ *****
@echo off

echo 444 Fails
(echo 4444 %L%%L%#)


Output wrote:#11
222 #

#REM # 3333 ^ *****
444 Fails
Die eingegebene Zeile ist zu lang.


Expample "444" shows that the expanded result is too long, this result is only possible when the 16000 CR's are still in a buffer.
And Example 333 shows that the CR's are stripped before the special character phase starts.

RaceQuest
Posts: 6
Joined: 02 Feb 2016 22:19

carriage return as a single key response

#4 Post by RaceQuest » 25 Mar 2017 03:14

Is is possible to use pass a <CR> as data to a program used in a batch script. Example:

Code: Select all

choice.com /n "CR test: " /c:!CR!yn 
where CR contains a carriage return character?

I have a replacement utility to choice.com that is functionally equivalent so I would like pressing enter key (carriage return) to trigger a default action as well as having "y" or "n" single key responses. I tested thelinefeed character with:

Code: Select all

Choice /n "LF test: " /c:yn^


In the above example control+J (linefeed) is a single key response but does not look like control+m (carriage return) works. I know parsing does CR stripping so wondering if there is a technique to get into a command argument. I know "set /P" is not useful as I would have to press enter after "y" and "n" as well - where is the fun in that.

I found one solution to use a utilty getch.com http://alt.msdos.batch.narkive.com/On38IrqN/using-the-enter-key-in-the-choice-command

Code: Select all

<nul: set /p="CR test: "
getch.com
echo ascii code=%errorlevel%
rem test errorlevel for ascii return code here

There is no validation of keys entered like you do using choice.com but that is ok you can always code it in batch.

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

Re: Functional carriage return literal within a batch script!

#5 Post by dbenham » 25 Mar 2017 08:30

I'm not exactly clear on what your end goal is. But it is definitely possible to have a pure batch script read a string that contains a carriage return (<CR>).

Both SET /P and FOR /F treat a line of input nearly the same way. The line is terminated at the first occurrence of a linefeed (<LF>). If the character before the <LF> is a <CR>, then that <CR> is stripped. Any remaining <CR> characters are preserved.

If you expand a FOR variable that contains <CR>, then the <CR> character will be preserved.

But if you expand an environment value with percents (%VAR%), then all <CR> characters will be stripped. You must use delayed expansion (!VAR!) to successfully expand an environment variable containing <CR>.

It is even possible to include <CR> via user keyboard input when using SET /P. However, this technique seems to be dependent on some combination of Windows version, console version (new Win 10 version or legacy), active code page, and current console font.

On my Win 10 machine with legacy console using raster font, I can enter a <CR> as part of SET /P input by holding the <Alt> key and pressing <1> and <3> on the numeric keypad, and then releasing <Alt>. The character displayed on the console is a musical eighth note symbol, but the character in the variable is a <CR>. But if I change my font to Consolas, then the value in the variable is the eighth note character. I haven't tested to determine the effect of all possible permutations of code page, font, console version, and Windows version.

I have written a :getAnyKey pure batch function that allows input of any byte value from 0x00 to 0xFF via a key press. It even has the ability to specify which characters are accepted as input. The 0x00 value (entered by holding <Ctrl> and pressing <2>) is represented as an empty string - it cannot be included in a variable within batch. There are reports that 0x00 cannot be entered on some machines, but I believe all the other characters can be entered on any "normal" machine. However, there are exotic code pages that do not treat 0x00 - 0xFF as ASCII that might cause the routines to fail.

You can find the current version of :getAnyKey, with built in documentation, at viewtopic.php?f=3&t=7396&p=50819#p50819.

My original post where I introduce the concepts is at viewtopic.php?f=3&t=7396.


Dave Benham

MarioZac
Posts: 38
Joined: 16 May 2018 00:12

Re: Functional carriage return literal within a batch script!

#6 Post by MarioZac » 27 May 2018 20:29

I wonder if someone noticed that adding CR| allows to execute commands as if they were typed on separate lines in a batch file, while in fact they're typed on the same line. Its useful when adding commandline to Registry. I couldn't find mentioning it anywhere. Examples:

Code: Select all

:: Add command sequence to a Registry key via .REG file
@="cmd /c\"DisplaySwitch.exe /internal CR| reg add \"HKCU\\Software\\Classes\\DesktopBackground\\Shell\\Change Current Display\" /v \"Icon\" /d \"imageres.dll,-109\" /f\""

:: Add a similar oneliner in .BAT file. Replacing CR| with &, &&, or || causes DisplaySwitch.exe to fail, thus requiring use of caret return in the oneliner, or 2-line code
DisplaySwitch.exe /internal CR| reg add "HKCU\Software\Classes\DesktopBackground\Shell\Change Current Display" /v "Icon" /d "imageres.dll,-109" /f

Post Reply