Problem accessing hash imported from CSV in Perl - csv

I am working with the Text::CSV library of Perl to import data from a CSV file, using the functional interface. The data is stored in an array of hashes, and the problem is that when the script tries to access those elements/keys, they are uninitialized (or undefined).
Using the library Dumper, it is possible to see that the array and the hashes are not empty, in fact, they are correctly filled with the data of the CSV file.
With this small piece of code, I get the following output:
my $array = csv(
in => $csv_file,
headers => 'auto');
foreach my $mem (#{$array}) {
print Dumper $mem;
foreach (keys $mem) {
print $mem{$_};
}
}
Last part of the output:
$VAR1 = {
'Column' => '16',
'Width' => '13',
'Type' => 'RAM',
'Depth' => '4096'
};
Use of uninitialized value in print at ** line 81.
Use of uninitialized value in print at ** line 81.
Use of uninitialized value in print at ** line 81.
Use of uninitialized value in print at ** line 81.
This happens with all the elements of the array. Is this problem related to the encoding, or I am just simply accessing the elements in a incorrect way?

$mem is a reference to a hash, but you keep trying to use it directly as a hash. Change your code to:
foreach (keys %$mem) {
print $mem->{$_};
}
There is a slight complication in that in some versions of perl, 'keys $mem' was allowed directly as an experimental feature, which later got removed. In any case, adding
use warnings;
use strict;
would likely have given you some helpful clues as to what was happening.

