Page 1 of 1

Sequential-file-based INDEXED file (random access) [DEMO]

Posted: 25 Apr 2014 04:47
by miskox
Hi all,

based on this thread viewtopic.php?p=33630#p33630 and with the help from Aacini's SetFilePointer.com I managed to make a working indexed file.

Indexed file contains a field marked as KEY (primary key, alternate keys are also possible).
So the rule here is it cannot be duplicated. Also NULL values are not allowed and usually a field length is determined (but not in this working example - many other checks should be added).

In my case I have a 'database' file (INDEXED FILE) with the following record description:

RECORD_OFFSET NUMERIC 9
DELIMETER CHAR 1 "+"
RECORD_LENGTH (withoud CR+LF) NUMERIC 3
DELIMETER CHAR 1 ";"
FIRSTNAME CHAR xx
DELIMETER CHAR 1 ";"
LASTNAME CHAR xx
DELIMETER CHAR 1 ";"
SERIALNUMBER NUMERIC 8
DELIMETER CHAR 1 ";"

Program expects a serial number as the key for the searches (though it could search for any info in the record).

Program allows READing data from the file, WRITING new records (append to the file), UPDATE/REWRITE the record. If the new rerord's length is different than the current record then current record is DELETEd (all spaces are written to the record) and the new record is added at the end of the file.

