Question

I'm using the PerlMonk example I found on: Reading and Writing Perl Config Files

Configuration.pl:

%CFG = (
    'servers' => {
        'SRV1' => {
            'IP'     => 99.32.4.0,
            'user'   => 'aname',
            'pswd'   => 'p4ssw0rd',
            'status' => 'unavailable'
        },
        'SRV2' => {
            'IP'     => 129.99.10.5
            'user'   => 'guest',
            'pswd'   => 'guest'
            'status' => 'unavailable'
        }
    },
    'timeout' => 60,
    'log' => {
        'file'  => '/var/log/my_log.log',
        'level' => 'warn',
    },
    'temp' => 'remove me'
);

It is working great, but the only issue is when reading and writing the HASH like configuration is being 'out of order'. Is there a way to keep it TIED?

This important since the configuration file will be also edited manually, so I want the keys and values in the same order.

Was it helpful?

Solution 2

If you use JSON then you have the advantage that your software is safe from a malicious attack (or perhaps accidental corruption). JSON also has a simpler syntax than Perl data structures, and it is easier to recover from syntax errors.

Setting the canonical option will create the data with the keys in sorted order, and so generate the same output for the same Perl data every time. If you need the data in a specific order other than alphabetical then you can use the Tie::IxHash module as @mpapec describes in his answer.

Alternatively you can use the sort_by method from the Pure Perl version of the module that lets you pass a collation subroutine. That would let you prescribe the order of your keys, and could be as simple as using a hash that relates all the possible key values with a numerical sort order.

This program uses the sort_by method to reconstruct the JSON in the same order as the keys appear in your original hash. That is unlikely to be the order you want, but the mechanism is there. It works by looking up each key in a hash table to determine how they should be ordered. Any keys (like SVR1 and SVR2 here) that don't appear in the hash are sorted in alphabetical order by default.

use strict;
use warnings;

use JSON::PP ();

my %CFG = (
  'servers' => {
    'SRV1' => {
      'IP'     => '99.32.4.0',
      'user'   => 'aname',
      'pswd'   => 'p4ssw0rd',
      'status' => 'unavailable'
    },
    'SRV2' => {
      'IP'     => '129.99.10.5',
      'user'   => 'guest',
      'pswd'   => 'guest',
      'status' => 'unavailable'
    }
  },
  'timeout' => 60,
  'log'     => {
    'file'  => '/var/log/my_log.log',
    'level' => 'warn',
  },
  'temp' => 'remove me'
);

my %sort_order;
my $n = 0;
$sort_order{$_} = ++$n for qw/ servers timeout log temp /;
$sort_order{$_} = ++$n for qw/ IP user pswd status /;
$sort_order{$_} = ++$n for qw/ file level /;

my $json = JSON::PP->new->pretty->sort_by(\&json_sort);
print $json->encode(\%CFG);

sub json_sort {
  my ($aa, $bb) = map $sort_order{$_}, $JSON::PP::a, $JSON::PP::b;
  $aa and $bb and $aa <=> $bb or $JSON::PP::a cmp $JSON::PP::b;
}

generates this output

{
   "servers" : {
      "SRV1" : {
         "IP" : "99.32.4.0",
         "user" : "aname",
         "pswd" : "p4ssw0rd",
         "status" : "unavailable"
      },
      "SRV2" : {
         "IP" : "129.99.10.5",
         "user" : "guest",
         "pswd" : "guest",
         "status" : "unavailable"
      }
   },
   "timeout" : 60,
   "log" : {
      "file" : "/var/log/my_log.log",
      "level" : "warn"
   },
   "temp" : "remove me"
}

which can simply be saved to a file and similarly restored.

OTHER TIPS

You could tie config variable before using it, so later hash keys will stay in same order as before,

use strict;
use warnings;

use Tie::IxHash;
tie my %CFG, 'Tie::IxHash';

%CFG = (
    'servers' => {
        'SRV1' => {
            'IP'     => '99.32.4.0',
            'user'   => 'aname',
            'pswd'   => 'p4ssw0rd',
            'status' => 'unavailable'
        },
        'SRV2' => {
            'IP'     => '129.99.10.5',
            'user'   => 'guest',
            'pswd'   => 'guest',
            'status' => 'unavailable'
        }
    },
    'timeout' => 60,
    'log' => {
        'file'  => '/var/log/my_log.log',
        'level' => 'warn',
    },
    'temp' => 'remove me'
);

use Data::Dumper;
print Dumper \%CFG;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top