0

I have a text file that lists alphanumeric codes such as follows:

17YYUIO
QUICK77
RTY8900

I would like a batch file that either modifies the original text file so everything is double spaced OR simply a result is echoed to the screen as follows:

1 7 Y Y U I O P
Q U I C K 7 7 7
R T Y 8 9 0 0 Q

Basically, I am trying to get Microsoft Narrator to read individual characters from a text file instead of Narrator attempting to pronounce the characters as words.

Any ideas on whether adding spaces between every character in a text file using a batch file is possible?

Heap77
  • 3
  • 1
  • 3
  • 1
    Where do the last characters of your proposed output come from?? – aschipfl Jan 01 '17 at 19:32
  • Using [JREPL.BAT](http://www.dostips.com/forum/viewtopic.php?t=6044): `jrepl "(?!^|$)" " " /f input.txt` to print result to screen. Add `/o output.txt` to write to a file, or `/o -` to replace the original file. – dbenham Jan 01 '17 at 23:46

5 Answers5

3

Here is a completely commented batch code for this task:

@echo off
setlocal EnableExtensions

rem Define the name of the file to encode by inserting a space
rem character between each alphanumeric character in the file.
set "CodeFile=Test.txt"

rem Define the name of a temporary file with the encoded lines.
set "TempFile=%CodeFile%.tmp"

rem Make sure the temporary file does not already exist.
del "%TempFile%" 2>nul

rem Process each non blank line from file to encode.
for /F "usebackq delims=" %%I in ("%CodeFile%") do call :EncodeLine "%%~I"

rem Overwrite the file to encode with the temporary file
rem with the encoded lines if there was one created at all.
if exist "%TempFile%" move /Y "%TempFile%" "%CodeFile%"

rem Restore previous command environment end exit processing
rem of the batch file without falling through to subroutine.
endlocal
goto :EOF

rem The subroutine EncodeLine inserts a space between each
rem character of the string passed to the subroutine and
rem appends the encoded line to the temporary file.

rem This is done by appending the first character from input
rem line to the current output line after an additionally
rem added space character and removing from input line the
rem just copied first character until the input line does
rem not contain anymore any character.

rem Then output the encoded line without first space character
rem with redirecting the output from handle STDERR to the
rem temporary file with appending the line to temporary file.

rem It is important here to have the redirection operator
rem and the temporary file name on left side before the
rem command ECHO to get correct output if the output line
rem ends with 1 to 9 and avoid adding a trailing space on
rem each output line on writing the line into the file.

rem goto :EOF at end results in exiting the subroutine
rem and returning to FOR command line above.

:EncodeLine
set "InputLine=%~1"
set "OutputLine="

:NextChar
set "OutputLine=%OutputLine% %InputLine:~0,1%"
set "InputLine=%InputLine:~1%"
if not "%InputLine%" == "" goto NextChar

>>"%TempFile%" echo %OutputLine:~1%
goto :EOF

Note: This batch code is not designed for working with any text file contents, just for ANSI encoded text files containing only alphanumeric characters and line terminations.

For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.

  • call /?
  • del /?
  • echo /?
  • endlocal /?
  • for /?
  • goto /?
  • if /?
  • move /?
  • rem /?
  • set /?
  • setlocal /?

See also the Microsoft article Using command redirection operators.

Mofi
  • 46,139
  • 17
  • 80
  • 143
  • 1
    This fails if the file contains poison characters like `&`, `<` etc. – dbenham Jan 02 '17 at 16:16
  • 2
    Yes, that is right and clearly indicated below the batch code in the note. The questioner has written that it needs the batch code for a text file with __alphanumeric__ characters. And as the questioner is a beginner in batch code writing, I did not make it more complex than necessary by using for example delayed expansion making it more difficult to watch how this batch file works on running from within a command prompt window with `@echo on` in first line. The batch file also does not work for UTF-8 and UTF-16 encoded text files as I wrote, too. – Mofi Jan 02 '17 at 16:54
  • Sorry, I missed the "only alphanumeric characters and line terminations." when I scanned the answer. Although that is not quite accurate. There are many non-alphanumeric symbols that work just fine with your code. – dbenham Jan 02 '17 at 17:54
2
@echo off
setlocal EnableDelayedExpansion

(for /F "delims=" %%a in (input.txt) do (
   set "output="
   for /F "delims=" %%b in ('cmd /U /C echo %%a^| find /V ""') do (
      set "output=!output! %%b"
   )
   echo !output:~1!
)) > output.txt

rem Replace the input file for the result
move output.txt input.txt /Y

This solution makes good use of the fact that /U switch in cmd.exe creates Unicode output, that is, each input character is converted into two output bytes where the first byte of each pair is a binary zero. These "0-char" pairs are read by find command that takes each zero as an end-of-line mark. The final result is that the %%b replaceable parameter in the for command takes each character of the input lines in a very simple way.

This program eliminate exclamation-marks from the input file; this point may be fixed, if needed.

EDIT: Method modified as reply to comments

I modified the original method so it now manages almost all special characters (excepting quote):

2nd EDIT: I further modified the method following a dbenham's advice and it now manages all characters!

@echo off
setlocal DisableDelayedExpansion

(for /F delims^=^ eol^= %%a in (input.txt) do (
   set "str=%%a"
   set "output= "
   for /F delims^=^ eol^= %%b in ('cmd /V:ON /U /C echo !str!^| find /V ""') do (
      setlocal EnableDelayedExpansion
      for /F "delims=" %%c in ("!output!") do (
         endlocal
         set "output=%%c %%b"
      )
   )
   setlocal EnableDelayedExpansion
   echo !output:~2!
   endlocal
)) > output.txt

rem Replace the input file for the result
move output.txt input.txt /Y

input.txt:

1 7<Y>IO
QU|C"K&7
;T Y!9^0

output.txt:

1   7 < Y > I O
Q U | C " K & 7
; T   Y ! 9 ^ 0

If the division in lines would not be needed in the output file, a simpler method could be based on this:

set "output="
for /F eol^= %%b in ('cmd /U /C type input.txt ^| find /V ""') do (
   set "output=!output! %%b"
)
Aacini
  • 65,180
  • 12
  • 72
  • 108
  • 1
    I was curious about this answer. It does look short and sweet, but as you noted it doesn't handle exclamation points. It also doesn't handle semi colons and ALOT of the [characters you'd need to escape](http://www.robvanderwoude.com/escapechars.php) in batch. – Troy Witthoeft Jan 01 '17 at 20:27
  • @TroyWitthoeft - There should not be a problem with most characters. Just `!` as noted, as well as `^`. Also, the code will skip lines that begin with `:`. All other printable ANSI characters should be just fine. – dbenham Jan 01 '17 at 23:42
  • Have you tested with the less than and greater than signs? Those too are showing errors in my testing? See my answer for an example that handles exclamation point and colons. – Troy Witthoeft Jan 01 '17 at 23:55
  • 1
    The main problem here is the pipe; you could replace the involved `echo %%a` by `echo^("%%a"` and remove the surrounding `""` later (by `echo(!output:~3,-2!` instead of `echo !output:~1!`), which would lead to have `^`, `!` and `"` as forbidden characters, but all other special characters may occur; I guess the only reliable way to use this method is to use a temporary file rather than a pipe; anyway, I really like the main idea of this approach, hence +1... – aschipfl Jan 02 '17 at 09:17
  • 1
    @dbenham, I guess you mean lines that begin with `;`, right? – aschipfl Jan 02 '17 at 09:20
  • 1
    @TroyWitthoeft - Ouch! My last comment was entirely wrong :-( Colon vs. semicolon was a complete short circuit, and I missed the significance of the extra layer of parsing introduced by `cmd /U /C echo %%a`. All poison characters are indeed a problem with the current code. – dbenham Jan 02 '17 at 14:08
  • @aschipfl - ditto. Also, adding the quotes doesn't completely solve the problem if the content already contains quotes and poison characters. – dbenham Jan 02 '17 at 14:09
  • @Denham No worries. That bit of code you posted is the most elegant, with the caveat of special characters. To handle special characters means increasing code complexity. I think my answer has them covered? However, the author may not need that. For future visitors - gladly - we have several folks offering ideas here. All of them work in some fashion. Thanks all. – Troy Witthoeft Jan 02 '17 at 14:30
  • @dbenham, that is true, but I found a way to solve everything now: besides toggling delayed expansion (to properly handle `^` and `!`), adding surrounding quotes and temporarily doubling every `"` in the piped data allows all ANSI characters; opposed to my initial thought, a temporary file does not help at all, because this does not avoid the extra parsing layer, as `cmd /U /C` is needed anyway... – aschipfl Jan 02 '17 at 15:27
  • @TroyWitthoeft - This is not my answer - it is Aacini's – dbenham Jan 02 '17 at 15:30
  • @aschipfl - I don't see how you can toggle delayed expansion and preserve the growing output variable unless you add even more complexity. – dbenham Jan 02 '17 at 16:11
  • @dbenham and others: I modified the method so it now manages _almost_ all characters, excepting quote... – Aacini Jan 02 '17 at 16:18
  • 2
    Nice - You can solve the quoted poison character problem by `set "str=%%a"` before the inner loop, and then use `cmd /V:on /U /C echo(!str!`. Also, I would preserve spacing between words by using `delims^=^ eol^=` – dbenham Jan 02 '17 at 16:37
  • @dbenham, meanwhile Aacini shows the to do the toggling I was thinking about, in a very smart and compact way. By the way, brilliant idea using the `cmd /V` option; so I learned that a command in `for /F` inherits the current environment (similar to the `cmd` instances of pipes)... – aschipfl Jan 02 '17 at 18:19
0

This would be trivial in PowerShell, but batch is a little... errr... messy:

@echo off
setlocal
set INPUT_FILE=c:\temp\in.txt
for /f "tokens=*" %%L in (%INPUT_FILE%) do call :DO_LINE %%L
endlocal
goto END

:DO_LINE
set LINE_IN=%*
set LINE_OUT=
:DL_LOOP
if "%LINE_IN%" == "" echo %LINE_OUT%&&goto END
if "%LINE_OUT%" == "" (
    set LINE_OUT=%LINE_IN:~0,1%
) else ( 
    set LINE_OUT=%LINE_OUT% %LINE_IN:~0,1%
)
set LINE_IN=%LINE_IN:~1%
goto DL_LOOP
goto END

:END

You'll need to modify the set INPUT_FILE line.

Good luck!

Simon Catlin
  • 2,141
  • 1
  • 13
  • 15
0

This can be done. We will use a FOR loop with a subroutine nested inside. The FOR Loop reads the lines, the LineSpacer subroutine uses substring syntax to space them out.

@echo OFF

REM - Set your source and destination files.
Set SourceFilePath=C:\Users\Admin\Desktop\SourceFile.txt
Set OutputFilePath=C:\Users\Admin\Desktop\OutputFile.txt

REM Blank out the OutputFile
TYPE nul > %OutputFilePath%

REM For every line in the source text file call linespacer subroutine.
FOR /F "tokens=*" %%A IN (%SourceFilePath%) DO (
    SET line=%%A
    SETLOCAL EnableDelayedExpansion
    SET spacedline=
    CALL :LineSpacer
    ECHO !spacedline! >> %OutputFilePath%
    ENDLOCAL    
)

REM - Get the first character of the line, add a space to it
REM - Reset the line variable to one less character, repeat. 
:LineSpacer
    IF DEFINED line (
       SET char=!line:~0,1!
       SET "charpluspace=!char! "
       SET spacedline=!spacedline!!charpluspace!
       SET "line=!line:~1!"
       IF "%line%" EQU "!line:~1!" EXIT /b
       GOTO linespacer
    ) 
EXIT /b

The batch file above will do what you need. To get a better understanding of how it's done, try some of the examples in the two links I've posted.

Community
  • 1
  • 1
Troy Witthoeft
  • 2,498
  • 2
  • 28
  • 37
0

I would use my JREPL.BAT regular expression command line text processing utility, as the solution is simpler and faster than any pure batch solution (disregarding the complexity of the utility iteslf)

Write to screen

jrepl "(?!^|$)" " " /f file.txt

Write to new file

jrepl "(?!^|$)" " " /f file.txt /o new.txt

Overwrite original

jrepl "(?!^|$)" " " /f file.txt /o -

But if you want a pure batch solution to replace the original file, then I think this is about as fast as you can get and still support all characters other than null, preserve white space, and support lines of max length ~4090 (~8180 after inserting spaces). The logic is very straight forward, once you add the cryptic code to compute the length of a string. But you can productively use :strlen even if you don't understand how it works.

@echo off
setlocal disableDelayedExpansion
set "file=%~1"

>"%file%.new" (
  for /f "delims=" %%A in ('findstr /n "^" "%file%"') do (
    set "in=%%A"
    setlocal enableDelayedExpansion
    set "in=!in:*:=!"
    call :strlen in len
    set /a len-=1
    set "out="
    for /l %%N in (0 1 !len!) do set "out=!out! !in:~%%N,1!"
    if defined out (echo(!out:~1!) else echo(
    endlocal
  )
)
move /y "%file%.new" "%file%" >nul
exit /b

:strlen <stringVar> <resultVar>
(
  setlocal EnableDelayedExpansion
  set "s=!%~1!#"
  set "len=0"
  for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
    if "!s:~%%P,1!" NEQ "" (
      set /a "len+=%%P"
      set "s=!s:~%%P!"
    )
  )
)
(
  endlocal
  set "%~2=%len%"
  exit /b
)
Community
  • 1
  • 1
dbenham
  • 127,446
  • 28
  • 251
  • 390