First here is the database.backup file (file's length is 726 bytes):

Code: Select all

RECOFFSET+LEN;FIRSTNAME;LASTNAME;SERIAL_NUMBER;
       49+ 37;Abigail;Jones;10000000;
       88+ 35;Brian;Right;10000001;
      125+ 34;Chuck;Left;10000002;
      161+ 36;Demi;Somebod;10000003;
      199+ 33;Erin;Hill;10000004;
      234+ 39;Frank;Overthere;10000005;
      275+ 38;Greta;Williams;10000006;
      315+ 34;Hank;Smith;10000007;
      351+ 35;Ian;Johnson;10000008;
      388+ 34;John;Brown;10000009;
      424+ 35;Kevin;White;10000010;
      461+ 34;Luca;Davis;10000011;
      497+ 36;Miles;Miller;10000012;
      535+ 36;Norah;Wilson;10000013;
      573+ 34;Owen;Moore;10000014;
      609+ 38;Patrick;Taylor;10000015;
      649+ 37;Rick;Anderson;10000016;
      688+ 36;Sean;Jackson;10000017;


And here is the main.cmd:

Code: Select all

@echo off

REM =======================================================================================================
REM                             INDEXED FILE based on a sequential file
REM
REM Setfilepointer.com: thanks to Aacini
REM This works only on 32-bit architecture because of setfilepointer.com being a 16-bit application.
REM
REM This is a working demo to demonstrate usage of indexed file in batch.
REM
REM =======================================================================================================

if not exist setfilepointer.com call :makesetfilepointercom

REM Copies backup version to the production version (for testing - should be deleted in production)
copy database.backup *.txt /y

set db_file_name=database.txt

:start
if exist x.x del x.x
cls
title MAIN MENU
echo.
echo                      READ/WRITE/UPDATE/DELETE records in
echo                      sequential-file-based  indexed file
echo.
echo                              1. READ
echo.
echo                              2. WRITE (APPEND)
echo.
echo                              3. UPDATE (REPLACE)
echo.
echo                              4. DELETE
echo.
echo.
echo                              0. exit
echo.
set your_choice=&rem

set /p "your_choice=Enter your choice: "
if "%your_choice%"=="1" call :READ
if "%your_choice%"=="2" call :WRITE
if "%your_choice%"=="3" call :UPDATE
if "%your_choice%"=="4" call :DELETE
if "%your_choice%"=="0" goto :EOF
goto :START

REM ========================================================================
REM                  READ
REM ========================================================================

:READ
cls
title READ
echo Reading database...
echo.
echo Serial numbers in the database: 10000000..10000017
echo.
call :ENTER_SERIAL_NUMBER
call :READ_RECORD %serial_number%

if "%record_length%"=="-1" echo Serial number %serial_number% does not exist.&&pause&&goto :EOF

call :DISPLAY_INFO

pause

goto :EOF

REM ========================================================================
REM                       WRITE
REM ========================================================================

:WRITE
cls
title WRITE
echo Writing (appending) to the database...
set firstname=&rem
set lastname=&rem

echo.
call :ENTER_SERIAL_NUMBER
call :READ_RECORD %serial_number%

if not "%record_offset%"=="-1" echo.&&echo Record with serial number %serial_number% already exists.&&echo.&&pause&&goto :EOF

set /p "firstname=Enter firstname: "
set /p "lastname=Enter lastname: "

set record_string=;%firstname%;%lastname%;%serial_number%;

call :strlen record_string str_len

set /a record_length=13 + %str_len%
for %%f in (%db_file_name%) do set fillen=%%~zf

set record_offset=          %fillen%
set record_offset=%record_offset:~-9%

set  record_length=   %record_length%
set  record_length=%record_length:~-3%

set record_string=%record_offset%+%record_length%;%firstname%;%lastname%;%serial_number%;
set record_offset=%fillen%

call :WRITE_RECORD

echo.
echo Record written.
echo.

pause

goto :EOF

REM ========================================================================
REM                UPDATE
REM ========================================================================

:UPDATE

title UPDATE (REPLACE)
cls
echo Updating database...
echo.
echo Serial numbers in the database: 10000000..10000017
echo.

call :ENTER_SERIAL_NUMBER
call :READ_RECORD %serial_number%

if "%record_length%"=="-1" echo Serial number %serial_number% does not exist.&&pause&&goto :EOF

set old_record_offset_and_length=%record_offset_and_length%
set old_record_offset=%record_offset%
set old_record_length=%record_length%

call :DISPLAY_INFO

echo.
echo Enter new data:
echo.
set /p "firstname=Enter firstname [%firstname%]: "
set /p "lastname=Enter lastname [%lastname%]: "
set /p "serial_number=Enter serial number [%serial_number%]: "

echo.
echo.
echo New First name    : %firstname%
echo New Last name     : %lastname%
echo New Serial number : %serial_number%
echo.
echo.

set record_string=;%firstname%;%lastname%;%serial_number%;

call :strlen record_string str_len

set /a record_length=13 + %str_len%

if not "%old_record_length%"=="%record_length%" (
   echo New record's length is different than the old record's length. Deleting ^(DELETE^) current record and adding new record ^(WRITE ^(APPEND^)^).

   set record_string=%old_record_offset_and_length%;                                                                                        &rem
   call set record_string=%%record_string:~0,%old_record_length%%%
   echo.
   call :WRITE_RECORD
   echo.
   for %%w in (%db_file_name%) do set record_offset=%%~zw
)

set record_offset=          %record_offset%
set record_offset=%record_offset:~-9%

set  record_length=   %record_length%
set  record_length=%record_length:~-3%

set record_string=%record_offset%+%record_length%;%firstname%;%lastname%;%serial_number%;
set /a record_offset=%record_offset%

call :WRITE_RECORD

echo.
echo.
echo.
echo.
echo Record updated.
echo.

pause

goto :EOF

REM =========================================================================================
REM                                      DELETE
REM =========================================================================================

:DELETE
title DELETE
cls
echo Deleting record...
echo.
echo Serial numbers in the database: 10000000..10000017
echo.

call :ENTER_SERIAL_NUMBER
call :READ_RECORD %serial_number%

if "%record_length%"=="-1" echo Serial number %serial_number% does not exist.&&pause&&goto :EOF

set record_string=%record_offset_and_length%;                                                                                        &rem
call set record_string=%%record_string:~0,%record_length%%%
echo.
call :WRITE_RECORD
echo.
echo Record %serial_number% deleted.
echo.
pause

goto :EOF

REM =========================================================================================
REM             ENTER SERIAL NUMBER
REM =========================================================================================
:ENTER_SERIAL_NUMBER

set serial_number=&rem
set /p "serial_number=Enter serial number: "

goto :EOF

REM =========================================================================================
REM            DISPLAY INFO
REM =========================================================================================
:DISPLAY_INFO

echo.
echo.
echo First name    : %firstname%
echo Last name     : %lastname%
echo Serial number : %serial_number%
echo.
echo.
goto :EOF


REM =========================================================================================
REM                      READ RECORD
REM =========================================================================================
:READ_RECORD
REM parameter1=serial number (index key)
set par1=%1

set firstname=&rem
set lastname=&rem
rem set serial_number=&rem

set record_offset=-1
set record_length=-1

>x.x (findstr /C:";%par1%;" %db_file_name%)||goto :EOF
for /f "tokens=1-4 delims=;" %%f in (x.x) do (
   for /f "tokens=1,2 delims=+" %%u in ("%%f") do (
      set    record_offset_and_length=%%u+%%v
      set /a record_offset=%%u
      set /a record_length=%%v
   )
   set firstname=%%g
   set lastname=%%h
   set serial_number=%%i
)

if exist x.x del x.x

goto :EOF

REM =========================================================================================
REM            WRITE RECORD
REM =========================================================================================

:WRITE_RECORD
set ccd=%cd%
(   
   setfilepointer.com 1 %record_offset%
   echo %record_string%
)>>%db_file_name%
cd %ccd%
goto :EOF

>>%db_file_name% (echo %record_offset%+%record_length%;%firstname%;%lastname%;%serial_number%;)

REM ==================================================================================

:strLen string len -- returns the length of a string
::                 -- string [in]  - variable name containing the string being measured for length
::                 -- len    [out] - variable to be used to return the string length
:: Many thanks to 'sowgtsoi', but also 'jeb' and 'amel27' dostips forum users helped making this short and efficient
:$created 20081122 :$changed 20101116 :$categories StringOperation
:$source http://www.dostips.com
(   SETLOCAL ENABLEDELAYEDEXPANSION
    set "str=A!%~1!"&rem keep the A up front to ensure we get the length and not the upper bound
                     rem it also avoids trouble in case of empty string
    set "len=0"
    for /L %%A in (12,-1,0) do (
        set /a "len|=1<<%%A"
        for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"
    )
)
( ENDLOCAL & REM RETURN VALUES
    IF "%~2" NEQ "" SET /a %~2=%len%
)
EXIT /b

:makesetfilepointercom
for %%b in (
"4D5343460000000020010000000000002C000000000000000301010001000000000000004F00000001000100DB000000"
"00000000000099447A64200073657466696C65706F696E7465722E636F6D003BB7D22FC900DB00434B33FABFB9B1FB77"
"F767A3B75DBEFF1FBB6E50F8F3795D895D57ECFF86DF0645660DBF2DCB0D1B5E1B043FD66EB055285503CA29F9A7191F"
"48333EB951BBE1A5629AF16D77B05AD712ED86DFA9A51ABDA5FF5FABBEAE74074A40CD2806999197F6FD21D098B4EFB7"
"D3B40FBF7EF8FAC6EB2769DD278C0EA4299F2C0DF977E0DF81061BD712EF069BD41237B7632EFF9DBA2F6E3404197FF9"
"65F416A7B38A456B1B6AFE3B952E870A3EEABE90F6FD565AF7299822A3032033402A8D0E6CF139ABF87AD1EBBDBC"
"5C00" ) Do >>setfilepointer.com (Echo.For b=1 To len^(%%b^) Step 2
Echo WScript.StdOut.Write Chr^(CByte^("&H"^&Mid^(%%b,b,2^)^)^) : Next)
Cscript /b /e:vbs setfilepointer.com > setfilepointer.co_
Expand -r setfilepointer.co_ setfilepointer.com>nul 2>&1
if exist setfilepointer.co_ del setfilepointer.co_
Goto :Eof


Of course the display of serials in the database is just for testing purposes so you know what to enter.

Saso

P.S.: Now we must nicely ask Aacini to make setfilepointer.com as a 32-bit application...

EDIT: 26-apr-2014 Some updates to the code.

Re: Sequential-file-based INDEXED file (random access) [DEMO

Posted: 25 Apr 2014 20:31
by Aacini
Yes. I was writting the description for the new 32-bits file pointer program when I saw your post, so I had to modify the mine a little. I called the new version FilePointer.exe to differentiate it from the old 16-bits .com one. The new version have a couple new features:

FilePointer.exe help wrote:Get or set the file pointer position of a redirected file handle.

FilePointer handle [position [/C|/E]]

If no /C nor /E options are given, move the file pointer to the 31-bits
absolute position given (files up to 2 GB size).

If /C option is given, move the file pointer relative to current position
(signed displacement).

If /E option is given, move the file pointer relative to end of file.
For example, to move FP to EOF: FilePointer handle 0 /E

When this program ends, it return the new FP position in ERRORLEVEL.
For example, to get the current FP position:
FilePointer handle 0 /C
set position=%errorlevel%

If the file pointer is moved beyond the End Of File and new bytes are written, the Win-32 API documentation specify that the bytes in between are not initialized, so they are random characters; however, it seems that in this case (Windows 8.1 cmd.exe) the bytes are initialized with spaces. This feature may be used to create a file up to 2 GB size filled with spaces.

In order to get the new FilePointer.exe auxiliary program, run the Batch file below for just one time:

Code: Select all

@echo off
setlocal EnableDelayedExpansion

echo Extracting FilePointer.exe, please wait...
call :ExtractBinaryFile FilePointer.exe
echo FilePointer.exe file created
goto :EOF


rem Extract Binary File from hexadecimal digits placed in a "resource" in this .bat file

:ExtractBinaryFile filename.ext
setlocal EnableDelayedExpansion
set "start="
set "end="
for /F "tokens=1,3 delims=:=>" %%a in ('findstr /N /B "</*resource" "%~F0"') do (
   if not defined start (
      if "%%~b" equ "%~1" set start=%%a
   ) else if not defined end set end=%%a
)
(for /F "skip=%start% tokens=1* delims=:" %%a in ('findstr /N "^" "%~F0"') do (
   if "%%a" == "%end%" goto decodeHexFile
   echo %%b
)) > "%~1.hex"
:decodeHexFile

rem Modified code based on :genchr subroutine
type nul > t.tmp
(for /F "usebackq" %%a in ("%~1.hex") do (
   set input=%%a
   set i=0
   for /L %%I in (0,2,120) do for %%i in (!i!) do if "!input:~%%i,1!" neq "" (
      set hex=!input:~%%i,2!
      set /A i+=2
      if "!hex:~0,1!" neq "[" (
         set /A chr=0x!hex!
         if not exist !chr!.chr call :genchr !chr!
         type !chr!.chr
      ) else (
         for /L %%J in (1,1,5) do for %%i in (!i!) do if "!input:~%%i,1!" neq "]" (
            set "hex=!hex!!input:~%%i,1!"
            set /A i+=1
         )
         if not exist 0.chr call :genchr 0
         for /L %%J in (1,1,!hex:~1!) do type 0.chr
         set /A i+=1
      )
   )
)) > "%~1"
del *.chr
del t.tmp temp.tmp
del "%~1.hex"

exit /B


:genchr
REM This code creates one single byte. Parameter: int
REM Teamwork of carlos, penpen, aGerman, dbenham
REM Tested under Win2000, XP, Win7, Win8
set "options=/d compress=off /d reserveperdatablocksize=26"
if %~1 neq 26 (
   makecab %options% /d reserveperfoldersize=%~1 t.tmp %~1.chr > nul
   type %~1.chr | ( (for /l %%N in (1,1,38) do pause)>nul & findstr "^" > temp.tmp )
   >nul copy /y temp.tmp /a %~1.chr /b
) else (
   copy /y nul + nul /a 26.chr /a >nul
)
exit /B


<resource id="FilePointer.exe">
4d5a900003[3]04[3]ffff[2]b8[7]40[35]b0[3]0e1fba0e00b409cd21b8014ccd21546869732070726f6772616d2063616e6e6f74206265207275
6e20696e20444f53206d6f64652e0d0d0a24[7]551e49c1117f2792117f2792117f27929f603492167f2792ed5f3592137f279252696368117f2792
[8]5045[2]4c01020022e75a53[8]e0000f010b01050c0002[3]02[7]10[3]10[3]20[4]40[2]10[3]02[2]04[7]04[8]30[3]02[6]03[5]10[2]10
[4]10[2]10[6]10[11]1420[2]28[84]20[2]14[27]2e74657874[3]f6[4]10[3]02[3]02[14]20[2]602e7264617461[2]a0[4]20[3]02[3]04[14]
40[2]40[8]e806[3]50e8d3[3]558bec83c4f8e89a[3]e8b9[3]0fb606460ac00f8484[3]2c30040af7d850e8b1[3]8945fce899[3]0fb606460ac0
746833db33ffb90a[3]3c2d7505470fb606463c30720d3c3977092c3093f7e103d8ebeb0bff7402f7dbba[4]e85e[3]8a06460ac074223c2f751e8a
063c4374043c637507ba01[3]eb0d3c4574043c657505ba02[3]526a0053ff75fce83f[3]c9c3cccccce83b[3]8bf08a06463c2275098a06463c2275
f9eb0c8a06463c20740484c075f54ec38a06463c2074f94ec3ccff250c204000ff2500204000ff2504204000ff25082040[267]5e20[2]6e20[2]80
20[2]5020[6]3c20[10]9220[3]20[22]5e20[2]6e20[2]8020[2]5020[6]9b004578697450726f63657373006a0147657453746448616e646c65[2]
850253657446696c65506f696e746572[2]e600476574436f6d6d616e644c696e6541006b65726e656c33322e646c6c[354]
</resource>

Below there is a short demo of FilePointer.exe capabilities, including the new ones:

Code: Select all

@echo off
setlocal EnableDelayedExpansion

rem FilePointer.exe test program
rem Antonio Perez Ayala

cls

echo Days.txt:
(
echo Sunday
echo Monday
echo Tuesday
echo Wednesday
echo Thursday
echo Friday
echo Saturday
) > Days.txt
type Days.txt
echo/

echo lines.dex:
(for /F "delims=:" %%a in ('findstr /O "^" Days.txt') do echo %%a) > lines.dex
type lines.dex
echo/

echo alpha.dex:
call :BubbleSort lines.dex < Days.txt > alpha.dex
type alpha.dex
echo/

echo DaysInAlphaOrder:
< Days.txt (
   for /F %%p in (alpha.dex) do (
      FilePointer 0 %%p
      set /P line=
      echo !line!
   )
)
echo/

echo Line 6 of Days.txt modified:
(
   FilePointer 1 46
   echo Line 6
) >> Days.txt
type Days.txt
echo/

rem Get lengths of each line
set n=0
set "lastPos="
for /F %%a in (lines.dex) do (
   if defined lastPos (
      set /A n+=1
      set /A lineLen[!n!]=%%a-lastPos
   )
   set lastPos=%%a
)
set /A n+=1
for %%a in (Days.txt) do set /A lineLen[%n%]=%%~Za-lastPos

rem New features of FilePointer.exe

echo Days in backwards order:
< Days.txt (
   rem Move FP to End Of File
   FilePointer 0 0 /E
   for /L %%i in (%n%,-1,1) do (
      rem Move FP backwards from current position
      FilePointer 0 -!lineLen[%%i]! /C
      set lastPos=!errorlevel!
      set /P line=
      echo %%i- !line!
      FilePointer 0 !lastPos!
   )
)
echo/

echo Insert 240 spaces after EOF:
(
   FilePointer 1 240 /E
   echo Previous 240 bytes are spaces
) >> Days.txt
type Days.txt
del Days.txt

goto :EOF


:BubbleSort baseIndex < inputFile > resultIndex
setlocal EnableDelayedExpansion
set n=0
for /F %%p in (%1) do (
   set /A n+=1
   set linePos[!n!]=%%p
)
:bubblePass
   set anyChange=
   set one=1
   FilePointer 0 !linePos[1]!
   set /P line1=
   for /L %%i in (2,1,%n%) do (
      FilePointer 0 !linePos[%%i]!
      set /P line2=
      if "!line1!" gtr "!line2!" (
         set /A temp=linePos[!one!], linePos[!one!]=linePos[%%i], linePos[%%i]=temp
         set "line2=!line1!"
         set anyChange=True
      )
      set /A one+=1
      set "line1=!line2!"
   )
if defined anyChange goto bubblePass
for /L %%i in (1,1,%n%) do echo !linePos[%%i]!
exit /B

You may also review a basic Relational Data Base example at this post.

Below there is a Batch file example that allows a very efficient maintenance of a text file up to 2 GB size (I'm sorry miskox, but this code was ready when I saw your post :| ).

Code: Select all

@if (@CodeSection == @Batch) @then


@echo off
setlocal EnableDelayedExpansion

rem General method to update a text file up to 2 GB size giving these rules:

rem - Records (lines) are of variable length.
rem - The first comma-separated field is an unique key; the rest of the record may have any format.
rem - Deleted records have 0 as key followed by the record length.
rem - Records may have filling spaces at end.

if not exist theFile.txt break > theFile.txt

:loop
echo/
echo 1- List records
echo 2- Add new record
echo 3- Modify a record
echo 4- Delete a record
echo 0- End
echo/
choice /C 12340 /N /M "Select the option: "
echo/
if errorlevel 5 goto :EOF
call :option-%errorlevel%
goto loop


:option-1  List records
echo =======================================
rem List all records, excepting deleted ones
REM findstr /B /V "0," theFile.txt
rem For this demo, list all records including deleted ones
type theFile.txt
echo =======================================
exit /B


:option-2  Add new record
set "newRecord="
set /P "newRecord=Enter new record (key,contents): "
if not defined newRecord goto option-2

:addRec
rem Look for the smallest deleted record with enough space for the new one
call :StrLen newRecord newRecLen=
set /A "lastRecLen=0x7FFFFFFF"
set "recPos="
for /F "tokens=1,3 delims=:," %%a in ('findstr /B /O "0," theFile.txt') do (
   if %%b geq %newRecLen% if %%b lss !lastRecLen! (
      set /A "recPos=%%a, lastRecLen=%%b"
   )
)
if not defined recPos goto appendRec

rem Use a deleted record for the new one
set /A filler=lastRecLen-newRecLen
for /L %%i in (1,1,%filler%) do set "newRecord=!newRecord! "
(
   FilePointer 1 %recPos%
   echo %newRecord%
) >> theFile.txt
exit /B

:appendRec
rem Add the new record at end of file
echo %newRecord%>> theFile.txt
exit /B


:option-3  Modify a record
set /P "keyRecord=Enter the key of the record to modify: "
set "recPos="
for /F "tokens=1* delims=:" %%a in ('findstr /B /O "%keyRecord%," theFile.txt') do set "recPos=%%a" & set "Record=%%b"
if defined recPos goto modifyRec
echo ERROR: No such key exists
echo/
exit /B

:modifyRec
call :deleteRec
Cscript //nologo //E:JScript "%~F0" "%Record%{ENTER}"
set /P "=Old: "
REM ECHO Old: %Record%
Cscript //nologo //E:JScript "%~F0" "{UP}"
set /P "newRecord=New: "
goto addRec


:option-4  Delete a record
set /P "keyRecord=Enter the key of the record to delete: "
set "recPos="
for /F "tokens=1* delims=:" %%a in ('findstr /B /O "%keyRecord%," theFile.txt') do set "recPos=%%a" & set "Record=%%b"
if defined recPos goto deleteRec
echo ERROR: No such key exists
echo/
exit /B

:deleteRec
call :StrLen Record RecLen=
(
   FilePointer 1 %recPos%
   set /P "=0,%RecLen%," < NUL
) >> theFile.txt
exit /B


:StrLen string result=
setlocal EnableDelayedExpansion
set "str=0!%1!"
set len=0
for /L %%A in (12,-1,0) do (
   set /A "len|=1<<%%A"
   for %%B in (!len!) do if "!str:~%%B,1!" == "" set /A "len&=~1<<%%A"
)
(endlocal & set "%2=%len%")
exit /B


@end


WScript.CreateObject("WScript.Shell").SendKeys(WScript.Arguments(0));

Previous program requires modifications in the presentation and in the management of the record keys, but the important point here is the maintenance of records. You must note these points of previous program:

  • It is not necessary to store the position of the records in the file because FINDSTR /O command give the right offset.
  • Deleted records are reused when possible, so the space wasted in deleted records is keep at a minimum.
  • The use of JScript's SendKeys function allows to use the command-line edition keys when a record is being modified.

Antonio

Re: Sequential-file-based INDEXED file (random access) [DEMO

Posted: 25 Apr 2014 20:48
by Squashman
This might come in handy once I read through the code and understand it. I deal with very large text files on a daily basis. Not sure what I would use it for yet but I am sure I will find something.

Re: Sequential-file-based INDEXED file (random access) [DEMO

Posted: 26 Apr 2014 03:29
by miskox
Thanks Antonio. Now I have to make another version.

The

Code: Select all

set ccd=%cd%


and

Code: Select all

cd %ccd%


in my code are just to get back the LFN directory names. So this will not be needed anymore when I use filepointer.exe. (Thanks again Aacini).

I used to work with INDEXED files in COBOL and DCL on OpenVMS. They are very handy. Also COBOL has a START command: this sets the pointer to the first record according to the rules (key value etc.) and then sequential reads are executed. With setfilepointer.com or filepointer.exe this can also (probably?*) be achieved: we set the file pointer to the desired location and then perform reads via FOR /F - this should be tested of course.

Saso

* probably because setfilepointer.com/.exe must be within the same command block so the FOR command must be in the same command block?