Read key presses via REPLACE - New functions :getKey, :getAnyKey, :getMaskedInput

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

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

Read key presses via REPLACE - New functions :getKey, :getAnyKey, :getMaskedInput

#1 Post by dbenham » 05 Sep 2016 12:50

Version 2, with new behavior and bug fixes, is available at http://www.dostips.com/forum/viewtopic.php?f=3&t=7396&p=50819#p50819

In the past I have used the XCOPY method to read key presses. I first learned of the XCOPY method while developing SNAKE.BAT. However, it does not support all possible characters. For example, I could not figure out a way to capture <Ctrl-Z> (0x1A). Also, it is difficult, though possible, to distinguish LineFeed (0x0A) from Carriage Return (0x0D). And ! is also a bit difficult to get.

I only recently read Carlos' thread where he uses REPLACE to read a key: Password Input (new method) - most excellent :!:
I find REPLACE much easier to use, and am able to capture all possible single byte characters, including NULL (0x00).
Using this basic technique, I have developed three robust functions that share the following key features:
  • Each function has an option to specify which characters are accepted
  • Each function works regardless whether delayed expansion is enabled or disabled
  • Full documentation is embedded within comments at the top of each function

:getAnyKey - Capture any character, including NULL. Some characters can only be entered using <Alt> with the numeric keypad. Null is entered using [Ctrl-2] and is returned as an empty string (undefined variable).

:getKey - Same as :getAnyKey except Null, LineFeed, CarriageReturn, (and sometimes Ctrl-Z) are all reported as an empty string (undefined variable)

:getMaskedInput - Similar to the carlos :PasswordInput function, with the added ability to specify which characters are accepted, and delayed expansion need not be enabled. This function is dependent on the :getKey function.

Code: Select all

