Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Update config file parameters

by pavan.gup (Initiate)
on Jan 05, 2016 at 11:39 UTC ( [id://1151942]=perlquestion: print w/replies, xml ) Need Help??

pavan.gup has asked for the wisdom of the Perl Monks concerning the following question:

Hello,

I have a configuration file in the following format:

DEVICE=eth0 ONBOOT=yes BOOTPROTO=static TYPE=Ethernet IPADDR=10.9.0.200 NETMASK=255.255.0.0 GATEWAY=10.9.1.254

Some of the parameters above need to update. E.g. IPADDR needs to be updated to say 1.2.3.4. I have written a function that reads the entire file:

sub read_file { local $/ = undef; open FILE, $_[0] or die "Couldn't open file: $!"; $_[1] = <FILE>; close FILE; }

I call it read_file("config.txt", $file_content); Now $file_content has the entire file in form of string. I don't have option to install packages so need to use standard Perl. Can anyone please help in coming up with perl syntax that would update the config file parameters in the string. I can then write the string back to the file.

Replies are listed 'Best First'.
Re: Update config file parameters
by Athanasius (Archbishop) on Jan 05, 2016 at 13:17 UTC

    Hello pavan.gup, and welcome to the Monastery!

    The Tie::File module makes this sort of task even easier. Don’t worry, you don’t have to install it — it’s a core module, so if you have Perl, you already have Tie::File:

    #! perl use strict; use warnings; use Tie::File; my $filename = 'config.txt'; tie my @array, 'Tie::File', $filename or die "Cannot tie file '$filename': $!"; for my $line (@array) { my @fields = split /=/, $line; if ($fields[0] eq 'IPADDR') { $fields[1] = '1.2.3.4'; $line = join '=', @fields; last; } } untie @array;

    — and the configuration file has been updated!

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Re: Update config file parameters
by hdb (Monsignor) on Jan 05, 2016 at 14:46 UTC

    Assuming order is not important and there are no duplicates I would read the file into a hash and then change the values of interest:

    use strict; use warnings; my %config = map { /(.*)=(.*)/ } <DATA>; $config{'IPADDR'} = '1.2.3.4'; print "$_=$config{$_}\n" for keys %config; __DATA__ DEVICE=eth0 ONBOOT=yes BOOTPROTO=static TYPE=Ethernet IPADDR=10.9.0.200 NETMASK=255.255.0.0 GATEWAY=10.9.1.254

      This has slight superiority over the current best idea in that, if the tag does not yet exist, this will create it.

      But God demonstrates His own love toward us, in that while we were yet sinners, Christ died for us. Romans 5:8 (NASB)

Re: Update config file parameters
by u65 (Chaplain) on Jan 05, 2016 at 11:59 UTC

    Welcome to the monastery!

    First of all, you should look at perlbrew which allows you to easily use whatever version of Perl 5 you want.

    Second, I would simply read the file line by line, split on the equal sign, change the value for that key as desired, and rewrite the pair to the file. You could either copy the original file to a temp file for the reads, or write to a temp file and rename it to the original name after closing it.

Re: Update config file parameters
by Anonymous Monk on Jan 05, 2016 at 13:53 UTC
    If you're willing to copy/paste an off-the-cuff answer from perlmonks, perhaps you'd be willing to copy/paste or even (gasp) study a tried & true solution from a little site called CPAN.

    Yeh, you're right. That's just crazy talk.

    TJD

Re: Update config file parameters
by 7stud (Deacon) on Jan 07, 2016 at 05:59 UTC

    You can do inplace editing of files:

    use strict; use warnings; use 5.020; my %new_values = ( ONBOOT => 'no', TYPE => 'Wifi', ); my $file_name = "data.txt"; read_file($file_name, \%new_values); sub read_file { my ($fname, $new_values_href) = @_; local $^I = ".bak"; #Enable inplace editing for this block only. local @ARGV = $fname; #Set value for @ARGV for this block only. while (my $line = <>) { my ($name, $val) = split /=/, $line; my $replacement_value = $new_values_href->{$name}; if (defined $replacement_value) { say "$name=$replacement_value"; #Output goes to file } else { print $line; #Output goes to file } } } #Restore $^I and @ARGV to the values they had before the block

    To get inplace editing, you cannot read a file like this:

    while(my $line = $INFILE)

    You have to read a file using the diamond operator: <>. You cannot have anything between the angle brackets. The diamond operator reads from @ARGV (or STDIN if @ARGV is empty). So, you either have to set @ARGV to the file name, or enter the file name on the command line. @ARGV contains all the arguments entered on the command line.

    When you enable inplace editing, perl creates a new file, redirects STDOUT to the new file, i.e. print() and say() go to the new file; and when you are done, perl saves a copy of the original file using the original file name with a .bak extension, then perl renames the new file to the original file name. If you don't want to save a copy of the original file, then write:

    { local $^I = ""; ... ... }

    Here's a sample run:

    ~/pperl_programs$ cat data.txt DEVICE=eth0 ONBOOT=yes BOOTPROTO=static TYPE=Ethernet IPADDR=10.9.0.200 NETMASK=255.255.0.0 GATEWAY=10.9.1.254 ~/pperl_programs$ perl 1.pl ~/pperl_programs$ cat data.txt DEVICE=eth0 ONBOOT=no BOOTPROTO=static TYPE=Wifi IPADDR=10.9.0.200 NETMASK=255.255.0.0 GATEWAY=10.9.1.254
      You can do inplace editing of files:

      But that introduces a race condition. The problem is documented in perlrun:

      -i[extension]
      specifies that files processed by the <> construct are to be edited in-place. It does this by renaming the input file, opening the output file by the original name, and selecting that output file as the default for print() statements.

      The race: Instance 1 is started, reads the configuration file, then starts rewriting by renaming the configuration file and creating a new, still empty (or partly written) configuration file. Instance 2 is started, and reads an empty (or partly written) configuration file.

      How to get rid of that problem:

      • Use a (separate) lock file to synchronise access to the configuration file. Only the instance that can successfully lock the lock file may read or write the configuration file.
      • Use an atomic operation to change the configuration file. The only way I know is to rewrite the configuration file to a new, temporary file, and to rename that file to the original configuration file name.

      (Note that safely creating temporary files is another problem: mkstemp(3) and tmpfile(3) should be safe. mktemp(3) and tmpnam(3) aren't. File::Temp has safe and unsafe functions.)

      Alexander

      (And yes, the problem of "disappearing configuration data" has bitten me once, and it was quite hard to find.)

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
Re: Update config file parameters
by dbuckhal (Chaplain) on Jan 06, 2016 at 22:37 UTC

    I understand that your config file is probably plain text, but here's an option using Data::Dumper and require.

    Start with a configuration format, like a HASH:
    perl -MData::Dumper -e ' $conf = { DEVICE => 'eth0', ONBOOT => 'yes', BOOTPROTO => 'static', TYPE => 'Ethernet', IPADDR => '10.9.0.200', NETMASK => '255.255.0.0', GATEWAY => '10.9.1.254', }; open( $fh, ">", "monkConfig.txt" ) or die; print $fh Data::Dumper->Dump([$conf],["config"]); '
    Creates a file with this format:
    $config = { 'TYPE' => 'Ethernet', 'ONBOOT' => 'yes', 'NETMASK' => 255.255.0.0, 'GATEWAY' => 10.9.1.254, 'DEVICE' => 'eth0', 'IPADDR' => 10.9.0.200, 'BOOTPROTO' => 'static' };
    Then use require to access the data:
    perl -le 'require "monkConfig.txt"; print $config->{'TYPE'}; ' __output__ Ethernet
    ...then redo the Dump to write changes back to file. Yeah, probably a bit more tedious then Tie::File, but imagine the possibilities!
      here's an option using Data::Dumper and require

      Please DON'T use this "solution"!

      This opens a HUGE vulnerability, simply because require $filename;, use $filename;, do $filename;, and eval $filecontent; all treat the configuration file as executable code.

      See also Re^2: conf file in Perl syntax, Re^2: Storing state of execution.

      Or just imagine someone successfully executing echo 'system "/bin/rm -rf /";' >> monkConfig.txt before root executes perl -le 'require "monkConfig.txt";'.

      Also, use and require load each file only once, unless you start messing with %INC.

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

        Wow, I did not know require would execute the content of the file.

        Very good to know. I am glad you found my post and corrected it.

        Thanks!

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1151942]
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (3)
As of 2024-04-26 06:34 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found