The propose of the script to grep some value from some data table in the ASCII files.
I modified the
script which I posted yesterday.
Now it barely works. I wonder if it is the proper way to move a file handle in this way.
The usage is still the same
myscript.pl targetfolder/*> result.csv
F is my file handle.
The argument I passed to the subroutine is the scalar $_, which is used by the if condition. When I want to move downward in my subroutine next if 1..4 will not work, so I repeat $a = <F>; a few times to achieve moving file handle downward.
But I think this is not a proper way to move the same file handle both in my main code and my subroutine. I am not sure it will really go through every line. I need your advice.
myscript.pl
#Report strip
use warnings;
use strict;
##Print the title
Tfms2();
##Print the title
print "\n";
#ff = <#ARGV>;
foreach $ff ( #ff ) {
open (F, $ff);
#fswf = #fschuck = #fsxpos = #fsypos = #fsdev = #csnom = "";
#cswf = #cschuck = #csxpos = #csypos = #csnom = ""; # is there an efficient way?
while (<F>) {
Mfms2();
Mfms3();
}
$r = 1;
while ( $r <= $#fswf ) { # because #fsws is the largest array
Cfms3();
print "\n";
$r++;
}
close (F);
}
##==========================================================================================
##Subs
##==========================================================================================
##FS II
sub Tfms2 {
print "FS_Wafer,FS_ChuckID,FS_pos_X,FS_pos_Y,FS_deviation,CS_Wafer,CS_ChuckID,CS_pos_X,CS_pos_Y,CS_NofWafer_Ident_Spot";
}
sub Mfms2 {
if ( /^F\sM\sSTATISTICS\sII$/ ) {
$a = (<F>);
$a = (<F>);
$a = (<F>);
$a = (<F>);
$a = (<F>);
$a = (<F>);
$a = (<F>);
$a = (<F>);
$r = 1;
#b = "";
while ( $a !~ /\+\-/ ) {
chomp $a;
#b = split / *\| */, $a;
$fswf[$r] = $b[1];
$fschuck[$r] = $b[2];
$fsxpos[$r] = $b[3];
$fsypos[$r] = $b[4];
$fsdev[$r] = $b[5];
$r++;
$a = (<F>);
#b = "";
}
}
}
##FS III
sub Mfms3 {
if ( /^F\sM\sSTATISTICS\sIII$/ ) {
$a = (<F>);
$a = (<F>);
$a = (<F>);
$a = (<F>);
$a = (<F>);
$a = (<F>);
$a = (<F>);
$a = (<F>);
$r = 1;
#b = "";
while ( $a !~ /\+\-/ ) {
chomp $a;
#b = split / *\| */, $a;
$cswf[$r] = $b[1];
$cschuck[$r] = $b[2];
$csxpos[$r] = $b[3];
$csypos[$r] = $b[4];
$csnom[$r] = $b[5];
$r++;
$a = (<F>);
#b = "";
}
}
}
sub Cfms3 {
print "$fswf[$r],$fschuck[$r],$fsxpos[$r],$fsypos[$r],$fsdev[$r],";
print "$cswf[$r],$cschuck[$r],$csxpos[$r],$csypos[$r],$csnom[$r],";
}
You have forgotten to tell us what the program is supposed to do, so it's very hard to be any help. There might be more information in your previous question, but we don't all read every question here and you don't even include a link to your previous question.
The answer to your question is that you can use seek() to move to an arbitrary position in a file. You might also find it useful to look at tell() which can tell you where you currently are in a file.
But I don't think that information will be particularly helpful to you as you seem rather confused about what you're trying to do. If you were to explain your task in a bit more detail, then I strongly suspect that we could help you (and I also suspect that help would largely involve rewriting your code from scratch). But until you give us details, all we can do is to point out some of the more obvious problems with your code.
You should always include use strict and use warnings in your code.
I don't think that #ff = <#ARGV> does what you think it does. I think you want #ff = #ARGV instead. And, even then, that feels like you're copying #ARGV pointlessly.
Having an array (#ff) and a scalar ($ff) with the same name is going to confuse someone at some point. And, more generally, you need to put more effort into naming your variables clearly.
Please use the three-argument version of open() along with lexical filehandles. You should also always check the return code from open() - open my $fh, "<", $ff or die "Could not open $ff: $!".
Your lines like #fswf=#fschuck=#fsxpos=#fsypos=#fsdev=#csnom="" aren't doing what you think they are doing. You end up with a lot of arrays each of which contain a single element. Better to just declare the arrays with my - Perl will create them empty (my (#fswf, #fschuck, #fsxpos, ...)).
If you really need to skip eight lines when reading from your file then it's much clearer to write: $a = <F> for 1 .. 8.
You are making heavy use of global variables. There's a good reason why this goes against software engineering best practices. It makes your code far more fragile than it needs to be.
All in all, you seem to be guessing at a solution here, and that's never a good approach. As I said above, we'd like to help you, but without a lot more information that's going to be almost impossible.
Update: Looking at your code a bit more closely, I see that you are storing the data that you parse from the files in a number of arrays. Each array contains data from a single column of the input file and in order to get all of the data from a single row, you need to access each of these arrays using the same index value. This isn't a very good idea. Splitting linked data across different variables is a recipe for disaster. A far better idea would be to store each record in a hash (where the key would denote the data item that is stored) and to store references to all of these hashes in an array. This brings all of your data together in a single variable.
Updated update: I don't know enough about your data to be sure, but here's the kind of approach I would take. I've only parsed the data and then used Data::Dumper to display the parsed data structure. Producing better output is left as an exercise for the reader :-)
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use Data::Dumper;
#ARGV or die "Usage: $0 file [file...]\n";
# Define two list of keys for the different types
my %cols = (
II => [qw(wf chuck xpos ypos dev)],
III => [qw(wf chuck xpos ypos nom)],
);
my #data; # To store the parsed data
my $cur_type; # Keep track of the current type of record
while (<>) {
# Look for a record header and determine which type of
# record we're dealing with (I think 'II' or 'III').
if (/^F M STATISTICS (III?)$/) {
$cur_type = $1;
next;
}
# Skip lines that are just headers/footers
next if /\+[-=]/;
# Skip lines that don't include data
next unless /\d/;
chomp;
# Remove the start and end of the line
s/^\|\s+//;
s/\s+\|$//;
# Store the data in a hash with the correct keys
# for this type of record
my %rec;
#rec{#{$cols{$cur_type}}} = split /\s+\|\s+/;
# Store a reference to our hash in #data
push #data, \%rec;
}
# Dump the contents of #data
say Dumper \#data;
Related
Is there any way to get Perl to convert the stringified version e.g (ARRAY(0x8152c28)) of an array reference to the actual array reference?
For example
perl -e 'use Data::Dumper; $a = [1,2,3];$b = $a; $a = $a.""; warn Dumper (Then some magic happens);'
would yield
$VAR1 = [
1,
2,
3
];
Yes, you can do this (even without Inline C). An example:
use strict;
use warnings;
# make a stringified reference
my $array_ref = [ qw/foo bar baz/ ];
my $stringified_ref = "$array_ref";
use B; # core module providing introspection facilities
# extract the hex address
my ($addr) = $stringified_ref =~ /.*(0x\w+)/;
# fake up a B object of the correct class for this type of reference
# and convert it back to a real reference
my $real_ref = bless(\(0+hex $addr), "B::AV")->object_2svref;
print join(",", #$real_ref), "\n";
but don't do that. If your actual object is freed or reused, you may very well
end up getting segfaults.
Whatever you are actually trying to achieve, there is certainly a better way.
A comment to another answer reveals that the stringification is due to using a reference as a hash key. As responded to there, the better way to do that is the well-battle-tested
Tie::RefHash.
The first question is: do you really want to do this?
Where is that string coming from?
If it's coming from outside your Perl program, the pointer value (the hex digits) are going to be meaningless, and there's no way to do it.
If it's coming from inside your program, then there's no need to stringify it in the first place.
Yes, it's possible: use Devel::FindRef.
use strict;
use warnings;
use Data::Dumper;
use Devel::FindRef;
sub ref_again {
my $str = #_ ? shift : $_;
my ($addr) = map hex, ($str =~ /\((.+?)\)/);
Devel::FindRef::ptr2ref $addr;
}
my $ref = [1, 2, 3];
my $str = "$ref";
my $ref_again = ref_again($str);
print Dumper($ref_again);
The stringified version contains the memory address of the array object, so yes, you can recover it. This code works for me, anyway (Cygwin, perl 5.8):
use Inline C;
#a = (1,2,3,8,12,17);
$a = \#a . "";
print "Stringified array ref is $a\n";
($addr) = $a =~ /0x(\w+)/;
$addr = hex($addr);
$c = recover_arrayref($addr);
#c = #$c;
print join ":", #c;
__END__
__C__
AV* recover_arrayref(int av_address) { return (AV*) av_address; }
.
$ perl ref-to-av.pl
Stringified array ref is ARRAY(0x67ead8)
1:2:3:8:12:17
I'm not sure why you want to do this, but if you really need it, ignore the answers that use the tricks to look into memory. They'll only cause you problems.
Why do you want to do this? There's probably a better design. Where are you getting that stringified reference from.
Let's say you need to do it for whatever reason. First, create a registry of objects where the hash key is the stringified form, and the value is a weakened reference:
use Scalar::Util qw(weaken);
my $array = [ ... ];
$registry{ $array } = $array;
weaken( $registry{ $array } ); # doesn't count toward ref count
Now, when you have the stringified form, you just look it up in the hash, checking to see that it's still a reference:
if( ref $registry{$string} ) { ... }
You could also try Tie::RefHash and let it handle all of the details of this.
There is a longer example of this in Intermediate Perl.
In case someone finds this useful, I'm extending tobyink's answer by adding support for detecting segmentation faults. There are two approaches I discovered. The first way locally replaces $SIG{SEGV} and $SIG{BUS} before dereferencing. The second way masks the child signal and checks if a forked child can dereference successfully. The first way is significantly faster than the second.
Anyone is welcome to improve this answer.
First Approach
sub unstringify_ref($) {
use bigint qw(hex);
use Devel::FindRef;
my $str = #_ ? shift : $_;
if (defined $str and $str =~ /\((0x[a-fA-F0-9]+)\)$/) {
my $addr = (hex $1)->bstr;
local $#;
return eval {
local $SIG{SEGV} = sub { die };
local $SIG{BUS} = sub { die };
return Devel::FindRef::ptr2ref $addr;
};
}
return undef;
}
I'm not sure if any other signals can occur in an attempt to access illegal memory.
Second Approach
sub unstringify_ref($) {
use bigint qw(hex);
use Devel::FindRef;
use Signal::Mask;
my $str = #_ ? shift : $_;
if (defined $str and $str =~ /\((0x[a-fA-F0-9]+)\)$/) {
my $addr = (hex $1)->bstr;
local $!;
local $?;
local $Signal::Mask{CHLD} = 1;
if (defined(my $kid = fork)) {
# Child -- This might seg fault on invalid address.
exit(not Devel::FindRef::ptr2ref $addr) unless $kid;
# Parent
waitpid $kid, 0;
return Devel::FindRef::ptr2ref $addr if $? == 0;
} else {
warn 'Unable to fork: $!';
}
}
return undef;
}
I'm not sure if the return value of waitpid needs to be checked.
#! /usr/bin/perl
use strict;
use warnings;
use File::stat;
my $file_name = 0;
my $info = 0;
my $ret_mode = 0;
my $size;
my $last_mod;
my #array_file;
my $index = 0;
my #array_mode;
my #array_size;
my #array_last_mod;
foreach(#ARGV){
$file_name = $_;
$info = stat($file_name);
$size = $info->size;
$last_mod = scalar(localtime($info->mtime));
$ret_mode = $info->mode;
$ret_mode = $ret_mode & 0777;
$array_file[$index] = ($file_name);
$array_mode[$index] = ($ret_mode);
$array_size[$index] = ($size);
$array_last_mod[$index] = ($last_mod);
$ret_mode = 0;
$index++;
}
my #array_arrays = (#array_file, #array_mode, #array_size, #array_last_mod);
my $array_ref = \#array_arrays;
my $i = 0;
for(#$array_ref){
print "#$array_ref[$i]\n";
$i++;
}
I have created an array of arrays and I want to print the filename,mmode,size and last access time from the array of arrays created. Its not printing any values with,
for(#$array_ref){
print "#$array_ref[$i]\n";
$i++;
}
my #array_arrays = (#array_file, #array_mode, #array_size, #array_last_mod);
This statement does not create an array of arrays. Instead, It flattens the various arrays into one big flat list and then assigns that to #array_arrays. You want to assign array references. Obtain those with the reference operator \:
my #array_arrays = (\#array_file, \#array_mode, \#array_size, \#array_last_mod);
or with the shortcut
my #array_arrays = \(#array_file, #array_mode, #array_size, #array_last_mod);
Even then, your last foreach-loop is wrong. You probably meant
for my $i (0 .. $#{ $array_arrays[0] }) {
for my $aref (#array_arrays) {
print $aref->[$i], "\n";
}
}
or something similar.
Your code style could be improved.
Please don't declare all your variables at the top. Declare them in the tightest scope possible. Try to declare them at the point of initialization, e.g.
for my $file_name (#ARGV) {
my $info = stat($file_name);
my size = $info->size;
...
}
Don't prefix your array names with array_. The # sigil and/or subscripting with the [...] operator makes it clear that these are arrays.
$ret_mode & 0777 – The result should be $ret_mode itself: 0777 is 0b111111111. I.e. this removes all but the last 9 bits – you wouldn't care if there were more to the left.
$last_mod = scalar(localtime($info->mtime)); – due to the scalar assignment, localtime is already executed in scalar context. No need to make this explicit.
my $index = 0; ... for (...) { $array[$index] = ...; $index++ }. Please not. Just use push: for (...) { push #array, ... }. Don't maintain indices yourself unless you have to.
$ret_mode = 0; Why? you assign a new value during the next iteration anyway. Note that you should declare this variables inside the loop (see my point about tight scopes), which would create a new variable in each iteration, making this even more useless.
my $array_ref = \#array_arrays; .. #$array_ref[$i]. Isn't this a bit backwards? $array_arrays[$i] would work just as well. Note that in your defeferencing, # is probably the wrong sigil. You meant $$array_ref[$i].
Let's try a little something different.
First off, there's a nice syntax using the -> for referenced arrays and hashes. Here I am going to make a array of people. I'll make a hash of %person that contains all that person's information:
my %person;
my $person{NAME} = "Bob";
my $person{JOB} = "Programmer";
my $person{PHONE} = "555-1234";
Now, I'll put it into an array:
my #array
my $array[0] = \%person;
I could reference the person in the array this way:
print ${$array[0]}{NAME} . "\n"; #Prints Bob
print ${$array[0]}{JOB} . "\n"; #Prints Porgrammer
But, Perl gives me a nice clean way to do this:
print $array[0]->{NAME} . "\n"; #Prints Bob
print $array[0]->{JOB} . "\n"; #Prints Progammer
In fact, I could skip the hash all together. Here I am adding Jill to my array:
$array[1]->{NAME} = "Jill";
$array[1]->{JOB} = "DBA";
$array[1]->{PHONE} = "555-5555";
You can see this is a much simpler way to use references. It's easier to see what is going on and takes fewer lines of code.
You can refer to an array of an array like this:
$myarray[1]->[3] = 42;
Or have a hash which stores an array. In this day and age, who has only a single phone number?:
$person[1]->{PHONE}->[0] = "555-4567";
$person[1]->{PHONE}->[1] = "555-4444";
Or, to make it even more complex we could have a hash of a hash of an array:
$person[1]->{PHONE}->{CELL}->[0] = "555-1111";
$person[1]->{PHONE}->{CELL}->[1] = "555-2222";
$person[1]->{PHONE}->{HOME}->[0] = "555-3333";
$person[1]->{PHONE}->{JOB}->[0] = "555-4444";
$person[1]->{PHONE}->{JOB}->[1] = "555-5555";
Using this syntax will really help clean up a lot of your code. You won't have to store the information into individual structures that are then only used to make references. Instead, you can simple setup your structure the way you want without intermediary steps.
Now to your problem: You're trying to store a bunch of information about files into a series of arrays. What you're hoping is that $array_mode[1] goes with $array_file[1] and you have to keep all of these arrays in sync. This is a pain and it is complex.
The entire purpose of using references is to eliminate this need for multiple variables. If you're going to use references, why not simply store your entire file structure into a single array.
What you really, really want is an array of hash references. And, that hash reference will be keyed based upon your file attributes. Here is your code restructured into using an array of hash references. I didn't even bother to check the rest of it. For example, I'm not sure how your localtime thing will work:
use strict;
use warnings;
use feature qw(say);
use File::stat;
my #files;
for my $file ( #ARGV ) {
my $info = stat( $file );
my $file = {}; #This will be a reference to a hash
$file->{NAME} = $file;
$file->{SIZE} = $info->size;
$file->{RET_MODE} = $info->mode & 0777;
$file->{LAST_MOD} = localtime $info->mtime; #Does this work?
push #files, $file #Pushes the hash reference onto the array
}
That's way shorter and cleaner. Plus, you know that $files[0]->{NAME} goes with $files[1]->{SIZE}, and if you remove $files[0] from your array, or transfer it to another variable, all of the attributes of that file go together.
Here's how you'd print it out:
for my $file ( #files ) {
say "File Name: " . $file->{NAME};
say "File Size: " . $file->{SIZE};
say "Last Modified: " . $file->{LAST_MOD};
say "File Mode: " . $file->{RET_MODE};
}
Simple and easy to do.
However, I would argue what you really want is a hash of hashes. Let your file name be the key to your main hash, and let {SIZE}, {LAST_MOD}, and {RET_MODE} be the keys to your sub hash:
my %files = {}; #This is a hash of hashes
for my $file_name ( #ARGV ) {
my $info = stat( $file );
$files{$file_name}->{SIZE} = $info->size;
$files{$file_name}->{RET_MODE} = $info->mode & 0777;
$files{$file_name}->{LAST_MOD} = localtime $info->mtime; #Does this work?
}
Now if someone asks, "When was foo.txt last modified?", you can say:
say "File 'foo.txt' was last modified on " . $file{foo.txt}->{LAST_MOD};
And to print out your entire structure:
for my $file_name ( sort keys %files ) {
say "File: $file_name";
for my attribute ( sort keys %{ $file_name } ) {
say " $attribute: " . $files{$file_name}->{$attribute};
}
}
Next step is to learn about Object Oriented Perl! Object Oriented Perl uses these types of references, but will greatly simplify the handling of these references, so you make fewer programming mistakes.
So I have a text file with four sets of data on a line, such as aa bb username password. So far I have been able to parse through the first line of the file using substrings and indices, and assigning each of the four to variables.
My goal is to use an array and chomp through each line and assign them to the four variables, and than to match an user inputted argument to the first variable, and use the four variables in that correct line.
For example, this would be the text file:
"aa bb cc dd"
"ee ff gg hh"
And depending on whether the user inputs "aa" or "ee" as the argument, it would use that line's set of arguments in the code.
I am trying to get up a basic array and chomp through it based on a condition for the first variable, essentially.
Here is my code for the four variables for the first line, but like I said, this only works for the first line in the text file:
local $/;
open(FILE, $configfile) or die "Can't read config file 'filename' [$!]\n";
my $document = <FILE>;
close (FILE);
my $string = $document;
my $substring = " ";
my $Index = index($string, $substring);
my $aa = substr($string, 0, $Index);
my $newstring = substr($string, $Index+1);
my $Index2 = index($newstring, $substring);
my $bb = substr($newstring, 0, $Index2);
my $newstring2 = substr($newstring, $Index2+1);
my $Index3 = index($newstring2, $substring);
my $cc = substr($newstring2, 0, $Index3);
my $newstring3 = substr($newstring2, $Index3+1);
my $Index4 = index($newstring3, $substring);
my $dd = substr($newstring3, 0, $Index4);
First of all, you can parse your whole line using split instead of running index and substring on them:
my ( $aa, $bb, $cc, $dd ) = split /\s+/, $line;
Even better, use an array:
my #array = split /\s+/, $line;
I think you're saying that you need to store each array of command parts into another array of lines. Is that correct? Take a look at this tutorial on references available in the Perl Documentation.
Perl has three different types of variables. The problem is that each of the types of variables of these stores only a single piece of data. Arrays and hashes may store lots of data, but only one piece of data can be stored in each element of a hash or array.
References allow you to get around this limitation. A reference is simply a pointer to another piece of data. For example, if $line = aa bb cc dd, doing this:
my #command_list = split /\s+/ $line;
Will give you the following:
$command_list[0] = "aa";
$command_list[1] = "bb";
$command_list[2] = "cc";
$command_list[3] = "dd";
You want to store #command_list into another structure. What you need is a reference to #command_list. To get a reference to it, you merely put a backslash in front of it:
my $reference = \#command_list;
This could be put into an array:
my #array;
$array[0] = $reference;
Now, I'm storing an entire array into a single element of an array.
To get back to the original structure from the reference, you put the correct sigil. Since this is an array, you put # in front of it:
my #new_array = #{ $reference };
If you want the first item in your reference without using having to transport it into another array, you could simply treat #{ $reference } as an array itself:
${ $reference }[0] = "aa";
Or, use the magic -> which makes the syntax a bit cleaner:
$reference->[0] = "aa";
Go through the tutorial. This will help you understand the full power of references, and how they can be used. Your program would look something like this:
use strict;
use warnings;
use feature qw(say); #Better print that print
use autodie; #Kills your program if the file can't be open
my $file = [...] #Somehow get the file you're reading in...
open my $file_fh, "<", $file;
my #command_list;
while ( my $line = <$file_fh> ) {
chomp $line;
my #line_list = split /\s+/, $line;
push #command_list, \#line_list;
}
Note that push #command_list, \#line_list; is pushing a reference to one array into another. How do you get it back out? Simple:
for my $cmd_line_ref ( #command_list ) {
my $command = $cmd_line_ref->[0]; #This is the first element in your command
next unless $command eq $user_desires; # However you figure out what the user wants
my $line = join " ", #{ $cmd_line_ref } #Rejoins your command line once again
??? #Profit
}
Read the tutorial on references, and learn about join and split.
You are reading the whole file in the my $document = <FILE> line.
Try something like:
my #lines;
open my $file, '<', $configfile or die 'xxx';
while( <$file> ) {
chomp;
push #lines, [ split ]
}
And now #lines has an array of arrays with the information you need.
(EDIT) don't forget to lose the local $/; -- it's what is making you read the whole file at once.
my $document = <FILE> is reading in only the first line. Try using a while loop.
If you want to read all lines of the file at once - assuming it's a small file - you may want to use File::Slurp module:
use File::Slurp;
my #lines = File::Slurp::read_file($configfile);
foreach my $line (#lines) {
# do whatever
Also, you can use CPAN modules to split the strings into fields.
If they are single-space separated, simply read the whole file using a standard CSV parser (you can configure Text::CSV_XS to use any characater as separator). Example here: How can I parse downloaded CSV data with Perl?
If they are separated by random amount of whitespace, use #massa's advice below and use split function.
Can anyone tell me where I am wrong? I can't figure it out....
Basically what my code is trying to do is to read the files and create a hash for each file, these hashes are organized into on hash. The user would input two parameters, one is the key of the outer hash, and the other is for the one inside.
The ones I input are city and PIT; the same as the parameter I wrote before the line that breaks down....
I tried thousands of times, I keep getting this error: Can't use an undefined value as a HASH reference I have commented that line out in the code.
The two files are cities.txt; school.txt.
Their content are just as below:
PIT\tPittsburgh
NY\tNewYork
#!/bin/perl -w
use strict;
use Data::Dumper;
our %hash_all = ();
sub readHash{
my #vars = #_;
my $filename = $vars[0];
my %iptable = ();
if(open(IN,$filename.".txt")) {
while(<IN>) {
my #tmp = split(/\t/);
$iptable{$tmp[0]} = $tmp[1];
}
}
return %iptable;
}
sub loadAll{
my %school = readHash("school");
my %city = readHash("cities");
$hash_all{school} = \%school;
$hash_all{city} = \%city;
print Dumper(\%hash_all);
}
sub queryValue{
my #pars = #_;
my $key1 = $pars[0];
my $key2 = $pars[1];
print "key1".$key1;
print "key2".$key2;
print Dumper(\%hash_all);
my %temp = %{$hash_all{"city"}};#THIS LINE WORKS
print $temp{"PIT"}; #THIS LINE WORKS
my %temp2 = %{$hash_all{$key1}};#THIS LINE HAS AN ERROR
print $temp2{$key2};
}
loadAll();
my $par1 = <>;
my $par2 = <>;
queryValue($par1,$par2);
Your problem is probably that when you read in $par1 and $par2, they include newlines at the end. So you end up looking for a hash key like "city\n", which is not the same as "city".
Make sure you use chomp on your input parameters, like chomp($par1) . That should take care of it.
Is there any way to get Perl to convert the stringified version e.g (ARRAY(0x8152c28)) of an array reference to the actual array reference?
For example
perl -e 'use Data::Dumper; $a = [1,2,3];$b = $a; $a = $a.""; warn Dumper (Then some magic happens);'
would yield
$VAR1 = [
1,
2,
3
];
Yes, you can do this (even without Inline C). An example:
use strict;
use warnings;
# make a stringified reference
my $array_ref = [ qw/foo bar baz/ ];
my $stringified_ref = "$array_ref";
use B; # core module providing introspection facilities
# extract the hex address
my ($addr) = $stringified_ref =~ /.*(0x\w+)/;
# fake up a B object of the correct class for this type of reference
# and convert it back to a real reference
my $real_ref = bless(\(0+hex $addr), "B::AV")->object_2svref;
print join(",", #$real_ref), "\n";
but don't do that. If your actual object is freed or reused, you may very well
end up getting segfaults.
Whatever you are actually trying to achieve, there is certainly a better way.
A comment to another answer reveals that the stringification is due to using a reference as a hash key. As responded to there, the better way to do that is the well-battle-tested
Tie::RefHash.
The first question is: do you really want to do this?
Where is that string coming from?
If it's coming from outside your Perl program, the pointer value (the hex digits) are going to be meaningless, and there's no way to do it.
If it's coming from inside your program, then there's no need to stringify it in the first place.
Yes, it's possible: use Devel::FindRef.
use strict;
use warnings;
use Data::Dumper;
use Devel::FindRef;
sub ref_again {
my $str = #_ ? shift : $_;
my ($addr) = map hex, ($str =~ /\((.+?)\)/);
Devel::FindRef::ptr2ref $addr;
}
my $ref = [1, 2, 3];
my $str = "$ref";
my $ref_again = ref_again($str);
print Dumper($ref_again);
The stringified version contains the memory address of the array object, so yes, you can recover it. This code works for me, anyway (Cygwin, perl 5.8):
use Inline C;
#a = (1,2,3,8,12,17);
$a = \#a . "";
print "Stringified array ref is $a\n";
($addr) = $a =~ /0x(\w+)/;
$addr = hex($addr);
$c = recover_arrayref($addr);
#c = #$c;
print join ":", #c;
__END__
__C__
AV* recover_arrayref(int av_address) { return (AV*) av_address; }
.
$ perl ref-to-av.pl
Stringified array ref is ARRAY(0x67ead8)
1:2:3:8:12:17
I'm not sure why you want to do this, but if you really need it, ignore the answers that use the tricks to look into memory. They'll only cause you problems.
Why do you want to do this? There's probably a better design. Where are you getting that stringified reference from.
Let's say you need to do it for whatever reason. First, create a registry of objects where the hash key is the stringified form, and the value is a weakened reference:
use Scalar::Util qw(weaken);
my $array = [ ... ];
$registry{ $array } = $array;
weaken( $registry{ $array } ); # doesn't count toward ref count
Now, when you have the stringified form, you just look it up in the hash, checking to see that it's still a reference:
if( ref $registry{$string} ) { ... }
You could also try Tie::RefHash and let it handle all of the details of this.
There is a longer example of this in Intermediate Perl.
In case someone finds this useful, I'm extending tobyink's answer by adding support for detecting segmentation faults. There are two approaches I discovered. The first way locally replaces $SIG{SEGV} and $SIG{BUS} before dereferencing. The second way masks the child signal and checks if a forked child can dereference successfully. The first way is significantly faster than the second.
Anyone is welcome to improve this answer.
First Approach
sub unstringify_ref($) {
use bigint qw(hex);
use Devel::FindRef;
my $str = #_ ? shift : $_;
if (defined $str and $str =~ /\((0x[a-fA-F0-9]+)\)$/) {
my $addr = (hex $1)->bstr;
local $#;
return eval {
local $SIG{SEGV} = sub { die };
local $SIG{BUS} = sub { die };
return Devel::FindRef::ptr2ref $addr;
};
}
return undef;
}
I'm not sure if any other signals can occur in an attempt to access illegal memory.
Second Approach
sub unstringify_ref($) {
use bigint qw(hex);
use Devel::FindRef;
use Signal::Mask;
my $str = #_ ? shift : $_;
if (defined $str and $str =~ /\((0x[a-fA-F0-9]+)\)$/) {
my $addr = (hex $1)->bstr;
local $!;
local $?;
local $Signal::Mask{CHLD} = 1;
if (defined(my $kid = fork)) {
# Child -- This might seg fault on invalid address.
exit(not Devel::FindRef::ptr2ref $addr) unless $kid;
# Parent
waitpid $kid, 0;
return Devel::FindRef::ptr2ref $addr if $? == 0;
} else {
warn 'Unable to fork: $!';
}
}
return undef;
}
I'm not sure if the return value of waitpid needs to be checked.