Add number List in For Loop without enabledelayedexpansion

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
Jer
Posts: 164
Joined: 23 Nov 2014 17:13
Location: California USA

Add number List in For Loop without enabledelayedexpansion

#1 Post by Jer » 03 Jul 2015 21:15

I am attempting to create a subset of a large list and display it with a column of
numbers. There was no problem until I saw that ! marks in the text caused
problems with those lines of text when delayed expansion was enabled.
The problem was the disappearance of part of the text.

The following code produces zeroes with the text. Any hints or solutions would be appreciated.

Code: Select all

@Echo Off

Type NUL>tmp.txt
Echo three to get ready...>>tmp.txt
Echo four to go!>>tmp.txt

Set /A aCounter=0
For /F "tokens=*" %%a In (tmp.txt) Do (
   Set /A aCounter+=1
   Echo %aCounter%  %%a
)

Yury
Posts: 115
Joined: 28 Dec 2013 07:54

Re: Add number List in For Loop without enabledelayedexpansi

#2 Post by Yury » 03 Jul 2015 23:54

1)

Code: Select all

@echo off

>"tmp.txt" (
echo three to get ready...
echo four to go!
)

setlocal

for /f "usebackq delims=" %%a in ("tmp.txt") do (
 set /a aCounter+=1
 cmd /v:on /c echo !aCounter! %%a
)

pause

endlocal& exit /b


.


2)

Code: Select all

@echo off

>"tmp.txt" (
echo three to get ready...
echo four to go!
)

setlocal

for /f "usebackq delims=" %%a in ("tmp.txt") do (
 call :GetCount
 echo %%a
)

pause

endlocal& exit /b


:GetCount
 set /a aCounter+=1
 <nul set /p ="%aCounter% "
 goto :eof


.


3)

Code: Select all

@echo off

>"tmp.txt" (
echo three to get ready...
echo four to go!
)

setlocal

for /f "usebackq delims=" %%A in ("tmp.txt") do (
 set /a aCounter+=1
 call echo %%aCounter%% %%A
)

pause

endlocal& exit /b


(please note that the letter "A" in the variable "%% A" is in the uppercase, this is to avoid the conflict between "%%aCounter%%" and "%%a").

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

Re: Add number List in For Loop without enabledelayedexpansi

#3 Post by aGerman » 04 Jul 2015 03:47

FINDSTR /N would be another possibility.

Simply with its native output

Code: Select all

findstr /n . "tmp.txt"

or processed in a FOR /F loop

Code: Select all

for /f "tokens=1* delims=:" %%i in ('findstr /n . "tmp.txt"') do echo %%i  %%j

Regards
aGerman

einstein1969
Expert
Posts: 776
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Add number List in For Loop without enabledelayedexpansi

#4 Post by einstein1969 » 04 Jul 2015 04:45

It could work the delayed toggling technic?

Code: Select all

@Echo Off

Type NUL>tmp.txt
Echo three to get ready...>>tmp.txt
Echo four to go!>>tmp.txt

Set /A aCounter=0

setlocal DisableDelayedExpansion
For /F "tokens=*" %%a In (tmp.txt) Do (
   set "line=%%a"
   Set /A aCounter+=1
   setlocal EnableDelayedExpansion
     echo !aCounter!:!line!
   endlocal
)


output:

Code: Select all

1:three to get ready...
2:four to go!


einstein1969

Jer
Posts: 164
Joined: 23 Nov 2014 17:13
Location: California USA

Re: Add number List in For Loop without enabledelayedexpansi

#5 Post by Jer » 04 Jul 2015 15:26

Thanks to all!
I got it to work using both Yuri and aGerman's suggestions, without which the
batch code failed when the ampersand occurred in the text. What I would like is
an explanation of how the column of numbers gets displayed...very mysterious.

From my tests, it appears that the & and ! characters do not interfere with the
search and echoing of what is found by FINDSTR.

Here's my proof-of-concept code:

Code: Select all

@Echo Off
setlocal disabledelayedexpansion

