Beefy Boxes and Bandwidth Generously Provided by pair Networks Bob
P is for Practical
 
PerlMonks  

Printing Fixed Width Strings and Spliting String into Multiple Lines

by mmartin (Monk)
on Feb 07, 2012 at 22:05 UTC ( #952367=perlquestion: print w/ replies, xml ) Need Help??
mmartin has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks,

I was hoping someone could lend me a hand.
I have a Perl script that works alongside MRTG building cfg files. This isn't really relevant to know what I want to do but I thought it couldn't hurt. MRTG has a Perl script called "cfgmaker" which as the name suggests builds cfg files. At the top of these config files it automatically generates a very small comment block.

What I would like to do is open that file read in it's contents, then print back out to the file with a modified comment block at the top. I have it pretty much where I want it except for making some of the strings a fixed width so that it doesn't span all the way to the end of the string on the same line. Because for example, one of the strings (which is the actual command executed from the command line) could be anywhere from 250-300+ characters.

This is what I have so far. (The variable $module_cmd will be gotten dynamically later when I add this to the full/production script, using ($0 and @ARGV):
Also it will only print out to the screen right now. After I get it working I will print it back to the file.
#!/usr/bin/perl use warnings; use strict; use Time::Local; #Will hold the command line data from the execution of module "add_us +erDefined" (my $module_cmd = "$0 @ARGV";) #But for now hard-code in an example. my $module_cmd = "./add_userDefined --cfg=Router-Memory_and_Storage.cf +g --host-template=In-Progress/Memory-and-Storage.htp --if-template=In +-Progress/Memory.template --ip=10.10.10.10"; #Will hold the command line data executed from cfgmaker (will be take +n from comment block of <DATA> file below my $cfgmaker_cmd; my $comment; my $wholeFile; &appendToFile(); sub appendToFile() { #Get ONLY the Month and the Year my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localti +me(); my %months = (0=>'January', 1=>'Feburary', 2=>'March', 3=>'April', + 4=>'May', 5=>'June', 6=>'July', 7=>'August', 8=>'September', 9=>'Oct +ober', 10=>'November', 11=>'December'); $year = 1900+$year; my $month_year = "$months{ $mon } $mday, $year"; open (DATA, "< /mrtg/mrtgwww/cfg/devices/Tests/Router-Memory_and_S +torage.cfg") or die "There was an error while trying to open the file +. $!\n"; while (my $line = <DATA>) { if ($line =~ /^# Created by/i) { #Skip this line... next; } elsif ($line =~ /\/jwpmrtg\/bin\/cfgmaker/i) { chomp($line); $cfgmaker_cmd .= $line; } else { $wholeFile .= $line; } } close DATA; $comment .= <<EOF; ###################################################################### +######################################### ## Date: $month_year ## Module CMD: $module_cmd ## cfgmaker CMD: $cfgmaker_cmd EOF $comment .= "##################################################### +##########################################################\n"; print $comment; # print $wholeFile; }
All I really am trying to figure out is how to make the strings $module_cmd and $cfgmaker_cmd be split into multiple lines depending on it's length.
I know this can be done using printf/sprintf. But not really sure where to begin with it.

Here is the output from the code above (printing only $comment)

###############################################################################################################
## Date: Feburary 7, 2012
## Module CMD: ./add_userDefined --cfg=Router-Memory_and_Storage.cfg --host-template=In-Progress/Memory-and-Storage.htp --if-template=In-Progress/Memory.template --ip=10.10.10.10
## cfgmaker CMD: /jwpmrtg/bin/cfgmaker --nointerfaces --host-template=/jwpmrtg/mrtgwww/cfg/templates/In-Progress/NetTools_Memory-and-Storage.htp --global "routers.cgi*Icon: router2-sm.gif" --output /jwpmrtg/mrtgwww/cfg/build_userDefined/NetTools-Percentage.cfg jwpadmin!@192.168.5.6
###############################################################################################################


What I would like it to look like is to have it print with each line ending with the same length as the "#####...####" border lines, with closing "#" at the end of each line):

I tried printing an example of what I want it to look like but can't seem to get it to look right on here.


I'm pretty sure I would be using something like... printf "%-100s #", $module_cmd;

Any help would be much appreciated.


Thanks,
Matt

Comment on Printing Fixed Width Strings and Spliting String into Multiple Lines
Download Code
Re: Printing Fixed Width Strings and Spliting String into Multiple Lines
by chessgui (Scribe) on Feb 07, 2012 at 22:23 UTC
    To split string $s to lines of 100 characters long do:
    $s=~s/(.{100})/$1\n/g;

      There's no reason to invoke something as heavy-duty as the regular expression engine when all you're working with is substrings of a given length.

      my $s; print "$s\n" while $s = substr $str, 0, 100, '';
      -- 
      I hate storms, but calms undermine my spirits.
       -- Bernard Moitessier, "The Long Way"
        Hey oko1,

        I just gave your little snippet a try and it worked without any problems... Thanks!


        Thanks,
        Matt

Re: Printing Fixed Width Strings and Spliting String into Multiple Lines
by GrandFather (Cardinal) on Feb 07, 2012 at 22:47 UTC

    Something like the magic you want is in wrapLine at the end of the code. However take note of the other changes I've made to your sample code. In particular:

    • Don't use global variables.
    • Don't use prototype subs
    • Don't call subs using &
    • Use three parameter open and lexical file handles
    • Declare variables in the smallest scope that makes sense
    #!/usr/bin/perl use warnings; use strict; use Time::Local; my $kMaxLine = 70; #Will hold the command line data from the execution of module "add_use +rDefined" (my $module_cmd = "$0 @ARGV";) #But for now hard-code in an example. appendToFile( '/mrtg/mrtgwww/cfg/devices/Tests/Router-Memory_and_Storage.cfg', './add_userDefined --cfg=Router-Memory_and_Storage.cfg --host-temp +late=In-Progress/Memory-and-Storage.htp --if-template=In-Progress/Mem +ory.template --ip=10.10.10.10', ); sub appendToFile { my ($cfgFileName, $module_cmd) = @_; #Get ONLY the Month and the Year my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(); my %months = ( 0 => 'January', 1 => 'Feburary', 2 => 'March', 3 => 'April', 4 => 'May', 5 => 'June', 6 => 'July', 7 => 'August', 8 => 'September', 9 => 'October', 10 => 'November', 11 => 'December' ); $year = 1900 + $year; my $month_year = "$months{ $mon } $mday, $year"; my $cfgmaker_cmd; my $wholeFile; #open my $dataIn, '<', $cfgFileName or die "Error opening $cfgFile +Name: $!\n"; while (my $line = <DATA>) { next if $line =~ /^# Created by/i; if ($line =~ /\/jwpmrtg\/bin\/cfgmaker/i) { chomp($line); $cfgmaker_cmd .= $line; } else { $wholeFile .= $line; } } #close $dataIn; $_ = wrapLine($_) for $module_cmd, $cfgmaker_cmd; my $sep = '#' x $kMaxLine; my $comment .= <<EOF; $sep ## Date: $month_year ## Module CMD: $module_cmd ## cfgmaker CMD: $cfgmaker_cmd EOF $comment .= "$sep\n"; print $comment; # print $wholeFile; } sub wrapLine { my ($line) = @_; $line = '' if ! defined $line; return join "\n## ", $line =~ m~(.{1,$kMaxLine})(?:\s|(?<![-. +\w=/]))~g; } __DATA__

    Prints:

    ###################################################################### ## Date: Feburary 8, 2012 ## Module CMD: ./add_userDefined --cfg=Router-Memory_and_Storage.cfg + ## --host-template=In-Progress/Memory-and-Storage.htp ## --if-template=In-Progress/Memory.template ## cfgmaker CMD: ######################################################################
    True laziness is hard work
      Hey GrandFather,

      I think I may be going with your suggestion (Nice Work BTW!)... I just had one question about it.

      For the line that starts with "$_ = wrapLine($_) for ..." what does the $_ stand for in this case. I tried just to do a print on the line before that statement just to see what the variable is and I get a "Use of Uninitialized value in concatenation (.) or string at..."

      Also, the last line of code that starts "return join "\n## ", $line =~ ......", I'm assuming that the right side of the =~ is a regex? Would you be able to explain a little how that works? I would like to have it split to a new line just at the end of the $kMaxLine length instead of a blank space.
      And also assuming your using parenthesis as separators instead of the usual "/", and that "m~" stands for multiline and the "~g" stands for gloabl... Is that right?

      Sorry for all the extra questions but I'm just one of those people who has to know how it works lol... Thanks again!

      Thanks in Advance,
      Matt

        Don't apologise for asking questions to find out about stuff you don't know! After all, that is exactly why you are visiting.

        $_ is Perl's default variable. It is used as an alias to the current item for things like grep and map. It is also used in the same way when you use for as a statement modifier such as in $_ = wrapLine($_) for $module_cmd, $cfgmaker_cmd;. I used that trick by the way to save writing out:

        $_ = wrapLine($module_cmd); $_ = wrapLine($cfgmaker_cmd);

        and of course to expose you to some interesting Perl.

        $line =~ m~(.{1,$kMaxLine})(?:\s|(?<![-.\w=/]))~g is a regular expression match. m is the match operator and uses the following non-whitespace character as the quote character for the regular expression string. In this case it's using ~ which I chose to avoid having to quote the / used in the match string. The g on the end is the "global match" flag - match as many times as you can. There is a lot more in that regular expression and I strongly encourage you to read perlretut and perlre to at least get an overview of whet regexes do and look like. You can simplify the regex line to:

        return join "\n## ", $line =~ m~(.{1,$kMaxLine})~g;

        to have it break the line into fixed size pieces without regard to white space or punctuation. You would find it instructive however to read the documentation for regexen engough to be able to figure out how the original regex works. ;)

        True laziness is hard work
