by Steven J. Owens (unless otherwise attributed)
The following was written in response to a question somebody asked about using a CGI script to edit a content document. I liked it so much I decided to save it and send it to people who ask similar questions :-)
> Rather than finding a workaround to get us all access to the > news.html file, it was discussed that putting an internal quick-form > would be best.
Sounds like a good idea; actually that's second-best; best would be storing each news item in a database with timestamps, dates, etc, and then either dynamically generating news.html or regularly updating it (i.e. each midnight via cron). But that might well be overkill.
> What the script needs to do is basically pull in the current > news.html file's contents, insert what the person typed into the > form (the news addition) at a pre-defined place (ie: <!-- append > news here -->) or some such, and then write the stuff back out. > > Does this sound like a feasible solution? It doesn't take into > account file locking etc, but the real issue is just in saving time > at this point...
Yah, trivial. Okay, assuming that you've written a CGI script that parses the request and builds a scalar variable $newpost, containing the string that's to be added, you now do this:
Note: This tutorial predates the perl three-arg file open. Back when dinosaurs ruled the earth, the perl open() function used a prefix character in the filename argument to indicate whether you're open the file for reading, writing, appending, etc. Since then, perl's open() function has been improved, to take a distinct, third argument, a mode string for the read/write/appending. So if you're reading this tutorial, go read up about three-arg open in perl, and do it that way.
# Using ">>news.html" in the open means we're appending to the end of # the existing file. open(OUTPUT, ">>news.html") || die "Can't open news.html\n" ; print OUTPUT "$newpost\n" ; # Append close(OPEN) ; # Close!
This approach appends to news.html, which is the simplest case. Assuming you want to actually check for "<!-- add post here -->" it gets slightly more complicated.
# We're going to replace "<!-- add post here -->" with the new post, # so we have to put the "<!-- add post here -->" line into newpost so # it'll be there the *next* time we have to do this. $newpost .= "<!-- add post here -->"# Note that this time we omit >> because we're not appending. # We're calling the filehandle INPUT this time, mainly for aesthetic # reasons. When we open to write, we'll call it OUTPUT. open(INPUT, "news.html") || die "Can't open news.html for reading.\n" ;
# Undef the line-terminator character so it'll read the whole file # as one line, including the newline (\n) characters. undef $/ ;
# Now do the read. my $newshtml = <INPUT> ;
# And close the file. close(INPUT) ;
# Now substitute in the new string. # Note that =~ is the part that means "run whatever substitute or match # is on the right side of me against whatever's on the left side of me". $newshtml =~ s/<!-- add post here -->/$newpost/g
# Now open the file again, this time using ">news.html" to tell perl # we want to write to the file; perl will clear the file when the # open command is run. open(OUTPUT, ">news.html") ; # Offhand, it's probably not absolutely necessary to put the quotes # around $newshtml" but I'm not certain so we'll do it this way in # the example. print OUTPUT "$newshtml" ; close(OUTPUT) ;
# Now print some sort of response to the user; probably a good one # would be to redirect the user to news.html so they can see the new # file. print "Location: news.html\n\n" ;
# Note I'm assuming here that you haven't yet printed the standard # "Content-type: text/html\n\n" line, which would tell the browser # to take whatever you printed and display it to the user; # instead, the browser will interpret this as a redirect header # and go back to the server and ask it to open up news.html.
Have fun! Most of this is done from memory, i.e. I haven't tested the code. Save it to a file, add what's appropriate at the beginning and do a "perl -c filename" from the command line to make sure it doesn't have syntax errors. If you get stuck, drop me a line and I'll try to give you a tip or two.
Other Suggestions:
-w: Don't Leave Home Without It. This switch makes Perl warn you about all the stupid things you're doing it. Just about all my scripts start with:
#!/user/bin/perl -wuse strict;
It's also advisable to use "use strict", which turns off the sloppy variable declaration stuff in Perl. Then you declare and initialize all of your variables appropriately with "my" (see below for my personal frustrations with variable scoping in perl).
Last tip: "use English;" lets you use handy and decipherable english names for all of the "magic variables" in Perl (as detailed on pages 127-140 of Programming Perl). So, for example, instead of $/ in the example a few posts ago, it could have used $INPUT_RECORD_SEPARATOR. This makes for much more readable code. There's a little bit of a CPU hit, but most of the time maintenance is more important than CPU.
I find Perl variable scoping somewhat frustrating, however:
In Perl there are mostly three kinds of scope. Default, which is global and very sloppy. The "local" kind which is not global "upwards" but propagates "downwards" (this is weird, I'll explain in a sec). And "my" which is a lot like C variables and of course cannot be sloppy. Putting "use strict;" at the beginning of your script tells Perl to insist on having all variables declared with local or my. In general you can still use local or my variables without "use strict;", perl just doesn't insist on them.
Default globals are scoped globally but you can declare them very sloppily, i.e. you just say "Oh, and $blah=whatever" whenever you need it. Trouble is, globals are, well, global. Makes it hard to do complex programming.
Local and my variables you need to declare ahead of time.
Local variables are weird; they're global to any subroutines you call from the routine where you declare a local variable, but not to the overall program. From what I can tell, few people use local these days. Actually, strictly speaking (at least according to the book) using the local command just says "stash the current value for the global version of this variable somewhere, and restore it when this local declaration goes out of scope".
My variables are scoped a lot like variables in C. The main difference is that they're scoped to block level, not subroutine level. A subroutine is a block; so is the inside of a for loop. Or just an extra block that you declare with a set of { braces }. Oh, and, of course, the extra difference is that you have to declare everything ahead of time.
What I find frustrating is that I'd like to have variables be scoped like my variables by default, but not need to be declared ahead of time. The one time I brought this up with one of the perl gurus he was extremely obtuse-on-purpose; somebody later told me this is some sort of major debate in the Perl crowd. The guru's point (or at least the point he should have made instead of yanking my chain) that my variables are scoped to whatever block the "my" is executed in, and without that "my" to spot it, how do you know where the variable is first created?