If Not EXIST tmp.txt (Echo tmp.txt not found.  Exiting.& exit /b)
Echo.& Echo source.txt:
Type tmp.txt & Echo.

:top
Set aCounter=0
Set "var="
Set /p var=Enter one of the words in the list, or partial word, or just press Enter to quit^>^>^>
If "%var%"=="" exit /b

For /f "tokens=1* delims=:" %%i In ('findstr /ic:"%var%" "tmp.txt"') Do (
   Call :GetCount
   Echo %%i
)

If "%aCounter%"=="0" Echo %var% not found& Echo.

GoTo:top

:GetCount
 Set /a aCounter+=1
 <nul Set /p ="%aCounter% "
 GoTo :eof


and the data in the file tmp.txt:

Alice & Cards (Gavotte)
Alice & Cheshire Cat
Alice & Flowers
Alice Introduction
Hip! Hip! Harrah!
I've heard that story
Tell me a story

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: Add number List in For Loop without enabledelayedexpansi

#6 Post by Ed Dyreen » 04 Jul 2015 17:29

When you are reading input into a variable, delayedExpansion should either be disabled or the input should escape characters that would otherwise be interpreted.

Code: Select all

% read input %
setlocal disableDelayedExpansion
<input set /P line=
% process input %
setlocal enableDelayedExpansion
set "line=!line!!line:~-1!"
Once you have a variable set to a value, delayedExpansion should be enabled in order to preserve characters that would otherwise be interpreted.

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

Re: Add number List in For Loop without enabledelayedexpansi

#7 Post by aGerman » 05 Jul 2015 02:52

Normally variable expansion is processed before the line or a block of lines (enclosed in parentheses) will be executed. That means special characters that occur in the read line are parsed and executed. E.g. the ampersand concatenates commands. ECHO Alice & Flowers will output Alice and the following space while the command processor tries to execute Flowers as a command (which will fail because there is no such command).
If you want to avoid it you have to enable the delayed variable expansion. This way special characters will be outputted because they are not already recognized while parsing the line. But now the exclamation mark is a marker for variables too and causes other problems with the processing of values that contains literal exclamation marks.
Further read http://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/4095133#4095133. Worth it to read the whole thread.

The conclusion is this rough rule of thumb:
Assigning a variable is save with delayed expansion disabled; once you have assigned the value it is save to use the variable with delayed expansion enabled.

Ed Dyreen and einstein1969 already told you what to do: toggle between disabled und enabled expansion.

My changed approach:

Code: Select all

setlocal DisableDelayedExpansion
for /f "tokens=1* delims=:" %%i in ('findstr /n . "tmp.txt"') do (
  set "line=%%j"
  setlocal EnableDelayedExpansion
  echo %%i  !line!
  endlocal
)

Regards
aGerman

Jer
Posts: 164
Joined: 23 Nov 2014 17:13
Location: California USA

Re: Add number List in For Loop without enabledelayedexpansi

#8 Post by Jer » 05 Jul 2015 15:37

aGerman, thanks for your thoughtful and educating comments.
The method I presented in my last post is more suitable to what I am doing.

I stated in my first post that a subset from many lines of data is to be displayed
in a numbered list. This is for my never-ending development of a DOS batch file application that
draws from my personal collection of 18,000+ MIDI and MP3 music files. I use whole-word searching
to return a numbered list all all titles that have the word entered, or contiguous words, and also
an option to return either of two words, such as "street" or "streets". The numbered and
alphabetized list is presented in pages and has pauses between pages if the list is larger than my
defined page.

The application opens Windows Media player to play individual files or a playlist, and the
column of numbers identify which files are to be played or added to the editable playlist.
While viewing a list, one or more numbers can be entered as a single request, or a range,
such as 24-31 to perform some action for files #24 thru #31 on the list. This is a can-it-be-done?
challenge for me, and the answer is 'Yes, I'm getting there'.

Your example returns every available line which is not what I want to do. But your advice did help
me solve the issue of working with ! and & in the text. And through this post I learned how to generate
sequential numbers on the fly with this in a function from Yuri,
Set /a aCounter+=1
<nul Set /p ="%aCounter% "