Re: Printing Fixed Width Strings and Spliting String into Multiple Lines
by mmartin (Monk) on Feb 08, 2012 at 14:37 UTC
    Hey Cheesgui, thanks for the reply, I'll give that a try.


    Hey oko1, also thanks for the reply. That looks pretty simple to do, I'll definatly give that a try and see how it goes... Thanks.


    Hello GrandFather, thanks for the knowledge! I know it was a little messy but it was more or less just a test script that I wrote on the fly just to see how it would work... But I will take your fixes and add them before I transfer it over to the actual script.
    You said don't call subs using '&'... Is that just an older way of doing it?
    Also, what do you mean by "Don't use prototype subs"..?


    Again, thanks everybody for your suggestions!

    Thanks,
    Matt


      Don't use my solution if you just have to print the data. I measured the time and it is slower than doing it with substr.
        Ok cool, no problem... Thanks for the follow-up.


        Thanks,
        Matt

        In the context of anything to do with file I/O the differences you may have measured are likely to be completely irrelevant. Even for lakh (105) lines is the difference likely to be more than a second?

        Benchmarking stuff is often fun, but seldom relevant and very seldom a good reason to prefer one technique over another. It is generally better to make the code clear and maintainable even if there is a small execution speed penalty.

        True laziness is hard work
Re: Printing Fixed Width Strings and Spliting String into Multiple Lines
by CountZero (Chancellor) on Feb 08, 2012 at 21:26 UTC
    If you only need the month and the year, it is less typing if you do:
    my ($mon, $year) = (localtime())[4, 5];

    And for finding the name of the month I use a list:

    my $month = (qw/January February March April May June July August Sept +ember October November December/)[(localtime())[4]];

    And combining all together (without using any intermediary variables):

    $month_year= (qw/January February March April May June July August Sep +tember October November December/)[(localtime())[4]] . (1900 + (local +time())[5]);

    CountZero

    A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

      Hey CountZero, thanks for the reply.

      Nice little trick. I'll have to add that to my code. Looks good, much cleaner... Thanks!


      Thanks,
      Matt

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://952367]
Approved by GrandFather
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others taking refuge in the Monastery: (13)
As of 2014-04-24 12:58 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (565 votes), past polls