Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Batch file to read first line of text that hasn't been used, and then mark as used

Tags:

batch-file

cmd

I have a requirement to, within a windows batch file, read the first available line from a text file, pass it to a variable and mark the name\line as used

An example of the file is below.

apple
pear
orange

The script would start with 'apple', pass 'apple' to a variable to be used later in the script (I know how to do that bit), and then write back that line to read &apple, the '&' works as a marker to say it's been used.

The file would then look like:

&apple
pear
orange

the next time the batch file is run it would take 'pear', pass it to a variable and mark it with a & making it look like:

&apple
&pear
orange

I started by trying to find '&' and then trying to move to the next line, but I'm failing after about 12 hours of trying. This is what I got so far .. not much:

for /f "tokens=1" %l in ('name.txt') do (Find /v "&" /v "^---- ^$") (For /F %n in (%l) do (set NewName=%n))

Thanks

like image 810
idarryl Avatar asked Dec 20 '22 13:12

idarryl


2 Answers

Running this on the.file would modify each line in turn;

@echo off
setlocal enabledelayedexpansion
type nul > the.file.temp
set last=
for /F "tokens=*" %%A in (the.file) do (
    set line=%%A
    if "!line:~0,1!" neq "&" if "!last!" equ "" (
        set last=!line!
        set line=^&!line!
    )
    echo !line! >> the.file.temp
)

echo last value is !last!
type the.file.temp > the.file

(If the line does not begin with & and the variable last is empty, put the line in last & modify line with a leading &. Always append line to a temp file, renaming when done)

like image 189
Alex K. Avatar answered Jan 24 '23 10:01

Alex K.


Alex k. has a good answer that is probably fine for most situations. (I upvoted.)

However, it will corrupt any text containing !. That limitation can be fixed by toggling delayed expansion on and off within the loop.

The solution is likely to be fast enough for most reasonably sized files. But a FOR loop can become quite slow for large files.

I tested a 190kb file containing 2817 lines, and the Alex K. solution took 20 seconds for one run.

Here is a completely different solution without using any loops that processes the same 190kb file in 0.07 seconds - 285 times faster :)

@echo off
setlocal enableDelayedExpansion
set "file=test.txt"
findstr /bv "$ &" "%file%" >"%file%.available"
set "var="
<"%file%.available" set /p "var="
if defined var (
  >"%file%.new" (
    findstr /b "&" "%file%"
    <nul set /p "=&"
    type "%file%.available"
  )
  move /y "%file%.new" "%file%" >nul
)
del "%file%.available"

echo var=!var!


Update: As requested in comment, here is a heavily commented version of the code.

@echo off
setlocal enableDelayedExpansion

:: Define the file to process
set "file=test.txt"

:: Write the unused lines to a temporary "available" file. We don't want any
:: empty lines, so I strip them out here. There are two regex search strings;
:: the first looks for empty lines, the second for lines starting with &.
:: The /v option means only write lines that don't match either search string.
findstr /bv "$ &" "%file%" >"%file%.available"

:: Read the first available line into a variable
set "var="
<"%file%.available" set /p "var="

:: If var defined, then continue, else we are done
if defined var (

  REM Redirect output to a "new" file. It is more efficient to redirect
  REM the entire block once than it is to redirect each command individulally
  >"%file%.new" (

    REM Write the already used lines to the "new" file
    findstr /b "&" "%file%"

    REM Append the & without a new line
    <nul set /p "=&"

    REM Append the unused lines from the "available" file. The first appended
    REM line is marked as used because of the previously written &
    type "%file%.available"
  )

  REM Replace the original file with the "new" content
  move /y "%file%.new" "%file%" >nul
)

:: Delete the temp "available" file
del "%file%.available"

:: Display the result
echo var=!var!


I haven't tested this, but I just realized I could have written the line that writes the available lines to look for lines that start with a character other than &:

findstr "^[^&]" "%file%" >"%file%.available"
like image 21
dbenham Avatar answered Jan 24 '23 11:01

dbenham