Improve company productivity with a Business Account.Sign Up

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 715
  • Last Modified:

Modification of a read-only value attempted...

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
mjcoyne
Asked:
mjcoyne
  • 6
  • 4
1 Solution
 
Adam314Commented:
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
 
Adam314Commented:
eg:

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

Open in new window

0
 
mjcoyneAuthor Commented:
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
The 14th Annual Expert Award Winners

The results are in! Meet the top members of our 2017 Expert Awards. Congratulations to all who qualified!

 
Adam314Commented:
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
 
mjcoyneAuthor Commented:
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
 
Adam314Commented:
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
 
mjcoyneAuthor Commented:
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
 
Adam314Commented:
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
 
mjcoyneAuthor Commented:
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
 
Adam314Commented:
>>array is altered merely by iterating through it...
Only if you change the iterated variable inside of the loop.

0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Upgrade your Question Security!

Your question, your audience. Choose who sees your identity—and your question—with question security.

  • 6
  • 4
Tackle projects and never again get stuck behind a technical roadblock.
Join Now