::getMaskedInput  StrVar  [ValidVar]
::
:: Get a user input string, echoing * for each key pressed. [Backspace] erases
:: the previous character. [Enter] completes the string. Additionally, any
:: method that generates Null (0x00), LineFeed (0x0A) or Carriage Return (0x0D)
:: will also terminate the string. On Windows 10 a [Ctrl-Z] (0x1A) will also
:: terminate the string. The final string may contain any characters between
:: 0x01 and 0xFF except Backspace, LineFeed, and Carriage Return. On Windows 10
:: Ctrl-Z is also excluded.
::
:: The optional ValidVar variable defines the characters that will be accepted.
:: If not specified or not defined, then all characters are accepted.
:: If specified and defined, then only characters within ValidVar are accepted.
::
:: Any value (except null) may be entered by holding the [Alt] key and pressing
:: the appropriate decimal code on the numeric keypad. For example, holding
:: [Alt] and pressing numeric keypad [1] and [0], and then releasing [Alt] will
:: result in a LineFeed.
::
:: The only way to enter a Null is by holding [Ctrl] and pressing the normal [2]
::
:: An alternate way to enter control characters 0x01 through 0x1A is by holding
:: the [Ctrl] key and pressing any one of the letter keys [A] through [Z].
:: However, [Ctrl-A], [Ctrl-F], [Ctrl-M], and [Ctrl-V] will be blocked on Win 10
:: if the console has Ctrl key shortcuts enabled.
::
:: This function works properly regardless whether delayed expansion
:: is enabled or disabled.
::
:: :getMaskedInput version 1.2 was written by Dave Benham, and originally
:: posted at http://www.dostips.com/forum/viewtopic.php?f=3&t=7396
::
:: This work was inspired by posts from carlos and others at
:: http://www.dostips.com/forum/viewtopic.php?f=3&t=6382
::
:getMaskedInput
setlocal
set "notDelayed=!"
setlocal enableDelayedExpansion
set "mask=!%2!"
for /f %%A in ('"Prompt;$H&for %%A in (1) do rem"') do set "BS=%%A"
if defined mask set "mask=1!BS!!mask!"
set "str="
:getMaskedInputLoop
call :getKey key mask
if defined key (
  if not defined notDelayed (
    if "!key!" equ "^!" set "key=^^^!"
    if "!key!" equ "^" set "key=^^"
  )
  if "!key!" equ "!BS!" (
    if defined str (
      set "str=!str:~0,-1!"
      <nul set /p "=%BS% %BS%"
    )
  ) else (
    set "str=!str!!key!"
    <nul set /p "=*"
  )
  goto :getMaskedInputLoop
)
for /f "delims=" %%A in (""!str!"") do (
  endlocal
  endlocal
  set "%1=%%~A" !
  echo(
  exit /b
)


::getKey  KeyVar  [ValidVar]
::
:: Read a keypress representing a character between 0x00 and 0xFF and store the
:: value in variable KeyVar. Null (0x00), LineFeed (0x0A), and Carriage Return
:: (0x0D) will result in an undefined KeyVar. On Windows 10, Ctrl-Z (0x1A) will
:: also result in an undefined KeyVar. The simplest way to get an undefined
:: KeyVar is to press the [Enter] key.
::
:: The optional ValidVar variable defines the values that will be accepted.
:: If not given or not defined, then all characters are accepted. If given
:: and defined, then only characters within ValidVar are accepted. The first
:: character within ValidVar should either be 0, meaning ignore undefined KeyVar,
:: or 1, meaning accept undefined KeyVar. The remaining characters represent
:: themselves. For example, a ValidVar value of 0YNyn will only accept upper
:: or lower case Y or N. A value of 1YNyn will additionally accept [Enter] etc.
::
:: Any value (except null) may be entered by holding the [Alt] key and pressing
:: the appropriate decimal code on the numeric keypad. For example, holding
:: [Alt] and pressing numeric keypad [1] and [0], and then releasing [Alt] will
:: result in a LineFeed.
::
:: The only way to enter a Null is by holding [Ctrl] and pressing the normal [2]
::
:: An alternate way to enter control characters 0x01 through 0x1A is by holding
:: the [Ctrl] key and pressing any one of the letter keys [A] through [Z].
:: However, [Ctrl-A], [Ctrl-F], [Ctrl-M], and [Ctrl-V] will be blocked on Win 10
:: if the console has Ctrl key shortcuts enabled.
::
:: This function works properly regardless whether delayed expansion is enabled
:: or disabled.
::
:: :getKey version 1.3 was written by Dave Benham, and originally posted at
:: http://www.dostips.com/forum/viewtopic.php?f=3&t=7396
::
:: This work was inspired by posts from carlos and others at
:: http://www.dostips.com/forum/viewtopic.php?f=3&t=6382
::
:getkey
set "%1="
setlocal disableDelayedExpansion
for /f skip^=1^ delims^=^ eol^= %%A in (
  'replace.exe ? . /u /w'
) do for /f delims^=^ eol^= %%B in ("%%A") do (
  endlocal
  if "%%B" equ "" (set "%1=^!") else set "%1=%%B"
)
setlocal enableDelayedExpansion
if "!%2!" neq "" (
  if not defined %1 if "!%2:~0,1!" equ "0" (endlocal&endlocal&goto :getKey) else exit /b
  set "getKey.key=!%1!"
  set "mask=!%2:~1!"
  if not defined mask endlocal&endlocal&goto :getKey
  if "!getKey.key!" equ "=" (
    set "test=a!mask!"
    for /f "delims=" %%A in ("!test!") do if /i "!test:%%A=%%A!" equ "!test!" endlocal&endlocal&goto :getKey
  )
  for /f delims^=^ eol^= %%A in ("!getKey.key!") do if "!mask:*%%A=!" equ "!mask!" endlocal&endlocal&goto :getKey
)
exit /b


::getAnyKey  KeyVar  [ValidVar]
::
:: Read a keypress representing any character between 0x00 and 0xFF and store
:: the character in variable KeyVar. A Null value of 0x00 is represented as an
:: undefined KeyVar.
::
:: The optional ValidVar variable holds the characters that will be accepted.
:: If not specified or not defined, then all values are accepted. If specified
:: and defined, then only characters within ValidVar are accepted. The first
:: three characters indicate whether Null (0x00), LineFeed (0x0A), and Carriage
:: Return (0x0D) are accepted, respectively. A value of 1 indicates acceptance,
:: and 0 indicates rejection. The remaining characters represent themselves.
:: For example, a ValidVar value of 000YNyn will only accept upper or lower case
:: Y or N. A value of 011YNyn will additionally accept LineFeed and Carriage
:: Return. A value of 111YNyn adds Null to the list.
::
:: Note that [Enter] is interpreted as a Carriage Return.
::
:: Any value (except null) may be entered by holding the [Alt] key and pressing
:: the appropriate decimal code on the numeric keypad. For example, holding
:: [Alt] and pressing numeric keypad [1] and [0], and then releasing [Alt] will
:: result in a LineFeed.
::
:: The only way to enter a Null is by holding [Ctrl] and pressing the normal [2]
::
:: An alternate way to enter control characters 0x01 through 0x1A is by holding
:: the [Ctrl] key and pressing any one of the letter keys [A] through [Z].
:: However, [Ctrl-A], [Ctrl-F], [Ctrl-M], and [Ctrl-V] will be blocked on Win 10
:: if the console has Ctrl key shortcuts enabled.
::
:: This function works properly regardless whether delayed expansion is enabled
:: or disabled.
::
:: :getAnyKey version 1.3 was written by Dave Benham, and originally posted at
:: http://www.dostips.com/forum/viewtopic.php?f=3&t=7396
::
:: This work was inspired by posts from carlos and others at
:: http://www.dostips.com/forum/viewtopic.php?f=3&t=6382
::
:getAnyKey
setlocal
set "notDelayed=!"
setlocal disableDelayedExpansion
for /f "skip=1 delims=" %%A in (
  'replace.exe ? . /u /w ^| findstr /n "^" ^| find /n /v ""'
) do set "str=%%A"
setlocal enableDelayedExpansion
if "!str!" equ "[2]2:" (         %= Ctrl-Z on Win 10 =%
  copy nul "%temp%\ctrlZ.tmp" /a >nul
  for /f "usebackq" %%A in ("%temp%\ctrlZ.tmp") do set "key=%%A"
  del "%temp%\ctrlZ.tmp"
) else if "!str!" equ "[3]3:" (  %= LineFeed =%
  set "key="
) else if "!str!" equ "[3]" (    %= Null = %
  set "key=NULL"
) else (                         %= All others =%
  set "key=!str:~-1!"
  if not defined notDelayed if "!key!" equ "^!" set "key=^^^!"
)
for /f "delims=" %%A in (""!key!"") do (
  endlocal&endlocal&endlocal
  set "%1=%%~A"
)
if not defined %1 (set %1=^
%= Do not remove or alter this line =%
)
setlocal enableDelayedExpansion
if !%1! equ NULL (
  endlocal
  set "%1="
  setlocal enableDelayedExpansion
)
if "!%2!" equ "" exit /b
set "getAnyKey.key=!%1!"
set "mask=!%2!"
(set LF=^
%= Do not remove or alter this line =%
)
if not defined getAnyKey.key if "!mask:~0,1!" equ "0" (endlocal&goto :getAnyKey) else exit /b
if !getAnyKey.key! equ !LF!  if "!mask:~1,1!" equ "0" (endlocal&goto :getAnyKey) else exit /b
for /f %%A in ('copy /z "%~dpf0" nul') do if !getAnyKey.key! equ %%A if "!mask:~2,1!" equ "0" (endlocal&goto :getAnyKey) else exit /b
set "mask=!mask:~3!"
if not defined mask endlocal&goto :getAnyKey
if "!getAnyKey.key!" equ "=" (
  set "test=a!mask!"
  for /f "delims=" %%A in ("!test!") do if /i "!test:%%A=%%A!" equ "!test!" endlocal&goto :getAnyKey
)
for /f delims^=^ eol^= %%A in ("!getAnyKey.key!") do if "!mask:*%%A=!" equ "!mask!" endlocal&goto :getAnyKey
exit /b


EDITS
2016-09-06: Bug fix for :getKey and :getAnyKey when ValidVar option not specified. Also documentation modified slightly for all three routines.
2016-09-07: Added ability to capture Null in :getAnyKey, and improved documentation of all three routines.


Dave Benham
Last edited by dbenham on 07 Sep 2016 16:40, edited 6 times in total.

aGerman
Expert
Posts: 4678
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Read key presses via REPLACE - New functions :getKey, :getAnyKey, :getMaskedInput

#2 Post by aGerman » 05 Sep 2016 13:45

Dave

getMaskedInput works for me, getKey and getAnyKey do not (the input isn't available in the passed variable, it remains undefined). I'll try to find out what happened.

Steffen

Code: Select all

@echo off &setlocal
call :getAnyKey out
setlocal EnableDelayedExpansion
echo !out!
pause
goto :eof

:: your code appended here ...

aGerman
Expert
Posts: 4678
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Read key presses via REPLACE - New functions :getKey, :getAnyKey, :getMaskedInput

#3 Post by aGerman » 05 Sep 2016 13:59

Still don't see it with echo switched on.
I pressed X and Enter.

Code: Select all

C:\test>call :getAnyKey out

C:\test>setlocal

C:\test>set "notDelayed=!"

C:\test>setlocal disableDelayedExpansion

C:\test>for /F "skip=1 delims=" %A in ('replace.exe ? . /u /w|findstr /n "^"') do set "str=%A"

C:\test>set "str=2:X"

C:\test>setlocal enableDelayedExpansion

C:\test>if "!str!" EQU "2:" (
copy nul "C:\Users\steffen\AppData\Local\Temp\ctrlZ.tmp" /a  1>nul
 for /F "usebackq" %A in ("C:\Users\steffen\AppData\Local\Temp\ctrlZ.tmp") do set "key=%A"
 del "C:\Users\steffen\AppData\Local\Temp\ctrlZ.tmp"
)  else if "!str!" EQU "3:" (set "key=" )  else (
set "key=!str:~-1!"
 if not defined notDelayed if "!key!" EQU "^!" set "key=^^^!"
)

C:\test>for /F "delims=" %A in (""!key!"") do (endlocal & endlocal & endlocal
 set "out=%~A" )

C:\test>(endlocal & endlocal & endlocal
 set "out=X" )

C:\test>if not defined out set "out=
" The empty line above is critical - DO NOT REMOVE

C:\test>if "!!" EQU "" exit /b

C:\test>setlocal enableDelayedExpansion

C:\test>set "getAnyKey.key=!out!"

C:\test>set "mask=!!"

C:\test>set "LF=
" The empty line above is critical - DO NOT REMOVE

C:\test>if !getAnyKey.key! EQU !LF! if "!mask:~0,1!" EQU "0" (endlocal & goto :getAnyKey )  else exit /b

C:\test>for /F %A in ('copy /z "C:\test\x.bat" nul') do if !getAnyKey.key! EQU %A if "!mask:~1,1!" EQU "0" (endlocal & goto :getAnyKey )  else exit /b

 if "!mask:~1,1!" EQU "0" (endlocal & goto :getAnyKey )  else exit /b

C:\test>set "mask=!mask:~2!"

C:\test>if not defined mask endlocal & goto :getAnyKey

C:\test>if "!getAnyKey.key!" EQU "=" (
set "test=a!mask!"
 for /F "delims=" %A in ("!test!") do if /I "!test:%A=%A!" EQU "!test!" endlocal & goto :getAnyKey
)

C:\test>for /F delims= eol= %A in ("!getAnyKey.key!") do if "!mask:*%A=!" EQU "!mask!" endlocal & goto :getAnyKey

C:\test>if "!mask:*X=!" EQU "!mask!" endlocal & goto :getAnyKey

C:\test>setlocal

C:\test>set "notDelayed=!"

C:\test>setlocal disableDelayedExpansion

C:\test>for /F "skip=1 delims=" %A in ('replace.exe ? . /u /w|findstr /n "^"') do set "str=%A"

" \test>set "str=2:

C:\test>setlocal enableDelayedExpansion

C:\test>if "!str!" EQU "2:" (
copy nul "C:\Users\steffen\AppData\Local\Temp\ctrlZ.tmp" /a  1>nul
 for /F "usebackq" %A in ("C:\Users\steffen\AppData\Local\Temp\ctrlZ.tmp") do set "key=%A"
 del "C:\Users\steffen\AppData\Local\Temp\ctrlZ.tmp"
)  else if "!str!" EQU "3:" (set "key=" )  else (
set "key=!str:~-1!"
 if not defined notDelayed if "!key!" EQU "^!" set "key=^^^!"
)

C:\test>for /F "delims=" %A in (""!key!"") do (endlocal & endlocal & endlocal
 set "out=%~A" )

C:\test>(endlocal & endlocal & endlocal
" )  "out=

C:\test>if not defined out set "out=
" The empty line above is critical - DO NOT REMOVE

C:\test>if "!!" EQU "" exit /b

C:\test>setlocal enableDelayedExpansion

C:\test>set "getAnyKey.key=!out!"

C:\test>set "mask=!!"

C:\test>set "LF=
" The empty line above is critical - DO NOT REMOVE

C:\test>if !getAnyKey.key! EQU !LF! if "!mask:~0,1!" EQU "0" (endlocal & goto :getAnyKey )  else exit /b

C:\test>for /F %A in ('copy /z "C:\test\x.bat" nul') do if !getAnyKey.key! EQU %A if "!mask:~1,1!" EQU "0" (endlocal & goto :getAnyKey )  else exit /b

 if "!mask:~1,1!" EQU "0" (endlocal & goto :getAnyKey )  else exit /b

C:\test>setlocal EnableDelayedExpansion

C:\test>echo !out!
ECHO ist eingeschaltet (ON).

C:\test>pause
Drücken Sie eine beliebige Taste . . .

The Batch file is "C:\test\x.bat" (no spaces, no special characters).

Steffen

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

Re: Read key presses via REPLACE - New functions :getKey, :getAnyKey, :getMaskedInput

#4 Post by dbenham » 05 Sep 2016 14:02

Thanks aGerman. It was a silly bug that appeared when the ValidVar option is not specified. My original tests without a value worked. But then I found a bug and had to modify the code a bit, and forgot to retest without a value.

The bug is fixed by moving setlocal enableDelayedExpansion to before the test for the presence of the ValidVar option.
I've fixed the two routines and modified the version to 1.1 in my first post.


Dave Benham

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

Re: Read key presses via REPLACE - New functions :getKey, :getAnyKey, :getMaskedInput

#5 Post by dbenham » 05 Sep 2016 14:10

I made a slight change to the documentation of all three routines, and updated the versions in my first post.


Dave Benham

aGerman
Expert
Posts: 4678
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Read key presses via REPLACE - New functions :getKey, :getAnyKey, :getMaskedInput

#6 Post by aGerman » 05 Sep 2016 14:12

Now it returns immediatelly regardless if the second parameter was left out, if it was 00, or 11 :?

EDIT Oh I guess that's the expected behavior, isn't it?

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

Re: Read key presses via REPLACE - New functions :getKey, :getAnyKey, :getMaskedInput

#7 Post by dbenham » 05 Sep 2016 14:29

:? I'm not sure I understand what you are experiencing.

The 2nd argument is optional, and is passed by reference. If it is not specified, then any value is accepted.

Here is an example usage that accepts only alpha-numeric input, as well as LineFeed, and Carriage Return.

Code: Select all

@echo off &setlocal
set "valid=11abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
call :getAnyKey out valid
setlocal EnableDelayedExpansion
echo    key={!out!}
goto :eof

:getAnyKey ...
etc.

Sample output:

Code: Select all

C:\test>test
   key={a}        REM - I pressed the lower case <a> key

C:\test>test
}  key={          REM - I pressed the <Enter> key

C:\test>test
   key={          REM - I held the <Alt> key and pressed <1> and <0> on the numeric keypad
}


Dave Benham

aGerman
Expert
Posts: 4678
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Read key presses via REPLACE - New functions :getKey, :getAnyKey, :getMaskedInput

#8 Post by aGerman » 05 Sep 2016 14:41

Sorry Dave. That was my bad. I was expecting that the functions behave similar to getMaskedInput where you can enter more than one key and the function doesn't return before you hit Enter. Of course you would have named such a function getString rather than getKey :oops:

Steffen

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

Re: Read key presses via REPLACE - New functions :getKey, :getAnyKey, :getMaskedInput

#9 Post by dbenham » 05 Sep 2016 14:58

No problem :)

:getString is not a bad idea, except <Tab> causes a display problem, especially when <Backspace> is used.

:getMaskedInput doesn't have the display problem with <Tab> because all characters are displayed the same.

EDIT - Other characters that can cause difficulties for :getString display are the bell character <Ctrl-G> (0x07), and on Windows 10, the Escape character <Alt-27> (0x1B).


Dave Benham

aGerman
Expert
Posts: 4678
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Read key presses via REPLACE - New functions :getKey, :getAnyKey, :getMaskedInput

#10 Post by aGerman » 05 Sep 2016 16:29

Anyway these functions are brilliant Dave :)

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

Re: Read key presses via REPLACE - New functions :getKey, :getAnyKey, :getMaskedInput

#11 Post by jeb » 06 Sep 2016 10:07

Cool solution :!: 8)

