Naveen Desai
Naveen Desai

Reputation: 21

in a sub routine ... using return to get some elements of array

I am trying to get some elements of array to return in the subroutine .it gives me only the last one dd 4 13

here is my csv

aa ,1 ,no ,ed ,8 
bb ,2 ,yes ,ed ,10
cc ,3 ,no ,ed ,12
dd ,4 ,yes ,ed ,13

here is my perl code

use strict;
use Getopt::Std;

my $input_file  = $ARGV[0];
my @data =  read_data_from_csv($input_file);

sub read_data_from_csv
{
    my ($fh) = @_;
    my @d = ();

    open(FH, "<$fh") || die "Error: no open for $fh: $!";
  
    while (<FH>) {
    chomp;
    my @list =split(/,/);
    my ($aa) = $list[0];
    my ($bb) = $list[1];
    my ($cc) = $list[4];
    push (@d , ($aa, $bb, $cc));
}
close (FH);
    return @d
}

 print "@data\n"; 
cat test.csv 
aa ,1 ,no ,ed ,8 
bb ,2 ,yes ,ed ,10
cc ,3 ,no ,ed ,12
dd ,4 ,yes ,ed ,13
/pkg/qct/software/perl/5.20.0/bin/perl test.pl test.csv 
 dd  yes  13

Upvotes: 1

Views: 83

Answers (2)

amit bhosale
amit bhosale

Reputation: 482

You can use https://metacpan.org/pod/Text::CSV_XS module. Text::CSV_XS - comma-separated values manipulation routines. you can use functionality as per your requirement.

use strict;
use warnings;
use Text::CSV_XS;
use Data::Dumper qw(Dumper);

my @rows;
my @column_numbers = (0,1,4);
my $csv_file = "tst.csv";
# check https://metacpan.org/pod/Text::CSV_XS#binary
# check http://metacpan.org/pod/Text::CSV_XS#auto_diag
my $csv = Text::CSV_XS->new ({ binary => 1, auto_diag => 1 });
open my $fh, "<:encoding(utf8)", $csv_file or die "$csv_file: $!";
# $csv->getline_all ($fh); will return a reference to a list of getline ($fh) results.
#The map function of Perl provides a simple way to transform a list of values to another list of values.
@rows = map { [ @{ $_ }[@column_numbers] ] } @{ $csv->getline_all($fh) };
print Dumper(\@rows);
close $fh;

Output

$VAR1 = [
          [
            'aa ',
            '1 ',
            '8 '
          ],
          [
            'bb ',
            '2 ',
            '10'
          ],
          [
            'cc ',
            '3 ',
            '12'
          ],
          [
            'dd ',
            '4 ',
            '13'
          ]
        ];

Note : Please check solution provided by @ikegami Unable to read multiple columns from a .csv using Text::CSV_XS in Perl

map: 1) https://perldoc.perl.org/functions/map.html 2) https://perlmaven.com/transforming-a-perl-array-using-map

Upvotes: 1

Sobrique
Sobrique

Reputation: 53478

Your code as posted works:

use strict;
use Getopt::Std;

my $input_file = $ARGV[0];
my @data       = read_data_from_csv($input_file);

sub read_data_from_csv {
    my ($fh) = @_;
    my @d = ();

    #open(FH, "<$fh") || die "Error: no open for $fh: $!";

    while (<DATA>) {
        chomp;
        my @list = split(/,/);
        my ($aa) = $list[0];
        my ($bb) = $list[1];
        my ($cc) = $list[4];
        push( @d, ( $aa, $bb, $cc ) );
    }
    close(FH);
    return @d;
}

print "@data\n";

 __DATA__
 aa ,1 ,no ,ed ,8 
 bb ,2 ,yes ,ed ,10
 cc ,3 ,no ,ed ,12
dd ,4 ,yes ,ed ,13

Output:

aa  1  8   bb  2  10  cc  3  12 dd  4  13

I would suggest you need to check your input file - if on unix, try cat -v which might show you you've got bad line endings.

An easy fix if you do have this problem (or to test it) is include:

s/[\r\n]//g; 

More generally though I think there's a few errors in your code - that 'push' for example, probably isn't doing what you're thinking, because you're compressing your CSV into a flat array.

I'd also suggest using Data::Dumper to test outputs, because it's clearer what's happening:

$VAR1 = [
          ' aa ',
          '1 ',
          '8 ',
          ' bb ',
          '2 ',
          '10',
          ' cc ',
          '3 ',
          '12',
          'dd ',
          '4 ',
          '13'
        ];

As you can see, you've flattened your data, which I am assuming isn't what you want, based on what your write in push.

So you might want to consider using [] instead, because then you get:

$VAR1 = [
          [
            ' aa ',
            '1 ',
            '8 '
          ],
          [
            ' bb ',
            '2 ',
            '10'
          ],
          [
            ' cc ',
            '3 ',
            '12'
          ],
          [
            'dd ',
            '4 ',
            '13'
          ]
        ];

You can also direct assign array slices, rather than having a 1-1:

push ( @d, [ @list[0,1,4] ] ); 

Also as a final point - it's bad style to use single letter variables, and you should also use warnings;.

Giving you:

use strict;
use Getopt::Std;
use warnings;
use Data::Dumper;

my $input_file = $ARGV[0];
my @data       = read_data_from_csv($input_file);

sub read_data_from_csv {
    my ($fh) = @_;
    my @d = ();

     ##NB Commented out so I can use inline data
    #open(FH, "<$fh") || die "Error: no open for $fh: $!";

    while (<DATA>) {
        chomp;
        s/[\r\n]//g;
        my @list = split(/,/);        
        push( @d, [ @list[0,1,4] ]);
    }
    ##close(FH);
    return @d;
}

print Dumper \@data;

 __DATA__
 aa ,1 ,no ,ed ,8 
 bb ,2 ,yes ,ed ,10
 cc ,3 ,no ,ed ,12
dd ,4 ,yes ,ed ,13

You might want to consider that instead of opening the file specified in $ARGV[0] this is precisely what the 'magic' file handle <> does. It opens file specified on command line and iterates them, but it also allows you to 'pipe' into the process on STDIN:

Making your program:

#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;

## flattened array
my @data_A = map { s/[\r\n]//g; ( split(/,/) ) [0,1,4] } <>;     
## preserving structure
my @data_B = map { s/[\r\n]//g; [( split(/,/) ) [0,1,4]] } <>;    
print Dumper \@data_B;

I appreciate your code is probably a reduction of the core problem, but I'm just wanting to illustrate simplification options.

Upvotes: 2

Related Questions