Martin Janitor
Martin Janitor

Reputation: 31

In Perl, why does print not generate any output after I close STDOUT?

I have the code:

open(FILE, "<$new_file") or die "Cant't open file \n";
@lines=<FILE>;

close FILE;

open(STDOUT, ">$new_file") or die "Can't open file\n";
$old_fh = select(OUTPUT_HANDLE);
$| = 1;
select($old_fh);

for(@lines){
    s/(.*?xsl.*?)xsl/$1xslt/;
    print;
}
close(STDOUT);  
STDOUT -> autoflush(1);
print "file changed";

After closing STDOUT closing the program does not write the last print print "file changed". Why is this? *Edited* Print message I want to write on Console no to file

Upvotes: 0

Views: 2631

Answers (5)

TLP
TLP

Reputation: 67940

You seem to be confused about how file IO operations are done in perl, so I would recommend you read up on that.

What went wrong?

What you are doing is:

  • Open a file for reading
  • Read the entire file and close it
  • Open the same file for overwrite (org file is truncated), using the STDOUT file handle.
  • Juggle around the default print handle in order to set autoflush on a file handle which is not even opened in the code you show.
  • Perform a substitution on all lines and print them
  • Close STDOUT then print a message when everything is done.

Your main biggest mistake is trying to reopen the default output file handle STDOUT. I assume this is because you do not know how print works, i.e. that you can supply a file handle to print to print FILEHANDLE "text". Or that you did not know that STDOUT was a pre-defined file handle.

Your other errors:

  • You did not use use strict; use warnings;. No program you write should be without these. They will prevent you from doing bad things, and give you information on errors, and will save you hours of debugging.
  • You should never "slurp" a file (read the entire file to a variable) unless you really need to, because this is ineffective and slow and for huge files will cause your program to crash due to lack of memory.
  • Never reassign the default file handles STDIN, STDOUT, STDERR, unless A) you really need to, B) you know what you are doing.
  • select sets the default file handle for print, read the documentation. This is rarely something that you need to concern yourself with. The variable $| sets autoflush on (if set to a true value) for the currently selected file handle. So what you did actually accomplished nothing, because OUTPUT_HANDLE is a non-existent file handle. If you had skipped the select statements, it would have set autoflush for STDOUT. (But you wouldn't have noticed any difference)
  • print uses print buffers because it is efficient. I assume you are trying to autoflush because you think your prints get caught in the buffer, which is not true. Generally speaking, this is not something you need to worry about. All the print buffers are automatically flushed when a program ends.
  • For the most part, you do not need to explicitly close file handles. File handles are automatically closed when they go out of scope, or when the program ends.
  • Using lexical file handles, e.g. open my $fh, ... instead of global, e.g. open FILE, .. is recommended, because of the previous statement, and because it is always a good idea to avoid global variables.
  • Using three-argument open is recommended: open FILEHANDLE, MODE, FILENAME. This is because you otherwise risk meta-characters in your file names to corrupt your open statement.

The quick fix:

Now, as I said in the comments, this -- or rather, what you intended, because this code is wrong -- is pretty much identical to the idiomatic usage of the -p command line switch:

perl -pi.bak -e 's/(.*?xsl.*?)xsl/$1xslt/' file.txt

This short little snippet actually does all that your program does, but does it much better. Explanation:

  • -p switch automatically assumes that the code you provide is inside a while (<>) { } loop, and prints each line, after your code is executed.
  • -i switch tells perl to do inplace-edit on the file, saving a backup copy in "file.txt.bak".

So, that one-liner is equivalent to a program such as this:

$^I = ".bak"; # turns inplace-edit on
while (<>) {  # diamond operator automatically uses STDIN or files from @ARGV
    s/(.*?xsl.*?)xsl/$1xslt/;
    print;
}

Which is equivalent to this:

my $file = shift; # first argument from @ARGV -- arguments 
open my $fh, "<", $file or die $!;
open my $tmp, ">", "/tmp/foo.bar" or die $!; # not sure where tmpfile is
while (<$fh>) {                           # read lines from org file
    s/(.*?xsl.*?)xsl/$1xslt/;
    print $tmp $_;                        # print line to tmp file
}
rename($file, "$file.bak") or die $!;     # save backup
rename("/tmp/foo.bar", $file) or die $!;  # overwrite original file

The inplace-edit option actually creates a separate file, then copies it over the original. If you use the backup option, the original file is first backed up. You don't need to know this information, just know that using the -i switch will cause the -p (and -n) option to actually perform changes on your original file.

Using the -i switch with the backup option activated is not required (except on Windows), but recommended. A good idea is to run the one-liner without the option first, so the output is printed to screen instead, and then adding it once you see the output is ok.

The regex

s/(.*?xsl.*?)xsl/$1xslt/;

You search for a string that contains "xsl" twice. The usage of .*? is good in the second case, but not in the first. Any time you find yourself starting a regex with a wildcard string, you're probably doing something wrong. Unless you are trying to capture that part.

In this case, though, you capture it and remove it, only to put it back, which is completely useless. So the first order of business is to take that part out:

s/(xsl.*?)xsl/$1xslt/;

Now, removing something and putting it back is really just a magic trick for not removing it at all. We don't need magic tricks like that, when we can just not remove it in the first place. Using look-around assertions, you can achieve this.

In this case, since you have a variable length expression and need a look-behind assertion, we have to use the \K (mnemonic: Keep) option instead, because variable length look-behinds are not implemented.

s/xsl.*?\Kxsl/xslt/;

So, since we didn't take anything out, we don't need to put anything back using $1. Now, you may notice, "Hey, if I replace 'xsl' with 'xslt', I don't need to remove 'xsl' at all." Which is true:

s/xsl.*?xsl\K/t/;

You may consider using options for this regex, such as /i, which causes it to ignore case and thus also match strings such as "XSL FOO XSL". Or the /g option which will allow it to perform all possible matches per line, and not just the first match. Read more in perlop.

Conclusion

The finished one-liner is:

perl -pi.bak -e 's/xsl.*?xsl\K/t/' file.txt

Upvotes: 2

cppcoder
cppcoder

Reputation: 23145

A print statement will output the string in the STDOUT, which is the default output file handle.

So the statement

print "This is a message";

is same as

print STDOUT "This is a message";

In your code, you have closed STDOUT and then printing the message, which will not work. Reopen the STDOUT filehandle or do not close it. As the script ends, the file handles will be automatically closed

Upvotes: 3

Matthew Walton
Matthew Walton

Reputation: 9979

It's because you've closed the filehandle stored in STDOUT, so print can't use it anymore. Generally speaking opening a new filehandle into one of the predefined handle names isn't a very good idea because it's bound to lead to confusion. It's much clearer to use lexical filehandles, or just a different name for your output file. Yes you then have to specify the filehandle in your print call, but then you don't have any confusion over what's happened to STDOUT.

Upvotes: 3

askovpen
askovpen

Reputation: 2494

open OLDOUT, ">&", STDOUT;
close STDOUT;

open(STDOUT, ">$new_file") or die "Can't open file\n";
...
close(STDOUT);  
open (STDOUT, ">&",OLDOUT);
print "file changed";

Upvotes: 2

Birei
Birei

Reputation: 36292

I suppose it is because print default filehandle is STDOUT, which at that point it is already closed. You could reopen it, or print to other filehandle, for example, STDERR.

print STDERR "file changed";

Upvotes: 4

Related Questions