Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

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

by dazz (Beadle)
on Sep 04, 2021 at 00:23 UTC ( [id://11136429]=note: print w/replies, xml ) Need Help??


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

{ # 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
  • Comment on Re^3: Yet another config file editing programme : Tell me how to make it better !
  • Download Code

Replies are listed 'Best First'.
Re^4: Yet another config file editing programme : Tell me how to make it better !
by eyepopslikeamosquito (Archbishop) on Sep 04, 2021 at 07:28 UTC

    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).

      Hi I am not a professional programmer. I don't work with or for any professional programmers (does it show???). I am merely an engineer whose toolbox includes some software languages. The thing that attracted me to Perl was the extensive library of modules. Lego blocks that I could join together to get things done. Not a new concept. I was using Coral66 back in the 1980's.
      I thought that perl would be the ideal tool to do something as simple as copy ip settings from one file to another. What I found was two broken case type constructs that seem to have remained broken for at least a decade? with no fix in sight. A Unix config module that goes half way to solving a problem, but like a hammer with no handle, is not much use to anyone. Really clever code that needs a Rosetta stone to translate.
      Of course only a bad craftsman blames his tools but a good tool should enable simple things to be done simply. In the hands of an expert, that is doubtless true for perl. I shouldn't need to be an expert. As a non-expert, this simple task has proven to be a lot more difficult than it should be. I did not expect to hit fundamental language defects and I did expect to find a module that would hide all of the cryptic clever code.

      Dazz

        I am not a professional programmer. I don't work with or for any professional programmers ... The thing that attracted me to Perl was the extensive library of modules. Lego blocks that I could join together to get things done. Not a new concept. I was using Coral66 back in the 1980's ...

        Interesting to learn your programming history! Hadn't heard of Coral 66 before.

        Queen Elizabeth II sent the first email from a head of state from the Royal Signals and Radar Establishment over the ARPANET on March 26, 1976. The message read "This message to all ARPANET users announces the availability on ARPANET of the Coral 66 compiler provided by the GEC 4080 computer at the Royal Signals and Radar Establishment, Malvern, England, ... Coral 66 is the standard real-time high level language adopted by the Ministry of Defence"

        -- CORAL (wikipedia)

        Notwithstanding Coral66 being a fine language for its time -- even endorsed by Her Majesty Elizabeth II I see (rare for a programming language) -- I don't agree with your expectation that programming with Perl and its vast CPAN library is as easy as playing with Lego blocks (besides, it's Jenga not Lego ;).

        Update: In addition to endorsing Coral 66, Her Majesty Elizabeth II adores the Perl programming language - which she subtly reveals by wearing a pearl necklace time and time again (as in her wikipedia photo).

        Though using CPAN modules may seem "free", there are many pitfalls for the unwary (as noted at Writing Solid CPAN Modules):

        • What if the CPAN module has a security vulnerability? What if the author abandons it? How quickly can you isolate/troubleshoot a bug in its code? Does its license restrict what you can do with your code? If it requires Perl 5.20+ so do you. If it's Linux-only, so are you.
        • It's usually best to use popular, quality CPAN modules in complex domains (e.g. DBI and XML) rather than roll your own. Doing so allows you to leverage the work of experts in fields that you are probably not expert in. Moreover, widely used CPAN modules tend to be robust and have fewer bugs than code you write yourself because they are tested by more users and in many different environments.
        • For small and simple modules, on the other hand, such as slurping a file, I prefer to roll my own code rather than pay the dependency cost of an external module.
        • Before introducing a dependency, it's worth checking CPAN ratings, Kwalitee score, author, doco, popularity, PM nodes discussing it, community, dependencies (especially unnecessary ones), test results, code coverage, bug counts, how quickly bugs are fixed, ...

        Note that many of the points above apply not just to the module you are using but to all of its dependencies too. See also Dependency hell (wikipedia).

        So, if faced with your problem, I would roll my own code (without using the dreaded Switch! ;). I would probably roll my own code even if a CPAN module were available, to avoid paying the dependency costs described above.

        As already discussed to death, I'd focus on Maintainability, tybalt89 on Conciseness, you on Readability. TMTOWTDI! :)

        Updated: expanded last bullet point based on Perl Monks poll: I am most likely to install a new module from CPAN if:

        Try not to get hung up on the "switch" issue. In my view given/when is a fine addition to the language, but it came bundled with the smart match operator. It turned out the smart match operator was a bad idea (much too powerful and arcane) and unfortunately, because it was bundled with given/when, given/when acquired the same bad rep. As eyepopslikeamosquito suggests, there are other ways to do it - and that is really a cornerstone of Perl: there are many ways to do most things so you can play around and see what works best for you and the task at hand.

        Perl is a great language for this sort of task where you are munging files. But tricky stuff is tricky stuff and all a good language can do is make it easier to think about the key parts of the problem while it hides the boring stuff away. If someone else hasn't already done the job for you (or you can't find it) and you don't want to pay someone to do it, ya just gotta do the tricky stuff.

        Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond

        We'd all like to use a language that provides all the applications we'd ever need or want already encapsulated and available to us via some sort of module mechanism. If you ever find such a language, please, please let me know about it! Until then, I'm afraid we're all stuck with the necessity of knowing how to program in our language(s) of choice. So it goes...


        Give a man a fish:  <%-{-{-{-<

Re^4: Yet another config file editing programme : Tell me how to make it better !
by shmem (Chancellor) on Sep 08, 2021 at 13:26 UTC
    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'
Re^4: Yet another config file editing programme : Tell me how to make it better !
by eyepopslikeamosquito (Archbishop) on Sep 09, 2021 at 00:12 UTC

      I once put a sign (er, postit note) on my cubicle cork board that said

      Programming is the poetry of the 80's
      :)

Re^4: Yet another config file editing programme : Tell me how to make it better !
by dazz (Beadle) on Sep 15, 2021 at 09:52 UTC
    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
      I load it to ARGV to run it through a while( <> ) loop

      This is the cause of your problems. Don't do this. If you want to operate on the individual lines use slurp lines and a for loop instead.

      #!/usr/bin/env perl use strict; use warnings; use Path::Tiny; ###### Output dhcp configuration file my $dhcpcdfile = 'd.conf'; # TEST my @rows = path($dhcpcdfile)->lines; my %ip_params = (); # YOU MUST POPULATE THIS FIRST my $foundinterface; # YOU MUST ASSIGN A VALUE TO THIS for my $line (@rows) { ### YOUR CODE HERE 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 } ### YOUR CODE ENDS HERE } path('spew.cfg')->spew(@rows);

      Note, untested because what was supplied doesn't compile and isn't an SSCCE.

      Update: s/slurp/lines/;


      🦛

        Hi

        OK thanks that worked.


        Dazz

      dazz: you seem to be succumbing to a Cargo cult style of programming in harness with Shotgun debugging.

      tybalt89 is a Perl expert. You are a Perl beginner. Instead of making minor tweaks to tybalt89's advanced complete program, I suggest you start from scratch with a very small program of your own.

      Test it. Run it. Read the Perl and CPAN module documentation. Make sure you understand how every statement works. Format it in a way that suits your brain (please please don't copy tybalt89's idiosyncratic formatting style ;-). Liberally add print/dump statements to confirm that your program is working the way you think it should. Build confidence.

      Then gradually grow your program, step by step, until it solves your problem, making sure that you always have a working program, every step of the way. And making sure you understand in detail how every line works.

      Though not core modules, I suggest you stick with tybalt89's excellent choice of Path::Tiny and Data::Dump from CPAN (these two modules are also included with Strawberry Perl). Read and understand their CPAN documentation.

      To give a concrete example, your first version might be as simple as:

      #!/usr/bin/env perl use strict; use warnings; use Path::Tiny; use Data::Dump; ###### Output dhcp configuration file my $dhcpcdfile = 'd.conf'; print "at start of program---\n"; my @rows = path($dhcpcdfile)->lines; dd( @rows ); print "before loop---\n"; for my $line (@rows) { $line =~ s/^\s+//; # remove leading $line =~ s/\s+$//; # and trailing whitespace next unless length $line; # ignore empty lines next if $line =~ /^#/; # ignore comment lines my @fields = split /=/, $line, 2; dd( @fields ); } print "after loop---\n";

      With the file d.conf containing:

      #IP Configuration #Fri Aug 27 16:07:40 NZST 2021 routers=192.130.1.1 interface=eth0 domain_name_servers=8.8.8. 8.8.1.1 ip_address=192.130.1.10/24

      and running this program producing:

      at start of program--- ( "#IP Configuration\n", "#Fri Aug 27 16:07:40 NZST 2021\n", "\n", "routers=192.130.1.1\n", "interface=eth0\n", "domain_name_servers=8.8.8. 8.8.1.1\n", "ip_address=192.130.1.10/24\n", ) before loop--- ("routers", "192.130.1.1") ("interface", "eth0") ("domain_name_servers", "8.8.8. 8.8.1.1") ("ip_address", "192.130.1.10/24") after loop---

      Finally, please study and follow Short, Self-Contained, Correct Example when you post asking for assistance.

      Updated: minor improvements to wording.

Log In?
Username:
Password:

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

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

    No recent polls found