I never recognized the REPLACE command.

But I would change this bit of code to avoid the second FOR loop.
dbenham wrote:) do for /f delims^=^ eol^= %%B in ("%%A") do (
endlocal
if "%%B" equ "" (set "%1=^!") else set "%1=%%B"
)

To:

Code: Select all

) do (
    endlocal
    IF "^!" == "^!^" (
        set "%1=^%%A" !
    ) ELSE (
        set "%1=%%A"
    )


jeb

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

Re: Read key presses via REPLACE - New functions :getKey, :getAnyKey, :getMaskedInput

#12 Post by jeb » 06 Sep 2016 10:28

And a second suggestion

Instead of using piping the output through FINDSTR and using an IF-SWITCH block, it could be made much simpler by using this.

Code: Select all

:getAnyKey
setlocal DisableDelayedExpansion
(set str=^
%=Do not remove this=%
)
for /f skip^=1^ delims^=^ eol^= %%A in ('replace.exe ? . /u /w') do set "str=%%A"
setlocal EnableDelayedExpansion
echo(get:"!str!"


I tested it with
!^<LF><CR><CTRL-Z>

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

Re: Read key presses via REPLACE - New functions :getKey, :getAnyKey, :getMaskedInput

#13 Post by dbenham » 06 Sep 2016 17:21

@jeb - The reason for much of the complexity is because I developed the routines on my Win 10 machine at home, and I just now learned that REPLACE handles <Ctrl-Z> differently on Win 10 than it does on earlier versions.

The first line of REPLACE output is always the same ("Press any key to continue . . .<CR><LF>" on my English machine)
It is the remaining line(s) that differ depending on what character is pressed.

I will use [Alt-10] to mean "hold the [Alt] key and press numeric keypad [1] followed by [0] and then release [Alt]".

Here is the result I get for the 2nd (and possibly 3rd) line of REPLACE output for various key presses on Win 10 and Win 7:

Code: Select all

Keypress       Win 7                  Win 10
--------       ----------------       ----------------

[Enter]        <CR><CR><LF>           <CR><CR><LF>

[Alt-10]       <LF>                   <LF>
               <CR><LF>               <CR><LF>

[Alt-26]       <Ctrl-Z><CR><LF>       <CR><LF>

Note how <Ctrl-Z> is not captured on Win 10. During development I was not aware that Win 7 does capture <Ctrl-Z>.


Regarding your first suggestion for :getKey

I wanted to use a very simple algorithm for :getKey and :getMaskedInput. There is no simple way to differentiate between [Alt-26] and [Alt-10], and I wanted [Enter] to be the normal way to end. So I decided I want to treat all three cases the same - and I opted to return nothing if any of those options are pressed.

Note that FOR /F strips off only one <CR> if it is the last character in the string, so a single FOR /F preserves <CR> when [Enter] is pressed.

So that is why I added the 2nd FOR /F loop to :getKey - to eliminate the <CR> from the output so that [Enter] behaves the same as the other two. I initialize the result to undefined before reading the key, so I get my desired result.

The other option would have been to only use one FOR /F, and look at the captured key and check if it was <CR>, but I did not want to add the code to generate a <CR>, which is why I chose the 2nd FOR /F.

I now realize that I could have chosen to represent those 3 key presses as <CR>, and then I need only initialize the result to <CR> and I only need one FOR /F loop.

The modified IF code you suggest can't work. It successfully works with ^ and !, but for all other characters it will add an unwanted ^ before the returned character if delayed expansion is enabled. The code I used was the simplest way to preserve all characters, including !, if delayed expansion happened to be enabled.

But now that I know <Ctrl-Z> is preserved on Win 7, I will probably ditch this version all together. I do not want the outcome to be version dependent.


Regarding your second suggestion for :getAnyKey

I need the FINDSTR /N to preserve all lines of output so that I can differentiate between <Ctrl-Z> and <LF> on Win 10.

I plan to rewrite :getKey with code from :getAnyKey, and ditch :getAnykey. I will need to adapt :getMaskedInput as well. I am also thinking of making :getKey configurable so that you can choose what characters you want to return for [Enter] and [Alt-10].


Dave Benham
Last edited by dbenham on 06 Sep 2016 17:49, edited 2 times in total.

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

Re: Read key presses via REPLACE - New functions :getKey, :getAnyKey, :getMaskedInput

#14 Post by jeb » 06 Sep 2016 17:44

Hi Dave,

I was not aware of the difference between Win10 and Win7 for the <CTRL-Z> key.

Btw. You can create a <NUL> character by pressing <CTRL-2> (tested with Win7).
REPLACE outputs <NUL><CR><LF> for this.


dbenham wrote:The modified IF code you suggest can't work. It successfully works with ^ and !, but for all other characters it will add an unwanted ^ before the returned character if delayed expansion is enabled. The code I used was the simplest way to preserve ! if delayed expansion happened to be enabled.

Obviously :wink: it works as expected, as the caret will always be removed by the appended exclamation mark.

jeb

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

Re: Read key presses via REPLACE - New functions :getKey, :getAnyKey, :getMaskedInput

#15 Post by dbenham » 06 Sep 2016 17:59

Wow :shock:
Interesting find about <Ctrl-2> and Null.
EDIT - But FOR /F cannot capture the character, so I don't see a simple way to differentiate between <NULL> and <LF>. I believe I would need a temp file, and possibly the FC command

And Ugh, of course your code works, now that you spell it out. :oops:
I feel as though both of our IF statements are equally simple and elegant for the task at hand.


Dave Benham

Post Reply