which appears to echo to the console the line number without the linefeed, so that whatever
is immediately echoed after that , in my case a music file name, follows the line number.

I hope to be able to right-align the column of numbers to 5 places, which I can do with a character,
for example ....1, ....2, ...10, etc., with the number of leading spaces depending on the value
of aCounter So far I have not been able to keep leading spaces when substituted in another
variable that includes space followed by the value in aCounter.
Thanks again!

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

Re: Add number List in For Loop without enabledelayedexpansi

#9 Post by aGerman » 05 Jul 2015 17:04

I hope to be able to right-align the column of numbers to 5 places

Just prepend 4 spaces to the whatever sized number (1-5 digits) but output only the last 5 characters.

Code: Select all

setlocal DisableDelayedExpansion
for /f "tokens=1* delims=:" %%i in ('findstr /n . "tmp.txt"') do (
  set "num=    %%i"
  set "line=%%j"
  setlocal EnableDelayedExpansion
  echo !num:~-5!  !line!
  endlocal
)

Regards
aGerman

Jer
Posts: 164
Joined: 23 Nov 2014 17:13
Location: California USA

Re: Add number List in For Loop without enabledelayedexpansi

#10 Post by Jer » 05 Jul 2015 23:27

aGerman, your example uses line numbers generated by FINDSTR. My requirement is to add
sequential numbers only to the lines FINDSTR returns, which are a subset of a large text file.

The way I found to right-align a column of numbers to 5 places in the working batch file that I posted
was to create white space by adding an ASCII character followed by as many spaces as were needed
to align the number. The alignment tested OKAY for 10,000+ lines, but the typical return in the
program is usually none up to several hundred lines, and search words are restricted to 3 characters or
more, with common words such as 'the' not allowed if entered as a single target.

The column alignment is done with Yuri's function modified:

Code: Select all

:GetCount
 Set /a aCounter+=1
 If %aCounter% LEQ 9 (
    Set "p=.    "
 ) Else If %aCounter% LEQ 99 (
    Set "p=.   "
 ) Else If %aCounter% LEQ 999 (
    Set "p=.  "
 ) Else If %aCounter% LEQ 9999 (
    Set "p=. "
 ) Else (
    Set "p=."
 )
 <nul Set /p="%p%"
 <nul Set /p="%aCounter% "
 GoTo :eof
Last edited by Jer on 05 Jul 2015 23:38, edited 1 time in total.

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

Re: Add number List in For Loop without enabledelayedexpansi

#11 Post by foxidrive » 05 Jul 2015 23:38

Jer wrote:aGerman, your example uses line numbers generated by FINDSTR. My requirement is to add
sequential numbers only to the lines FINDSTR returns, which are a subset of a large text file.


If you use temporary files then the results file can be numbered in the one process.

Yury
Posts: 115
Joined: 28 Dec 2013 07:54

Re: Add number List in For Loop without enabledelayedexpansi

#12 Post by Yury » 06 Jul 2015 00:38

Code: Select all

:GetCount
 set /a aCounter+=1
 if %aCounter% equ 1 for /f %%? in ('forfiles /m "%~nx0" /c "cmd /c echo 0x08"') do set BS=%%?
 set "aCounter=    %aCounter%"
 <nul set /p ="%BS%%aCounter:~-5% "
 goto :eof

Jer
Posts: 164
Joined: 23 Nov 2014 17:13
Location: California USA

Re: Add number List in For Loop without enabledelayedexpansi

#13 Post by Jer » 06 Jul 2015 20:28

Here is what is working well: both the use of temporary files suggested by foxidrive
and echoing a backspace from Yuri's last post.
The lines returned by FINDSTR are stored in a file to type out either as 1 page if the
list is small, or to be scrolled in blocks of lines using a For Loop if the list is more than
one page. When using the backspace method, that inserted a backspace character into
the text file in column #1, setting the line off by 1 column and causing a glitch when it came
time to manipulate the text (play item or add to playlist)

