Working with flat-file databases

Renegade's picture

He has: 3,022 posts

Joined: Oct 2002

I would like to learn how to use a flat-file database (because my computer can't support MySQL) I googled for tutorials but didn't have much lick with it. Could anyone direct me to one or two good ones? Or explain how to make one?

Mark Hensler's picture

He has: 4,048 posts

Joined: Aug 2000

The way I've done it in the past is use rows as records and a delimeter for fields. Similar to the passwd file on a linux box.

First 3 lines of my /etc/passed file...

root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/bin/sh
daemon:x:2:2:daemon:/sbin:/bin/sh
'Each row is a record. The colon (Smiling seperates the fields.

How do you read it? Basically...

Suck the entire file into memory. If possible, make each line in the file a seperate element in an array.

If not, then suck the file into a string, then explode on the newline character (\n) to create an array with each element being one of the lines in the file.

Array
  (
    [1] => root:x:0:0:root:/root:/bin/bash
    [2] => bin:x:1:1:bin:/bin:/bin/sh
    [3] => daemon:x:2:2:daemon:/sbin:/bin/sh
  )
'
Then iterate (loop) through the first array, and explode on the colorn (Smiling to create a second array of the fields for the current element in the first array.
Array
  (
    [1] => root
    [2] => x
    [3] => 0
    [4] => 0
    [5] => root
    [6] => /root
    [7] => /bin/bash
  )
'
Now its important for you to know the format of your file. Meaning, the order of your fields within each line. In this case, the fields are as follows:
1 - user
2 - password
3 - user id
4 - group id
5 - name, notes, or short description
6 - home directory
7 - shell

Now, you will want to identify the record (first array) you wish to read/modify by examing the fields (second array). So, lets say you want to edit the 'bin' user. Within the loop, you would test the first element (or field) in the second array against the string 'bin'. If you have a match, then this is record you wish to modify. If not, you continue with the iteration until you find a match or you reach the end of the array.

Mark Hensler
If there is no answer on Google, then there is no question.

Renegade's picture

He has: 3,022 posts

Joined: Oct 2002

So, when people say something is using a flat file database they really mean an array thats in an external file?

They arrays, they start with a 1? I thought they started with a 0?

Mark Hensler's picture

He has: 4,048 posts

Joined: Aug 2000

Quote: So, when people say something is using a flat file database they really mean an array thats in an external file?

No. I just decided to use arrays to examine the file.

A flat file could use fixed length fields. In which case, you'd be using substr(). (do you want to see an example of that?)

Quote: They arrays, they start with a 1? I thought they started with a 0?

They do. Sorry about that.

Mark Hensler
If there is no answer on Google, then there is no question.

Renegade's picture

He has: 3,022 posts

Joined: Oct 2002

Quote: Originally posted by Mark Hensler
No. I just decided to use arrays to examine the file.

A flat file could use fixed length fields. In which case, you'd be using substr(). (do you want to see an example of that?)

Yes please

Mark Hensler's picture

He has: 4,048 posts

Joined: Aug 2000

root      x         0    0    root                /root               /bin/bash     
bin       x         1    1    bin                 /bin                /bin/sh       
daemon    x         2    2    daemon              /sbin               /bin/sh       
'
This time, the fields are fixed length...
field 1 - 10 characters
field 2 - 10 characters
field 3 - 5 characters
field 4 - 5 characters
field 5 - 20 characters
field 6 - 20 characters
field 7 - 15 characters
(record - 85 characters + 1 newline character)

To read this file, you do not need to suck the whole file into memory. You may simply read 86 bytes at a time.

You don't really need to suck the other format discussed into memory. You could simply read the file one character at a time until you read a newline character (\n). But that can get tedious. I've never seen anyone handle a delimited file without sucking the file into memory first.

<?php
$fptr
= fopen(file, \"r\");
if (
$fptr) {
do {
   
$data = fread($fptr, 86);
    if (strlen(
$data) == 0)  break;

   
$username   = substr($data, 0, 10);
   
$password   = substr($data, 10, 10);
   
$userid     = substr($data, 20, 5);
   
$groupid    = substr($data, 25, 5);
   
$notes      = substr($data, 30, 20);
   
$home       = substr($data, 50, 20);
   
$shell      = substr($data, 70, 15);

    if (
$username != $the_record_to_edit) continue;

    // this is the record i'm interested in

    } while(true);
    fclose (
$handle);
}
?>

I just checked the PHP manual, and there is an fgets() funciton which reads up to X bytes of data or until EOL (end of line) or EOF (end of file) is reached. This would be usefull for reading delimited files one record at a time.

Mark Hensler
If there is no answer on Google, then there is no question.

Renegade's picture

He has: 3,022 posts

Joined: Oct 2002

To me, the first one with arrays look a bit better.

If you wanted to change bin:x:1:1:bin:/bin:/bin/sh to something like bin:x:2:1:bin:/bin:/bin/sh how would yo go about doing it? or if you wanted to delete it.

Mark Hensler's picture

He has: 4,048 posts

Joined: Aug 2000

I've only worked flat files from Perl...

# suck the flat file into an array
open (INFILE, "</path/to/flatfile") or die "Cannot open file for input: $!";
@database = <INFILE>;
close INFILE or die "Cannot close filename: $!";


open (OUTFILE, ">/path/to/flatfile") or die "Cannot open file for output: $!";

# loop through records
foreach $record (@database) {
    @fields = split(/:/, $record);

    if ($field{0} eq "bin") {
        # this is the record we want to modify/delete

        # if you want to delete this record,
        # simply do not print it to the OUTFILE

        $field{2} = 2;
        $record = join(':', @field);
    }

    print OUTFILE $record;

}

close OUTFILE or die "Cannot close filename: $!";
'

Mark Hensler
If there is no answer on Google, then there is no question.

They have: 461 posts

Joined: Jul 2003

<?php
foreach ($array as $value){
  if(
$value = 'bin:x:1:1:bin:/bin:/bin/sh'){
   
$value = 'bin:x:2:1:bin:/bin:/bin/sh';
  }
}
?>
to remove...
<?php
function remove_path($array, $string){
 
$newarray=array();
  foreach (
$array as $value){
    if(
$value != '$string'){
     
$newarray[]=$value;
    }
  }
  return
$newarray;
}
$paths=remove_path($paths, 'bin:x:1:1:bin:/bin:/bin/sh');
?>

POSIX. because a stable os that doesn't have memory leaks and isn't buggy is always good.

Mark Hensler's picture

He has: 4,048 posts

Joined: Aug 2000

That's too restrictive. You have to know what the whole record looks like. Not just which "primary field" you wish to work on.

Perl code converted to PHP...

<?php
// suck the flat file into an array
$database = file(\"/path/to/flatfile\", \"r+\");

$fp = fopen(\"/path/to/flatfile\", \"w\");
if (
$fp) {
    // loop through records
    foreach (
$database as $record) {
       
$fields = explode(\":\", $record);

        if (
$field[0] == \"bin\") {
            // this is the record we want to modify/delete

            // if you want to delete this record,
            // simply do not print it to the OUTFILE

           
$field[2] = 2;
           
$record = implode(':', $field);
        }

        fwrite(
$fp, $record);

    }

    fclose(
$fp);
}
?>

Mark Hensler
If there is no answer on Google, then there is no question.

Renegade's picture

He has: 3,022 posts

Joined: Oct 2002

Cool, thanks Mark Laughing out loud

Want to join the discussion? Create an account or log in if you already have one. Joining is fast, free and painless! We’ll even whisk you back here when you’ve finished.