Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

Reading into array with sscanf

by colintu (Acolyte)
on Jul 16, 2024 at 10:47 UTC ( [id://11160631]=perlquestion: print w/replies, xml ) Need Help??

colintu has asked for the wisdom of the Perl Monks concerning the following question:

I'm writing and reading some data which includes array variables. I can make it work but I can't find an 'elegant' way. Code fragments below

my @c_speed = (5, 8, 12, 8); my $sfh; open($sfh, '>', $save_fn) or die "Unable to open file $save_fn : $!"; print $sfh "@c_speed\n"; ... The file contains a line that looks like "5 8 12 8\n" ... my $lfh; open($lfh, '<', $load_fn) or die "Unable to open file $load_fn : $!"; $rstring = <$lfh>; # Char speeds ($c_speed[0], $c_speed[1], $c_speed[2], $c_speed[3]) = sscanf("%d %d + %d %d", $rstring);

I tried using read to get the data but either encountered syntax errors or got zero (or maybe 'undef') results. What is the correct way to read the array back in? What I'm doing works for an array of 4 but is obviously impractical for bigger arrays.

Replies are listed 'Best First'.
Re: Reading into array with sscanf
by hippo (Archbishop) on Jul 16, 2024 at 11:24 UTC

    The correct answer is to choose an appropriate data format to save as, such as CSV, JSON, XML, etc. You can then use your module of choice for reliably reading and writing. If it's just for temporary backing storage then Storable, Sereal and friends will do the job too.

    However, for your example set of integers here's a workable approach using split:

    #!/usr/bin/env perl use strict; use warnings; my $save_fn = 'colintu.dat'; my @c_speed = (5, 8, 12, 8); open my $sfh, '>', $save_fn or die "Unable to open file $save_fn : $!"; print $sfh "@c_speed\n"; close $sfh; open my $lfh, '<', $save_fn or die "Unable to open file $save_fn : $!"; @c_speed = split / /, <$lfh>; print "Have: @c_speed\n"; close $lfh; unlink $save_fn; # clean up

    (Edited for typo fix - thanks, Corion)


    🦛

      Ah, thanks, that is what I was looking for (or failing to remember). I don't want to use any of the usual things like JSON, XML etc because this has got to be extremely light weight as it's going to be embedded.

        I don't want to use any of the usual things like JSON, XML etc because this has got to be extremely light weight as it's going to be embedded

        That's fine. I still encourage you to structure your lightweight script file into (unit-testable) subroutines at the top of the file, with each subroutine having well-defined inputs and outputs and not relying on global data ... followed by a short mainline at the end.

        A simple example of this approach can be found in this node.

        👁️🍾👍🦟
      I tried your example and it works BUT it does not do the same as my code example. My code fragments return values into the c_speed array that are *numeric* not the strings that split returns, Which is why I was using sscanf from String::Scanf The variable array c_speed needs to be numeric not string because the values get processed elsewhere.

        Perl makes very little difference between strings and numbers.

        There are Perl operators, like + that treat their arguments as numbers, and other operators that treat their arguments as strings.

        Where in your code do you see that the variables get treated differently?

        They perform just fine in a numeric context.

        #!/usr/bin/env perl use strict; use warnings; my $save_fn = 'colintu.dat'; my @c_speed = (5, 8, 12, 8); open my $sfh, '>', $save_fn or die "Unable to open file $save_fn : $!"; print $sfh "@c_speed\n"; close $sfh; open my $lfh, '<', $save_fn or die "Unable to open file $save_fn : $!"; @c_speed = split / /, <$lfh>; print "Have: @c_speed\n"; @c_speed = map { $_ * 2 } @c_speed; print "Doubled: @c_speed\n"; close $lfh; unlink $save_fn; # clean up

        🦛

Re: Reading into array with sscanf
by tybalt89 (Monsignor) on Jul 16, 2024 at 13:53 UTC
    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11160631 use warnings; my $save_fn = 'save.fn'; # NOTE my @c_speed = (5, 8, 12, 8); my $sfh; open($sfh, '>', $save_fn) or die "Unable to open file $save_fn : $!"; print $sfh "@c_speed\n"; close $sfh; # NOTE #... # #The file contains a line that looks like "5 8 12 8\n" #... my $load_fn = 'save.fn'; # NOTE my $lfh; open($lfh, '<', $load_fn) or die "Unable to open file $load_fn : $!"; #$rstring = <$lfh>; # Char speeds # ($c_speed[0], $c_speed[1], $c_speed[2], $c_speed[3]) = sscanf("%d % +d %d %d", $rstring); # NOTE using ' ' instead of / / also removes the \n @c_speed = map $_ + 0, split ' ', <$lfh>; # NOTE force number use Data::Dump 'dd'; dd @c_speed;

    Outputs:

    (5, 8, 12, 8)

      Thanks for all the replies. The examples all work and I even understand them (after a bit of reading). However, I'm seeing some weirdness in my actual application. The c_speed variable is being used in a Tk widget as follows:

      my $cs_e1 = $seg1_f->Entry(-textvariable => \$c_speed[0], -width => +4, -validate => 'all', -vcmd => \&validate_s +peed, -font => $med_font)->g +rid( -row => 0, -column => +1, -sticky => "w"); sub validate_speed{ my $val = shift; $val ||= 0; #get alphas and punctuation out if( $val !~ /^\d+$/ ){ return 0 } if (($val >= 0) and ($val <= 10)) {return 1} else{ return 0 } }

      When I use split I can print c_speed to the terminal and the values are there, the same as when I use my sscanf lines

      $rstring = <$lfh>; ($c_speed[0], $c_speed[1], $c_speed[2], $c_speed[3]) = sscanf("%d %d + %d %d", $rstring);

      BUT, the Tk Entry widget only sees the new values when using sscanf. When I use split the variable still retains it's old value. I'm assuming this is some widget weirdness - can anyone explain please?

        Can you show some of the values that do not update?

Re: Reading into array with sscanf
by LanX (Saint) on Jul 16, 2024 at 11:33 UTC
    I'm confused, Perl doesn't implement sscanf , that's apparently a C function to "inverse" printf ( for completeness compare String::Scanf )

    Anyway I'm pretty sure you basically only need this to "elegantly" parse one line:

    @c_speed = split / /, $rstring;

    See split for more

    There are more issues with your code, but better little by little.

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    see Wikisyntax for the Monastery

Re: Reading into array with sscanf
by jwkrahn (Abbot) on Jul 16, 2024 at 19:52 UTC
    my $lfh; open($lfh, '<', $load_fn) or die "Unable to open file $load_fn : $!"; $rstring = <$lfh>; # Char speeds ($c_speed[0], $c_speed[1], $c_speed[2], $c_speed[3]) = sscanf("%d %d + %d %d", $rstring);

    The simple answer is:

    open( my $lfh, '<', $load_fn ) or die "Unable to open file $load_fn : $!"; my @c_speed = split ' ', <$lfh>; # Char speeds
    Naked blocks are fun! -- Randal L. Schwartz, Perl hacker

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others rifling through the Monastery: (5)
As of 2024-09-15 07:03 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    The PerlMonks site front end has:





    Results (21 votes). Check out past polls.

    Notices?
    erzuuli‥ 🛈The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.