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.