When I run your code on my version of Perl (5.24), I get this error:
Experimental keys on scalar is now forbidden at ... line ...
This points to the line:
foreach (keys $mem) {
You should dereference the hash ref:
use warnings;
use strict;
use Data::Dumper;
use Text::CSV qw( csv );
my $csv_file="data.csv";
my $array = csv(
in => $csv_file,
headers => 'auto');
foreach my $mem (#{$array}) {
print Dumper($mem);
foreach (keys %{ $mem }) {
print $mem->{$_}, "\n";
}
}

Related

"Can't use string ("") as a HASH ref while "strict refs" in use at" error while assigning hash to an hash in perl

I'm assigning an hash %attributes to another hash %attributes_r. I need to print it as key/value pair of that hash %attributes, as mentioned below. But, while printing, why do I get this error, "Can't use string ("") as a HASH ref while "strict refs" in use at" in this line "foreach my $key1 (keys %{$attributes_r{$key}}) {" ?
My Code:
use strict;
use warnings;
our %attributes_r;
my %attributes = ('clear' => 0,
'reset' => 0,
'bold' => 1,
'dark' => 2,
'underscore' => 4,
'blink' => 5,
'reverse' => 7,
'concealed' => 8
);
for (keys %attributes) {
$attributes_r{$attributes{$_}} = $attributes{$_};
# print "$_ => $attributes_r{$attributes{$_}}\n";
}
foreach my $key (keys %attributes_r) {
foreach my $key1 (keys %{$attributes_r{$key}}) {
print "$key1 = > $attributes_r{$key}{$key1}\n";
}
}
Any help is appreciated.
With your data, this is incorrect:
foreach my $key1 (keys %{$attributes_r{$key}}) {
You are trying to dereference a value that is a string, not a reference, like the error says. If you did have a hash of hash, it might work, but you do not.
You might be trying to create a hash of hash, I'm not sure. This piece of code is very odd:
$attributes_r{$attributes{$_}} = $attributes{$_};
Here you are taking the value from the original hash and use it as a key in the other hash. For example:
$attributes_r{0} = 0;
This is not really going to accomplish anything useful. If you clarify what it is you are trying to do I might be able to suggest a fix.
Also, if you are trying to view the data structure you are creating, I suggest you print it with Data::Dumper like this:
use Data::Dumper;
...
print Dumper \%attributes_r;

Using Text::CSV on a String Containing Quotes

I have pored over this site (and others) trying to glean the answer for this but have been unsuccessful.
use Text::CSV;
my $csv = Text::CSV->new ( { binary => 1, auto_diag => 1 } );
$line = q(data="a=1,b=2",c=3);
my $csvParse = $csv->parse($line);
my #fields = $csv->fields();
for my $field (#fields) {
print "FIELD ==> $field\n";
}
Here's the output:
# CSV_XS ERROR: 2034 - EIF - Loose unescaped quote # rec 0 pos 6 field 1
FIELD ==>
I am expecting 2 array elements:
data="a=1,b=2"
c=3
What am I missing?
You may get away with using Text::ParseWords. Since you are not using real csv, it may be fine. Example:
use strict;
use warnings;
use Data::Dumper;
use Text::ParseWords;
my $line = q(data="a=1,b=2",c=3);
my #fields = quotewords(',', 1, $line);
print Dumper \#fields;
This will print
$VAR1 = [
'data="a=1,b=2"',
'c=3'
];
As you requested. You may want to test further on your data.
Your input data isn't "standard" CSV, at least not the kind that Text::CSV expects and not the kind that things like Excel produce. An entire field has to be quoted or not at all. The "standard" encoding of that would be "data=""a=1,b=2""",c=3 (which you can see by asking Text::CSV to print your expected data using say).
If you pass the allow_loose_quotes option to the Text::CSV constructor, it won't error on your input, but it won't consider the quotes to be "protecting" the comma, so you will get three fields, namely data="a=1, b=2" and c=3.

putting commas separated values into a new file line by line

From command line, we are passing multiple values separated by commas such as sydney,delhi,NY,Russia as an option. These values are getting stored under $runTest in the perl script. Now I want to create a new file under the script with contents of $runTest but as line by line. For example:
INPUT (passed values from command line):
sydney,delhi,NY,Russia
OUTPUT (under new file: myfile):
sydney
delhi
NY
Russia
In this simple example, it is better to use split on a delimiter than tr in such case. A few minor points: use snake_case for names instead of CamelCase, and use autodie to make open, close, etc, fatal, without the need to clutter the code with or die "...":
use autodie;
my $run_test = 'sydney,delhi,NY,Russia';
open my $out, '>', 'myFile';
print {$out} map { "$_\n" } split /,/, $run_test;
close $out;
For more robust parsing in general, beyond this simple example, prefer specialized modules, such as Text::CSV or Text::CSV_XS for csv parsing. Compared to the overly simplistic split, Text::CSV_XS enables correct input/output of quoted fields, fields containing the delimiter (comma), binary characters, provides error messages and more. Example:
use Text::CSV_XS;
use autodie;
open my $out, q{>}, q{myFile};
# All of these input strings are parsed correctly, unlike when using "split":
# my $run_test = q{sydney,delhi,NY,Russia};
# my $run_test = q{sydney,delhi,NY,Russia,"field,with,commas"};
my $run_test = q{sydney,delhi,NY,Russia,"field,with,commas","field,with,missing,quote};
# binary => 1 : enable parsing binary characters in quoted fields.
# auto_diag => 1 : print the internal error code and the associated error message to STDERR.
my $csv = Text::CSV_XS->new( { binary => 1, auto_diag => 1 } );
if ( $csv->parse( $run_test ) ) {
print {$out} map { "$_\n" } $csv->fields;
}
else {
print STDERR q{parse() failed on: }, $csv->error_input, qq{\n};
}

Perl - interpolate JSON string values

I have a json file like this:
"tool_name": {
"command": "$ENV{TOOL_BIN_DIR}/some_file_name",
"args": "some args"
}
I am using use:JSON from Perl 5.14. and using decode_json function to read the file and get data into perl hash.
But when I refer to this read data from code like this:
my $cmd = "$data->{tool_name}->{command}";
print $cmd;
I get
$ENV{TOOL_BIN_DIR}/some_file_name
How can I make perl resolve the value of this variable?
This example uses enviornment variable but in general if I want to use variables from JSON - how can I do that?
Using eval opens you to up to malicious or accidental damage: the string you are executing could contain any Perl code that may do anything at all to your system
It is preferable to use interpolate from the String::Interpolate module, which uses Perl's own interpolation engine that expands ordinary double-quoted strings at run time
This program sets up a value for the environment variable TOOL_BIN_DIR and expands all the values in the tool_name hash that contain a dollar $ or at # sigil
I've used Data::Dump to display the contents of the data after the interpolation
You may want to write a recursive subroutine that will process the values of all nested hashes and arrays if you don't know which values are likely to contain a value that needs to be expanded
use strict;
use warnings 'all';
use JSON 'decode_json';
use String::Interpolate 'interpolate';
use Data::Dump 'dd';
my $data = decode_json <<'__END_JSON__';
{
"tool_name": {
"command": "$ENV{TOOL_BIN_DIR}/some_file_name",
"args": "some args"
}
}
__END_JSON__
$ENV{TOOL_BIN_DIR} = 'tool_dir_test';
for ( values %{ $data->{tool_name} } ) {
$_ = interpolate($_) if /[\$\#]/;
}
dd $data;
output
{
tool_name => { args => "some args", command => "tool_dir_test/some_file_name" },
}
You can use eval like in:
my $cmd = '$ENV{HOME}/toto';
print eval('"' . $cmd . '"'), "\n";
But remember, this is very unsafe from a security point of view. You should probably avoid having to do this.

How do I use encode_json with string in Perl?

Here is my code that I try to open the file to get data and change it to UTF-8, then read each line and store it in variable my $abstract_text and send it back in JSON structure.
my $fh;
if (!open($fh, '<:encoding(UTF-8)',$path))
{
returnApplicationError("Cannot read abstract file: $path ($!)\nERRORCODE|111|\n");
}
printJsonHeader;
my #lines = <$fh>;
my $abstract_text = '';
foreach my $line (#lines)
{
$abstract_text .= $line;
}
my $json = encode_json($abstract_text);
close $fh;
print $json;
By using that code, I get this error;
hash- or arrayref expected (not a simple scalar, use allow_nonref to allow this)
error message also point out that the problem is in this line;
my $json = encode_json($abstract_text);
I want to send the data back as a string (which is in UTF-8). Please help.
I assume you're using either JSON or JSON::XS.
Both allow for non-reference data, but not via the procedural encode_json routine.
You'll need to use the object-oriented approach:
use strict; # obligatory
use warnings; # obligatory
use JSON::XS;
my $encoder = JSON::XS->new();
$encoder->allow_nonref();
print $encoder->encode('Hello, world.');
# => "Hello, world."