0

Primary Question:

How should the posted batch script, combined.bat, be changed so that I can have correct output for both OUTPUT_A and OUTPUT_B at the same runtime? (Knowledge of EnableDelayedExpansion usage/scope is required to provide a correct solution)

Context:

I combined batch scripts from the two following links:

https://stackoverflow.com/a/15535761/7889588

https://helloacm.com/the-chr-function-implementation-in-windows-pure-batch-script/

and created the following batch script (let me refer to this as combined.bat):

combined.bat is meant to be an ASCII Dec to ASCII Char converter.

@echo off
@setlocal EnableDelayedExpansion

:: define characters from 32 to 126
set alphabet= !"#$%%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"

set testVar=64_65_66_67_68_69_70_64
echo INPUT = %testVar%
echo.

set resultA=

call :CHR 64
set resultA=%resultA%%char%
call :CHR 65
set resultA=%resultA%%char%
call :CHR 66
set resultA=%resultA%%char%
call :CHR 67
set resultA=%resultA%%char%
call :CHR 68
set resultA=%resultA%%char%
call :CHR 69
set resultA=%resultA%%char%
call :CHR 70
set resultA=%resultA%%char%
call :CHR 64
set resultA=%resultA%%char%
echo OUTPUT_A = %resultA%
echo.

set char=randomValueJustToInitFill
call :split "%testVar%" "_" array

set resultB=
:: Loop through the resulting array
for /L %%I in (0, 1, %array.ubound%) do (
    call :CHR !array[%%I]!
    echo array[%%I] = !array[%%I]! = !char!
    set resultB=!resultB!!char!
)
echo OUTPUT_B = !resultB!
echo.
goto :EOF

:CHR
:: valid range should be from 32 to 126 inclusive
if "%1"=="" goto :EOF
if %1 LSS 32 goto :EOF
if %1 GTR 126 goto :EOF

:: call function
call :ASCII %1 chr

:: print result, using ^ to escape special characters 
:: such as <, > and |
REM echo.^%chr%
set char=^%chr%

:: end the script
goto :EOF

:: sub-routine
:ASCII
    setlocal EnableDelayedExpansion
        :: get the index
        set /a var=%1-32
        :: retrieve letter
        set character=!alphabet:~%var%,1!
    :: end the routine and return result as second parameter (out)
    endlocal & set %2=^%character%
@EXIT /B 0

:: split subroutine
:split <string_to_split> <split_delimiter> <array_to_populate>
:: populates <array_to_populate>
:: creates arrayname.length (number of elements in array)
:: creates arrayname.ubound (upper index of array)

set "_data=%~1"

:: replace delimiter with " " and enclose in quotes
set _data="!_data:%~2=" "!"

:: remove empty "" (comment this out if you need to keep empty elements)
set "_data=%_data:""=%"

:: initialize array.length=0, array.ubound=-1
set /a "%~3.length=0, %~3.ubound=-1"

for %%I in (%_data%) do (
    set "%~3[!%~3.length!]=%%~I"
    set /a "%~3.length+=1, %~3.ubound+=1"
)
@EXIT /B 0
@endlocal

when running combined.bat, it yields the following:

INPUT = 64_65_66_67_68_69_70_64

OUTPUT_A = Z[\]_`aZ

array[0] = 64 = Z
array[1] = 65 = [
array[2] = 66 = \
array[3] = 67 = ]
array[4] = 68 = _
array[5] = 69 = `
array[6] = 70 = a
array[7] = 64 = Z
OUTPUT_B = Z[\]_`aZ

OUTPUT_A and OUTPUT_B are both not correct. It is offset by 26. I made an educated guess that this is caused by line 2 of combined.bat, @setlocal EnableDelayedExpansion.

So when I comment out line 2 by changing it to REM @setlocal EnableDelayedExpansion, edited combined.bat yields the following:

INPUT = 64_65_66_67_68_69_70_64

OUTPUT_A = @ABCDEF@

array[0] = !array[0]! = !char!
array[1] = !array[1]! = !char!
OUTPUT_B = !resultB!

As you can see, OUTPUT_A is now correct. However, the batch code for OUTPUT_B does not work properly anymore. I tried tinkering around with the script for a bit (i.e. placing EnableDelayedExpansion in different locations, etc.). I am still unable to get the script to yield correct output for both OUTPUT_A and OUTPUT_B at the same runtime.

When providing a solution, please elaborate in respect to the script changes made to combined.bat.

Hybr13
  • 80
  • 2
  • 12
  • Well, you are right, the line `setlocal EnableDelayedExpansion` is problematic here, because in the assignment `set alphabet=` there is an exclamation mark which is consumed by delayed expansion; try to put `^^!` instead of `!`, and also `^^^^` instead of `^`, in order to escape the `!` as well as the `^` from both expansion phases (normal `%` and delayed `!`). I did not check the rest of the code though... – aschipfl Mar 07 '19 at 22:16
  • `set errorlevel=0` may ruin further use of builtin `errorlevel` variable. Suggest removal, especially as you are setting and not using the variable anywhere. – michael_heath Mar 08 '19 at 01:45
  • @aschipfl I made the changes you suggested and it almost fixed everything.Thank you. The `!` / `33` conversion is still not working though. Trying to figure it out atm. @michael_heath I got rid of the errorlevel vars since I don't use them, as you suggested, in the original question – Hybr13 Mar 08 '19 at 16:12
  • I suggested to use `^^^^` instead of `^`, but actually `^^` needs to be used instead because of the preceding `"`, which prevents such characters to be recognised as escape characters first; sorry for that! I did not check the other error you mentioned, but you have it solved yourself meanwhile anyway... – aschipfl Mar 08 '19 at 18:20
  • @aschipfl I noticed while trying to get everything working. Nonetheless, thanks!! Your comment definitely helped me get everything working in a shorter period of time. – Hybr13 Mar 08 '19 at 18:35

