Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

Re: Yet another config file editing programme : Tell me how to make it better !

by dazz (Beadle)
on Sep 03, 2021 at 00:55 UTC ( #11136390=note: print w/replies, xml ) Need Help??


in reply to Yet another config file editing programme : Tell me how to make it better !

Hi OK some good feedback there. I appreciate the efforts.
Just some feedback on some of the points.
I usually write the comments first then write the code to do what the comments say. When I go back to my code long after I have forgotten what I wrote, I read my comments to figure out what is doing.
I started using Switch, then I read in CPAN "do not use if you can use given/when". Then when I used given/when, I ended up with warnings plus advice not to use given/when. Hmmmmmm. As an occasional perl user, that is a little frustrating rewriting the same section of code 3x to do exactly the same thing. I just need something that has case type functionality.
My program is running on a Raspberry Pi. There is a very small, but finite risk of the Pi being turned off in the middle of a file write. Tie::File looked like it would minimise, but not eliminate, the risk of file corruption due to power loss. I have no idea which CPAN modules are commonly, or infrequently used. How could I know??
I was actually expecting to be told that I should have used CPAN XXX module, written specifically to do the sort of thing I am doing. Editing config files is a common requirement. I know there are modules for ini files, but apparently not for the dhcpcd.conf type of file that has sections, but not really marked as such.
The major flaw with my code is the risk that one of the parameters is deleted from the config file before my code is run, causing the script to keep looking in the following sections for the missing parameter. The easiest workaround is to place the section last in the file so the search will hit EOF as the section end marker.
I accept that my code writing is far from perfect, and I will be going through my code to make improvements and corrections based on the advice received. If I haven't mentioned your feedback above, it is not because I have ignored it.
Thanks.

Dazz
  • Comment on Re: Yet another config file editing programme : Tell me how to make it better !

Replies are listed 'Best First'.
Re^2: Yet another config file editing programme : Tell me how to make it better !
by eyepopslikeamosquito (Bishop) on Sep 03, 2021 at 09:39 UTC

    I started using Switch, then I read in CPAN "do not use if you can use given/when". Then when I used given/when, I ended up with warnings plus advice not to use given/when. Hmmmmmm. As an occasional perl user, that is a little frustrating rewriting the same section of code 3x to do exactly the same thing. I just need something that has case type functionality.

    I can certainly understand your frustration. This was a very sad affair for the Perl community. Though adding smart match into Perl was premature, and had to be backed out, at least P5P learned a valuable lesson.

    Not wanting to gloat (no, really) but this didn't affect me, at all, because I've never been a fan of switch. Stronger, I've almost never used Switch in over 20 years of coding in C++ and Perl and always queried its use during code reviews. Though it's a bit extreme to call Switch a code smell, cleaner alternatives, such as lookup-tables (hashes in Perl) and polymorphism (in OO languages, such as C++ and Java) should be preferred.

    From Perl Best Practices I suggest you take a look at the Control Structures chapter, especially:

    • 6.16 Value Switches - Use table look-up in preference to cascaded equality tests (item 78)
    • 6.17 Tabular Ternaries - When producing a value, use tabular ternaries (item 79)

    Though my advice in Perl is usually "just use a hash", as a last resort you could replace your switch with an if-elsif-elsif-else construct. See also:

Re^2: Yet another config file editing programme : Tell me how to make it better !
by eyepopslikeamosquito (Bishop) on Sep 03, 2021 at 01:25 UTC

    There is a very small, but finite risk of the Pi being turned off in the middle of a file write
    Ha ha, I have long experience contemplating this annoying and tricky problem! The straightforward solution I concocted back in 2003 (and am still happy with) is to simply write a new file on the same file system ... and then use (atomic) rename to clobber the original file - but only after the new file has been successfully written. This is described in detail at:

      See ->spew (and ->edit and ->edit_lines) in Path::Tiny

        From Path::Tiny's doco of its excellently named spew method: "The file is written to a temporary file in the same directory, then renamed over the original.". Hey, xdg stole my idea! Seriously, it looks like an excellent module, I should start using it ... especially given my old golfing buddy endorses it. :)

        Hi
        I am going to use Path::Tiny.

        Dazz
Re^2: Yet another config file editing programme : Tell me how to make it better !
by tybalt89 (Prior) on Sep 03, 2021 at 09:41 UTC

    Solution using Path::Tiny which writes the new file alongside the existing file, then does an atomic rename. See Re^2: Yet another config file editing programme : Tell me how to make it better ! and Re^3: Yet another config file editing programme : Tell me how to make it better !

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11136353 use warnings; use Path::Tiny; my $dhcpcdfile = 'fake.353'; # FIXME filename -w $dhcpcdfile or die "cannot write $dhcpcdfile"; my %ip_params = ( interface => "usb0", ip_address => "1.1.1.0", routers => "127.0.0.0", domain_servers => "1.1.1.1" ); %ip_params = ( %ip_params, # add new data to defaults path('inputfile.353')->slurp =~ /^(\w+)=(.*?)\s*$/gm ); # FIXME file +name my $foundinterface = 0; path( $dhcpcdfile )->edit_lines( sub { if( /^\s*profile\s+static_$ip_params{interface}\b.*\n/m ) # alter th +is section { $foundinterface = 1; } elsif( $foundinterface and /^\s*static/m ) { s/^\s*static\s+(\w+)=\K.*/$ip_params{$1}/m or die "failed to chang +e $1"; } elsif( $foundinterface ) { $foundinterface = 0; } } );
Re^2: Yet another config file editing programme : Tell me how to make it better !
by hippo (Bishop) on Sep 03, 2021 at 09:43 UTC
    I started using Switch, then I read in CPAN "do not use if you can use given/when". Then when I used given/when, I ended up with warnings plus advice not to use given/when. Hmmmmmm. As an occasional perl user, that is a little frustrating rewriting the same section of code 3x to do exactly the same thing.

    I completely agree and understand. The (mis-)management of the switch/case equivalent in Perl over the years has become something of a lesson in how not to do it. given/when never struck me as a good idea so I just avoided it but plenty succumbed. Similarly, I never went anywhere near smartmatch but only a minority were enticed into that one. FWIW the current guidance is here. It will probably change again.

    Sometimes being behind the curve is a good thing. These days I usually write Perl which is something like 5 years behind the current release in terms of features. This gives enough lead-in to be able to ascertain which "new" features have turned out to be turkeys and should be avoided. Such an approach is not for everyone, of course, but it can have its advantages (not least of which is back-compatibility).


    🦛

Re^2: Yet another config file editing programme : Tell me how to make it better !
by tybalt89 (Prior) on Sep 03, 2021 at 17:49 UTC

    Readability and brevity - together again !

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11136353 use warnings; use Path::Tiny; my $dhcpcdfile = 'fake.353'; # FIXME filename my %ip_params = ( # defaults interface => "usb0", ip_address => "1.1.1.0", routers => "127.0.0.0", domain_servers => "1.1.1.1" ); %ip_params = ( %ip_params, # add new data to defaults path('inputfile.353')->slurp =~ /^(\w+)=(.*?)\s*$/gm ); # FIXME file +name { # block for local local $/ = ''; # paragraph mode path( $dhcpcdfile )->edit_lines( sub { if( /^\s*profile\s+static_$ip_params{interface}\b.*\n/m ) # alter th +is section { s/^\s*static\s+(\w+)=\K.*/$ip_params{$1}/gm; } } ); }
      { # block for local local $/ = ''; # paragraph mode path( $dhcpcdfile )->edit_lines( sub { if( /^\s*profile\s+static_$ip_params{interface}\b.*\n/m ) # alter th +is section { s/^\s*static\s+(\w+)=\K.*/$ip_params{$1}/gm; } } ); }
      To an experienced perl programmer with deep knowledge of the language, this might look readable but for an occasional unprofessional perl user like myself, it would probably take at least half an hour to figure out what it is doing.
      If I came back to this sort of code in a couple of years to alter/reuse it, I'd be back to square one.
      If I had a future application that required a different input, this sort of all-in-one read/write approach would be difficult for me to repurpose.
      It seems that brevity and obfuscation in perl code are inseparable.

      For me, readability and brevity would be discovering a module Unix::ConfigFile::DHCPCD, that included a method "UpdateInterfaceIP". I think that a valid metric for a modern language is the code I don't have to write.

      Please note I am not criticizing the skill or helpfulness those that give up their own time to write replies to people like me. I don't want to sound ungrateful. This is my goto place to find expert advice on perl, but claiming code is "readable" on a website that has a section devoted to "Obfuscation" is not a good look.


      Dazz

        dazz, let me preface my reply by quoting Larry Wall from Programming Perl.

        You can use Perl however you see fit ... People feel like they can be creative in Perl because they have freedom of expression: they get to choose what to optimize for, whether that's computer speed or programmer speed, verbosity or concisenness, readability or maintainability or reusability or portability or learnability or teachability. You can even optimize for obscurity, if you're entering an Obfuscated Perl Contest. Any level of language proficiency is acceptable in Perl culture. We won't send the language police after you. A Perl script is "correct" if it gets the job done before your boss fires you.

        You've made it clear you're an occasional Perl programmer who values readability highly. I'm further assuming that you code Perl alone (rather than in a team) and that you're trying to "get your job done before your boss fires you". Is that right? I'm fine with that BTW.

        As I'm sure you're aware, the monks who responded to your thread probably use Perl in a very different work environment and participate here for different reasons. You may have noticed, for example, the light-hearted tone tybalt89 used around readability vs brevity. This is because he's renowned around these parts for writing very clever and very terse Perl code. He will comment further if he wishes, but I'm guessing he participates here mainly for enjoyment ... and he really enjoys writing clever and terse code!

        As you might have guessed, I value clean and efficient code at work, code that must be maintained by teams of many different programmers over periods of many years ... while also enjoying recreational Perl (e.g. obfu and golf) as a pastime ... hmmmm, maybe I have a personality disorder. :) The many links at my home node will give you more detail, in case you're interested.

        I confess I pulled a face the instant I set eyes on your:

        if ( $isFoundInterface == true )
        Though this is "readable" to you (and I'm fine with that, TMTOWTDI is part of Perl culture), if anyone in my team presented this at a code review meeting, we'd all be checking our phones to see if it was April the first. That is because in my environment, code must be maintained for many years by many different programmers, so we need to stick to the programming mainstream, individualistic eccentricities like this would never pass code review.

        Update: Put another way, in my work environment, Maintainability is more important than Readability (see Readability vs Maintainability).

        It seems that brevity and obfuscation in perl code are inseparable.

        Not at all. Obfuscation and abbreviation are different concepts.

        There is code in here and out there which is highly obfuscated but quite long, perhaps the most famous example being camel code. OTOH, there is perfectly readable code of unsurpassed succinctness and brevity, on CPAN as well as lurking here in the dungeons.

        Problem is, brevity encapsulates concepts, quite like in mathematics. The concepts behind a Ricci tensor or a Christoffel symbol easily fill books. But these weren't invented for obfuscation, but abbreviation. You need to know the symbols, their meanings and the operations they allow in order to calculate with these high order concepts.

        One perl builtin construct is a fine example - the diamond operator. Consider (taken from perlop):

        while (<>) { # do something with $_ here }

        This is equivalent to

        unshift(@ARGV, '-') unless @ARGV; while ($ARGV = shift) { open(ARGV, $ARGV); while ($_ = readline(<ARGV>)) { # do something with $_ } }

        So, the bare while(<>) { ... } is not an obfuscation, but an abbreviation for all the operations of its verbose version.

        Or consider the following pieces of code, which do exactly the same thing:

        %hash = (); { my @keys = qw ( a, b, c); my @values = 1..3; for ( my $c = 0; $c <= $#keys; $c++ ) { $hash { $keys [$c] } = $values [ $c ]; } }
        @hash { qw (a, b, c) } = 1..3;

        For me, readability and brevity would be discovering a module Unix::ConfigFile::DHCPCD, that included a method "UpdateInterfaceIP". I think that a valid metric for a modern language is the code I don't have to write.

        Your expectation does not address readability and brevity, but language adoption and laziness. Perl does a very good job in doing as much for you as possible, as does CPAN, but it cannot by itself unite all different flavors of e.g. dhcp client configuration into a standard module. The maintainer of such a module would inherit all the technological debt of dhcp implementors. There are folks who try to do that, see https://www.webmin.com/. Download the https://download.webmin.com/download/modules/dhcpd.wbm.gz (which is a tar.gz) file, unpack it, and see what is needed for just the ISC DHCPD on various platforms. AFAIK they don't have support for DHCPCD client config, but feel free to submit a module.

        It is the other way round: if all OS/platform vendors and DHCP implementors were to agree on perl as a standard configuration language, they would ship a Unix::ConfigFile::DHCPCD package which implements the method UpdateInterfaceIP whose body you would never look at, and wouldn't care about readability and brevity of its implementation, at all. But this ship has sailed far away and long ago.

        Last point -

        To an experienced perl programmer with deep knowledge of the language, this might look readable but for an occasional unprofessional perl user like myself, it would probably take at least half an hour to figure out what it is doing.
        If I came back to this sort of code in a couple of years to alter/reuse it, I'd be back to square one.

        Only use constructs you will never forget, and try to never forget what constructs you use.

        Perl helps you with this, e.g. with the /x modifier for regular expressions. You could rewrite the tybalt89 code as

        { # block for local local $/ = ''; # paragraph mode path( $dhcpcdfile )->edit_lines( sub { if( m/ # match this: ^\s* # zero or more whitespace cha +rs at line begin profile # followed by the word "profi +le" \s+ # one or more whitespace static_ # then "static_" $ip_params{interface} # the value for key "interfac +e" in %ip_params \b # a word boundary .* # zero or more following char +s \n # and a newline /mx # in a multiline block ) { # alter this section s/ # match and substitute ^\s* # zero or more whitespace cha +rs at line begin static # word "static" \s+ # one or more whitespace char +s (\w+) # a word (see perlre) capture +d in $1 = # equal sign \K # but keep what was matched s +o far .* # any following chars / # against $ip_params{$1} # the value of key $1 (see ab +ove) in hash %ip_params /gmx; # globally in a multiline blo +ck (for "x" see perlre) } # endif } # end of sub ); } # end of "local $/" block

        to explain what is meant today to the dumb-ass you'll be tomorrow. Just comment anything you might have forgotten later.

        perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
        Hi
        I have working at writing better perl based on the advice received here but I am stuck.
        I slurp in the configuration file I want to update OK.
        I load it to ARGV to run it through a while( <> ) loop OK.
        I search and edit the file line by line OK.
        When I try and spew it, the file is blank.

        The code sample below is not complete, and probably not working.
        I have stripped out everything that I don't think is relevant to the slurp/process/spew problem.
        I am wondering if the explicit use of $line is the problem??
        I have tried removing all references to $line but no change. The output file is still blank.
        #!/usr/bin/perl use strict; use warnings; use Path::Tiny; ###### Output dhcp configuration file # my $dhcpcdfile = '/etc/dhcpcd.conf'; my $dhcpcdfile = 'd.conf'; # TEST ####################################### Load the output config file path($dhcpcdfile)->slurp; ### ####################################### { # block to limit scope @ARGV = $dhcpcdfile; while( my $line = <> ) { ### Is the explicit use of $line the pro +blem ??? my @ip_fields = split( /=/,$line); # look for profile with matching interface name if ( $line =~ /^\s*profile\s+static_$ip_params{interface}\b.*\n/m + ) { # format matches 'profile static_eth0' } elsif( $foundinterface and $line =~ /^.*static\s*ip_address=/ ) { $line = "static ip_address=$ip_params{'ip_address'}\n"; } elsif( $foundinterface ){ last; # No need to continue looking through the file } } ########################################### path('spew.cfg')->spew(@ARGV); ### This saves a blank file ########################################### }


        Dazz

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://11136390]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others avoiding work at the Monastery: (4)
As of 2022-08-11 05:22 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?