So the solution is to echo all into a temporary file, then use a second For Loop with FINDSTR
and trim off the first column. That keeps the column of numbers right-aligned to 5 places and
positioned in columns 1-5.

The second for loop is something like this:

Code: Select all

setlocal enabledelayedexpansion
 For /f "tokens=1* delims=:" %%i In ('findstr /ic:"." "tmp.txt"') Do (
    Set "txt=%%i"
    Set "txt=!txt:~1!
    Echo !txt!>>stage3.txt
 )
 endlocal


You all have been a great help to make this work for me. Thanks!
Jerry

Jer
Posts: 164
Joined: 23 Nov 2014 17:13
Location: California USA

Re: Add number List in For Loop without enabledelayedexpansi

#14 Post by Jer » 07 Jul 2015 14:09

I hope you will find this post worthy of a look. Below is a snippet that
is more complete. It deals with removing the backspace, the character
that facilitates the number formatting, so that the BS does not occur in the
destination file. Also handled are those pesky ! and & chars in the source file.

My goal was to create in a file a numbered list and be able to control the alignment of
the numbers. Comments, refinements & criticism are welcome.

Code: Select all

@Echo Off
::creates a numbered list; formats the numbers to be right-justified in the first 5 columns.
::handles ! and & characters in the source text.
::
Type NUL>source.txt
setlocal disabledelayedexpansion

::write sample source text to file
For %%a In ("one!" "one two!" "one, two, three!" "one, two, three & four!") Do (
   For %%t In (%%a) Do (Set "tx=%%t" & setlocal enabledelayedexpansion & >>source.txt Echo(!tx:~1,-1!& endlocal)
)

Echo.& Echo source.txt contents:&Echo.& Type source.txt

Set "myTarget=one"
Set "outFile=results.txt"
Set /A aCounter=0

Type NUL>%outFile%
Type NUL>tmp.txt

For /f "tokens=1* delims=:" %%i In ('findstr /c:"%myTarget%" "source.txt"') Do (
    Call :GetLineNo
    Echo %%i>>%outFile%
 )

 For /f "tokens=1* delims=:" %%i In ('findstr /c:"%myTarget%" %outFile%') Do (
    Set "txt=%%i"
    setlocal enabledelayedexpansion
    Set "txt=!txt:~1!
    Echo !txt!>>tmp.txt
    endlocal
 )

 COPY tmp.txt "%outFile%">nul

 DEL tmp.txt

 Echo.& Echo The search target was "%myTarget%"
 Echo The folowing lines are stored in the file %outFile%:&Echo.& Type %outFile% & Echo.

endlocal & GoTo:eof


:GetLineNo
 Set /a aCounter+=1
 if %aCounter% equ 1 for /f %%? in ('forfiles /m "%~nx0" /c "cmd /c echo 0x08"') do set BS=%%?
 Set "aCounter=    %aCounter%"
 <nul Set /p ="%BS%%aCounter:~-5%  ">>%outFile%
 goto:eof

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

Re: Add number List in For Loop without enabledelayedexpansi

#15 Post by aGerman » 07 Jul 2015 16:59

Comments, refinements & criticism are welcome.

Code: Select all

@echo off &setlocal DisableDelayedExpansion

:: write sample source text to file
>"source.txt" (for %%i in ("one!" "one two!" "one, two, three!" "no target here" "one, two, three & four!") do echo %%~i)
echo(&echo source.txt contents:&echo(&type "source.txt"

:: process source text
set "myTarget=one"
set "outFile=results.txt"

>"%outfile%" (
  for /f "tokens=1* delims=:" %%i in ('findstr /c:"%myTarget%" "source.txt" ^| findstr /n .') do (
    set "aCounter=    %%i"&set "txt=%%j"
    setlocal EnableDelayedExpansion
    echo !aCounter:~-5!  !txt!
    endlocal
  )
)

echo(&echo The search target was "%myTarget%"
echo The folowing lines are stored in the file %outFile%:&echo(&type "%outFile%"&echo(
pause

Regards
aGerman

Post Reply