Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people, just like you, are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
Solved

Modification of a read-only value attempted...

Posted on 2007-11-15
10
618 Views
Last Modified: 2012-06-21
Hi Experts --

I've been looking at this too long -- I can't spot the (probably obvious) problem.  I'm hoping a fresh set of eyes will help.

Here is a stripped down version of a larger script.  The actual script has many more entries in the %species hash, does some stuff between the "foreach" loops, actually does some work on the downloaded files, etc., but this is sufficient to trigger the error:

"Modification of a read-only value attempted at C:\Documents and Settings\Mike\Desktop\bf gene data\test.pl line 31."

The FTP portion retrieves the files fine -- in fact, once I have them, I can comment out that entire piece and the error still occurs.

Note that the script does not die at opening the file (line 30), and that the "for" loop and construction of the $file name variable is the same for both the FTP and the "open file" sections, but the script only dies when it attempts to read the file; the FTP portion works as expected.

I've tried all the obvious stuff: the files are not read-only, are in fact there, and can be opened and read if named explictly.

#!/usr/bin/perl -w
use strict;
use Net::FTP;
use Cwd 'abs_path';

my %species = (
    'Bacteroides_fragilis_NCTC_9434' => 'CR626927',
    );

my $home = abs_path(".");
my $datapath = "$home/srRNA";

mkdir $datapath unless (-e $datapath);
chdir ($datapath) or die;

foreach my $organism (sort keys %species) {
    my $ftp = Net::FTP->new("ftp.ncbi.nih.gov", Debug => 0) or die "Cannot connect to ftp.ncbi.nih.gov: $@";
    $ftp->login("anonymous",'-anonymous@') or die "Cannot login ", $ftp->message;
    $ftp->cwd("/genbank/genomes/Bacteria/$organism/") or die "Cannot change working directory ", $ftp->message;
    for ('.ptt', '.rnt') {
        my $file = $species{$organism} . $_;
        $ftp->get($file) or die "get $file failed ", $ftp->message;
    }
    $ftp->quit;
}

foreach my $organism (sort keys %species) {
    for ('.ptt', '.rnt') {
        my $file = $species{$organism} . $_;
        open (TAB, $file) or die "Can't open $file: $!\n";
        while (<TAB>) {
            next unless /^\d+\.\.\d+/;
        }
        close (TAB);
    }
}

Thanks for your help...
0
Comment
Question by:mjcoyne
  • 6
  • 4
10 Comments
 
LVL 39

Expert Comment