1 Answers1

0

Figured it all out and posted the solution script, as well as the script output.

I changed alphabet so that the ! is now ^^!, and the ^ is now ^^^. This fixed a majority of the issues. However, there was still a bug where 33s were not converting correctly to !s. In order to fix that, I had to make a change to the :CHR and :ASCII functions. The significant script edits for the solution are listed at the bottom of this post.

Solution - ASCII Converting Batch Script (with all bug fixes):

@echo off
@setlocal EnableDelayedExpansion

:: define characters from 32 to 126
set alphabet= ^^!"#$%%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^^^_`abcdefghijklmnopqrstuvwxyz{|}~"

set testVar=64_65_66_67_68_69_70_64_32_33_34_126
echo INPUT = %testVar%
echo.

set resultA=

call :CHR 64
set resultA=!resultA!!char!
call :CHR 65        
set resultA=!resultA!!char!
call :CHR 66        
set resultA=!resultA!!char!
call :CHR 67        
set resultA=!resultA!!char!
call :CHR 68        
set resultA=!resultA!!char!
call :CHR 69        
set resultA=!resultA!!char!
call :CHR 70        
set resultA=!resultA!!char!
call :CHR 64        
set resultA=!resultA!!char!
call :CHR 32        
set resultA=!resultA!!char!
call :CHR 33        
set resultA=!resultA!!char!
call :CHR 34        
set resultA=!resultA!!char!
call :CHR 126
set resultA=!resultA!!char!
echo OUTPUT_A = !resultA!
echo.

set char=randomValueJustToInitFill
call :split "%testVar%" "_" array

set resultB=
:: Loop through the resulting array
for /L %%I in (0, 1, %array.ubound%) do (
    call :CHR !array[%%I]!
    echo array[%%I] = !array[%%I]! = !char!
    set resultB=!resultB!!char!
)
echo OUTPUT_B = !resultB!
echo.
goto :EOF

:CHR
:: valid range should be from 32 to 126 inclusive
if "%1"=="" goto :EOF
if %1 LSS 32 goto :EOF
if %1 GTR 126 goto :EOF

:: call function
call :ASCII %1 chr

:: print result, using ^ to escape special characters 
:: such as <, > and |
set char=!chr!

:: end the script
goto :EOF

:: sub-routine
:ASCII
        :: get the index
        set /a var=%1-32
        :: retrieve letter
        set character=!alphabet:~%var%,1!
    :: end the routine and return result as second parameter (out)
    set %2=!character!
@EXIT /B 0

:: split subroutine
:split <string_to_split> <split_delimiter> <array_to_populate>
:: populates <array_to_populate>
:: creates arrayname.length (number of elements in array)
:: creates arrayname.ubound (upper index of array)

set "_data=%~1"

:: replace delimiter with " " and enclose in quotes
set _data="!_data:%~2=" "!"

:: remove empty "" (comment this out if you need to keep empty elements)
set "_data=%_data:""=%"

:: initialize array.length=0, array.ubound=-1
set /a "%~3.length=0, %~3.ubound=-1"

for %%I in (%_data%) do (
    set "%~3[!%~3.length!]=%%~I"
    set /a "%~3.length+=1, %~3.ubound+=1"
)
@EXIT /B 0
@endlocal

Output of Solution Script:

INPUT = 64_65_66_67_68_69_70_64_32_33_34_126

OUTPUT_A = @ABCDEF@ !"~

array[0] = 64 = @
array[1] = 65 = A
array[2] = 66 = B
array[3] = 67 = C
array[4] = 68 = D
array[5] = 69 = E
array[6] = 70 = F
array[7] = 64 = @
array[8] = 32 =
array[9] = 33 = !
array[10] = 34 = "
array[11] = 126 = ~
OUTPUT_B = @ABCDEF@ !"~

Changes to the alphabet variable:

The following fixed a good bulk of the problem.

Changed

set alphabet= !"#$%%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"

to

set alphabet= ^^!"#$%%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^^^_`abcdefghijklmnopqrstuvwxyz{|}~"

Changes to :CHR and :ASCII functions:

The following fixed the bug where 33s were not converting correctly to !s.

  • Got rid of the local setlocal EnableDelayedExpansion and endlocal & specific to the :ASCII function.
  • Using set %2=!character! instead of set %2=^%character%.
  • Using set char=!chr! instead of set char=^%chr%.
Hybr13
  • 80
  • 2
  • 12
  • I suggest you take a look at [this batch file example](https://www.dostips.com/forum/viewtopic.php?p=32485#p32485) and [this one too](https://www.dostips.com/forum/viewtopic.php?p=32495#p32495). – Compo Mar 08 '19 at 19:00