by:Adam314
ID: 20290467
In this line (28):
    for ('.ptt', '.rnt') {
Sets $_ to the same as '.ppt' and '.rnt' - not a copy of the value, but to the same thing (similar to a reference).  
So then the while statement (line 31) tries to change $_, but it can't.

0
 
LVL 39

Expert Comment

by:Adam314
ID: 20290490
eg:

my @list = qw(item1 item2 item3);
for (@list) {
	s/i/I/;
}
 
print join(",", @list) . "\n";  #Notice uppercase I

Open in new window

0
 
LVL 17

Author Comment

by:mjcoyne
ID: 20291352
I agree that $_ is responsible for this error, as it's the only read-only value involved in the script.  However, I can't grasp the difference -- especially in terms of $_ -- between these two constructs:

#!/usr/bin/perl -w
use strict;

my @files = ('CR626927.ptt', 'CR626927.rnt');

# this works
for (@files) {
    open (TAB, $_) or die "Can't open $_: $!\n";
    while (<TAB>) {
        print unless /^\d+\.\.\d+/;
    }
    close (TAB);
}

# this does not work
for ('CR626927.ptt', 'CR626927.rnt') {
    open (TAB, $_) or die "Can't open $_: $!\n";
    while (<TAB>) {
        print unless /^\d+\.\.\d+/;
    }
    close (TAB);
}

0
Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

 
LVL 39

Accepted Solution

by:
Adam314 earned 500 total points
ID: 20291912
In the first one:
@files is an array variable, read/write
$_ is those values (eg: a C/C++ pointer to those elements), not a copy of their values
If you look at files after the "for (@files)", you'll see that it has changed.

In the second one:
$_ is now a pointer to a constant (the string 'CR626927.ptt' or 'CR626927.rnt')
The while loop is trying to change $_


Look at this version of your first code:
my @files = ('CR626927.ptt', 'CR626927.rnt');

# this works
$" = ",";   #just to make printing easier...
print "**\@files 1 = @files\n";   #@files is unchanged as of here
for (@files) {
    #Here, $_ is a pointer to the actual data in @files, not a copy of it
    open (TAB, $_) or die "Can't open $_: $!\n";
    while (<TAB>) {
        #The first time through the outer for loop, the first element (0th) in @files is changed
        #The second time through the outer for loop, the second element (1st) in @files is changed
        print "**\@files 2 = @files\n";
        print unless /^\d+\.\.\d+/;
    }
    close (TAB);
}

print "**\@files 3 = @files\n";  
#here:
#    $files[0] is the last line of CR626927.ptt
#    $files[1] is the last line of CR626927.rnt

0
 
LVL 17

Author Comment

by:mjcoyne
ID: 20292686
OK, maybe I'm being thick (likely).  But in your first answer (20290467), you said the reason the "for ('.ptt', '.rnt') {" construct *doesn't* work is because it "sets $_ to the same as '.ppt' and '.rnt' - not a copy of the value".  But, in your latest answer (20291912) you're saying the reason "for (@files) {" construct *does* work is because "$_ is those values...not a copy of their values".  Aren't these answers contridictory?

I know you're right, and that the problem is stemming from the difference between a value and a pointer to a value, but I'm failling to see some subtle difference here -- the value of $_ changes all the time without issues (for instance, the second time through the "for" loop), and I can't get why the "while (<TAB>) {" line triggers this error -- if the "for" loop can change the value of $_ (from CR626927.ptt to CR626927.rnt), why can't the "while" loop?

All "while" loops used in this manner constantly change the value of $_; it changes each time a new line is read -- after all, in the examples above, "print unless /^\d+\.\.\d+/;" is really saying "print $_ unless /^\d+\.\.\d+/;".

Thanks for your help.  I've already re-written the code to abolish the problem, but I'd like to more fully understand what caused this issue to rear its ugly head in the first place so I can avoid falling into the same trap again...
0
 
LVL 39

Expert Comment

by:Adam314
ID: 20293105
In a for loop, $_ is a pointer to the values.  Changing $_ changes the original values.
In a while loop, the value of $_ is changed - it is not a pointer.

The reason it *doesn't* work in the "for ('.ptt', '.rnt')" and *does* work in the "for (@files)" is because
    In both cases, $_ is a pointer.
    @files is a variable that you *can* write to.  The while loop changes the values in @files.
    '.ptt' is a literal that you can *not* write to.  The while loop tries to change the value of a literal.
So, when $_ is a pointer to the literal '.ptt', you can not change it's value.  The while loop tries to set the value of $_, and this is the problem.
When $_ is a pointer to a value in @files, you can change it's value.  The while loop tries to change the value of $_, and it does - and this change is reflected in @files.

The previous example shows this, by having @files different after the loop.

Here is an example using subroutines instead.  When you pass parameters to a subroutine, you are actually passing pointers (perl handles all of this for you).  If you try to modify @_, you will be modifying the original parameter.  Normally, a subroutine makes a copy of all of it's parameters, either with shift, or @_.

sub TryToChange {
      #Subroutine actually gets a pointer to the passed parameters
      print "First parameter: $_[0]\n";
      $_[0] = 'Changed in TryToChange';
}


my $var = 'Original Value';
print "var=$var\n";
TryToChange($var);   # $var will be changed, because a pointer to $var was changed
print "var=$var\n";

TryToChange('literal');   # no problem, changing a copy of a literal
0
 
LVL 17

Author Comment

by:mjcoyne
ID: 20293851
OK, I'm getting it.  But, as your second-to-last example shows, @files is irreversibly changed by populating it with filenames, and then using it to open files:

.
<snip>
.
print "**\@files 3 = @files\n";  
#here:
#    $files[0] is [now] the last line of CR626927.ptt   # mjc: but it was CR626927.ptt originally
#    $files[1] is [now] the last line of CR626927.rnt   # mjc: but it was CR626927.rnt originally

So, this means you could only use this array of filenames once in this manner?  Say you wanted to use it to open files, as above, and then later on, use it with unlink to clean up.  It would fail, right?

It seems bizzare to me that even though @files is scoped outside of the "for" loop, the act of using it within a "for" loop destroys it for subsequent use.  It also is bizzare to me that I've never come across this Perl behavior before...

I suppose using "foreach my $filename (@files) {", which is my usual construct, would be the way to avoid this....
0
 
LVL 39

Expert Comment

by:Adam314
ID: 20296023
The @files is only affected if $_ is changed within the for loop.  

$"=", ";
my @files = ('file1.txt', 'file2.txt');
print "\@files=@files\n";
for (@files) {
      print "\$_=$_\n";
}
$_='new test';    #has no effect on @files
print "\@files=@files\n";     #Unchanged from before

As for "destroying"... it can be helpful.  If you want to change everyitem in an array:
    my @files = ('/path/to/file1', '/another/path/to/a/file2', '/one/more/path/to/a/file3');
    $_=basename($_) foreach (@files);   #This works because $_ is a pointer to the actual value, not a copy...
    print "files=@files\n";

Or:
    my @files = ('/path/to/file1', '/another/path/to/a/file2', '/one/more/path/to/a/file3');
    s|.*\/|| foreach (@files);    #This works because $_ is a pointer to the actual value, not a copy...
    print "files=@files\n";
0
 
LVL 17

Author Comment

by:mjcoyne
ID: 20297372
I'll admit it's still troubling to me that an array is altered merely by iterating through it...

However, I understand that this behavior is also the source of my  "Modification of a read-only value..." error, since Perl obviously needs a write permissible variable under the circumstances.

Thank you, Adam.
0
 
LVL 39

Expert Comment

by:Adam314
ID: 20305707
>>array is altered merely by iterating through it...
Only if you change the iterated variable inside of the loop.

0

Featured Post

Free Tool: SSL Checker

Scans your site and returns information about your SSL implementation and certificate. Helpful for debugging and validating your SSL configuration.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

In the distant past (last year) I hacked together a little toy that would allow a couple of Manager types to query, preview, and extract data from a number of MongoDB instances, to their tool of choice: Excel (http://dilbert.com/strips/comic/2007-08…
Checking the Alert Log in AWS RDS Oracle can be a pain through their user interface.  I made a script to download the Alert Log, look for errors, and email me the trace files.  In this article I'll describe what I did and share my script.
Explain concepts important to validation of email addresses with regular expressions. Applies to most languages/tools that uses regular expressions. Consider email address RFCs: Look at HTML5 form input element (with type=email) regex pattern: T…

856 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question