One of the best ways to make your Perl code look more like … well, like Perl code – and not like C or BASIC or whatever you used before you were ...
MORE:
Togglenavigation
Perl.com
ABOUT
AUTHORS
CATEGORIES
#
TAGS
Perl'sSpecialVariables
Jun18,2004by
DaveCross
OneofthebestwaystomakeyourPerlcodelookmorelike…well,likePerlcode–andnotlikeCorBASICorwhateveryouusedbeforeyouwereintroducedtoPerl–istogettoknowtheinternalvariablesthatPerlusestocontrolvariousaspectsofyourprogram’sexecution.
Inthisarticlewe’lltakealookatanumberofvariablesthatgiveyoufinercontroloveryourfileinputandoutput.
CountingLines
IdecidedtowritethisarticlebecauseIamconstantlyamazedbythenumberofpeoplewhodon’tknowabouttheexistenceof$..Istillseepeopleproducingcodethatlookslikethis:
my$line_no=0;
while(){
++$line_no;
unless(/someregex/){
warn"Errorinline$line_no\n";
next;
}
#processtherecordinsomeway
}
Forsomereason,manypeopleseemtocompletelymisstheexistenceof$.,whichisPerl’sinternalvariablethatkeepstrackofyourcurrentrecordnumber.Thecodeabovecanberewrittenas:
while(){
unless(/someregex/){
warn"Errorinline$.\n";
next;
}
#processtherecordinsomeway
}
Iknowthatitdoesn’tactuallysaveyouverymuchtyping,butwhycreateanewvariableifyoudon’thaveto?
Oneothernicewaytouse$.isinconjunctionwithPerl’s“flip-flop”operator(..).Whenusedinlistcontext,..isthelistconstructionoperator.Itbuildsalistofelementsbycalculatingalloftheitemsbetweengivenstartandendvalueslikethis:
my@numbers=(1..1000);
Butwhenyouusethisoperatorinascalarcontext(like,forexample,astheconditionofanifstatement),itsbehaviorchangescompletely.Thefirstoperand(theleft-handexpression)isevaluatedtoseeifitistrueorfalse.Ifitisfalsethentheoperatorreturnsfalseandnothinghappens.Ifitistrue,however,theoperatorreturnstrueandcontinuestoreturntrueonsubsequentcallsuntilthesecondoperand(theright-handexpression)returnstrue.
Anexamplewillhopefullymakethisclearer.Supposeyouhaveafileandyouonlywanttoprocesscertainsectionsofit.Thesectionsthatyouwanttoprintareclearlymarkedwiththestring“!!START!!”atthestartand“!!END!!”attheend.Usingtheflip-flopoperatoryoucanwritecodelikethis:
while(){
if(/!!START!!/../!!END!!/){
#processline
}
}
Eachtimearoundtheloop,thecurrentlineischeckedbytheflip-flopoperator.Ifthelinedoesn’tmatch/!!START!!/thentheoperatorreturnsfalseandtheloopcontinues.Whenwereachthefirstlinethatmatches/!!START!!/thentheflip-flopoperatorreturnstrueandthecodeintheifblockisexecuted.Onsubsequentiterationsofthewhileloop,theflip-flopoperatorchecksformatchesagain/!!END!!/,butitcontinuestoreturntrueuntilitfindsamatch.Thismeansthatallofthelinesbetweenthe“!!START!!”and“!!END!!”markersareprocessed.Whenalinematches/!!END!!/thentheflip-flopoperatorreturnsfalseandstartscheckingagainstthefirstregexagain.
Sowhatdoesallthishavetodowith$.?Well,there’sanotherpieceofmagiccodedintotheflip-flopoperator.Ifeitherofitsoperandsareconstantvaluesthentheyareconvertedtointegersandmatchedagainst$..Sotoprintoutjustthefirst10linesofafileyoucanwritecodelikethis:
while(){
printif1..10;
}
Onefinalpointon$.,thereisonlyone$.variable.Ifyouarereadingfrommultiplefilehandlesthen$.containsthecurrentrecordnumberfromthemostrecentlyreadfilehandle.IfyouwantanythingmorecomplexthenyoucanusesomethinglikeIO::Fileobjectsforyourfilehandle.Theseobjectsallhaveaninput_line_numbermethod.
TheFieldRecordSeparators
Next,we’lllookat$/and$\whicharetheinputandoutputrecordseparatorsrespectively.Theycontrolwhatdefinesa“record”whenyouarereadingorwritingdata.
Letmeexplainthatinabitmoredetail.RememberwhenyouwerefirstlearningPerlandyouwereintroducedtothefileinputoperator.Almostcertainlyyouweretoldthatreaddatafromthefileuptoandincludingthenextnewlinecharacter.Wellthat’snottrue.Well,itis,butit’sonlyaspecializedcase.Actuallyitreadsdatauptoandincludingthenextoccurrenceofwhateveriscurrentlyin$/-thefileinputseparator.Let’slookatanexample.
Imagineyouhaveatextfilewhichcontainsamusingquotes.Orlyricsfromsongs.Orwhateveritisthatyouliketoputinyourrandomlygeneratedsignature.Thefilemightlooksomethinglikethis.
Thisisthedefinitionofmylife
%%
Wearefartooyoungandclever
%%
Stabasorryheart
Withyourfavoritefinger
Herewehavethreequotesseparatedbyalinecontainingjustthestring%%.Howwouldyougoaboutreadinginthatfileaquoteatatime?
Onesolutionwouldbetoreadthefilealineatatime,checkingtoseeifthenewlineisjustthestring%%.You’dneedtokeepavariablethatcontainscurrentquotethatyouarebuildingupandprocessacompletedquotewhenyoufindtheterminationstring.Oh,andyou’dneedtoremembertoprocessthelastquoteinthefileasthatdoesn’thaveaterminationstring(although,itmight!)
AsimplersolutionwouldbetochangePerl’sideaofwhatconstitutesarecord.Wedothatbychangingthevalueof$/.Thedefaultvalueisanewlinecharacter-whichiswhy<...>usuallyreadsinalineatatime.Butwecansetittoanyvaluewelike.Wecandosomethinglikethis
$/="%%\n";
while(){
chomp;
print;
}
Noweachtimewecallthefileinputoperator,Perlreadsdatafromthefilehandleuntilitfinds%%\n(ortheendoffilemarker).Anewlineisnolongerseenasaspecialcharacter.Notice,however,thatthefileinputoperatoralwaysreturnsthenextrecordwiththefileinputseparatorstillattached.When$/hasitsdefaultvalueofanewlinecharacter,youknowthatyoucanremovethenewlinecharacterbycallingchomp.Wellitworksexactlythesamewaywhen$/hasothervalues.Itturnsoutthatchompdoesn’tjustremoveanewlinecharacter(that’sanother“simplification”thatyoufindinbeginnersbooks)itactuallyremoveswhateveristhecurrentvalueof$/.Soinoursamplecodeabove,thecalltochompisremovingthewholestring%%\n.
ChangingPerl’sSpecialVariables
BeforewegoonIjustneedtoalertyoutoonepossiblerepercussionofchangingthesevariableswheneveryouwant.Theproblemisthatmostofthesevariablesareforcedintothemainpackage.Thismeansthatwhenyouchangeoneofthesevariables,youarealteringthevalueeverywhereinyourprogram.Thisincludesanymodulesthatyouuseinyourprogram.Thereverseisalsotrue.Ifyou’rewritingamodulethatotherpeoplewilluseintheirprogramsandyouchangethevalueof$/insideit,thenyouhavechangedthevalueforalloftheremainingprogramexecution.Ihopeyoucanseenwhychangingvariableslike$/inonepartofyourprogramcanpotentiallyleadtohardtofindbugsinanotherpart.
Soweneedtodowhatwecantoavoidthis.Yourfirstapproachmightbetoresetthevalueof$/afteryouhavefinishedwithit.Soyou’dwritecodelikethis.
$/="%%\n";
while(){
chomp;
print;
}
$/="\n";
Theproblemwiththisisyoucan’tbesurethat$/contained\nbeforeyoustartedfiddlingwithit.Someoneelsemighthavechangeditbeforeyourcodewasreached.Sothenextattemptmightlooklikethis.
$old_input_rec_sep=$/;
$/="%%\n";
while(){
chomp;
print;
}
$/=$old_input_rec_sep;
Thiscodeworksanddoesn’thavethebugthatwe’retryingtoavoidbutthere’sanotherwaythatlookscleaner.Rememberthelocalfunctionthatyouusedtodeclarelocalvariablesuntilsomeonetoldyouthatyoushouldusemyinstead?Wellthisisoneofthefewplaceswhereyoucanuselocaltogreateffect.
It’sgenerallyacknowledgedthatlocalisbadlynamed.Thenamedoesn’tdescribewhatthefunctiondoes.InPerl6thefunctionislikelytoberenamedtotempasthat’safarbetterdescriptionofwhatitdoes-itcreatesatemporaryvariablewiththesamenameasanexistingvariableandrestorestheoriginalvariablewhentheprogramleavestheinnermostenclosingblock.Thismeansthatwecanwriteourcodelikethis.
{
local$/="%%\n";
while(){
chomp;
print;
}
}
We’veenclosedallofthecodeinanotherpairofbracestocreateanakedblock.Codeblocksareusuallyassociatedwithloops,conditionalsorsubroutines,butinPerltheydon’tneedtobe.Youcanintroduceanewblockwheneveryouwant.Here,we’veintroducedablockpurelytodelimittheareawherewewant$/tohaveanewvalue.Wethenuselocaltostoretheold$/variablesomewherewhereitcan’tbedisturbedandsetournewversionofthevariableto%%\n.Wecanthendowhateverwewantinthecodeblockandwhenweexitfromtheblock,Perlautomaticallyrestorestheoriginalcopyof$/andweneverneededtoknowwhatitwassetto.
Forallthisreason,it’sgoodpracticetoneverchangeoneofPerl’sinternalvariablesunlessitislocalizedinablock.
OtherValuesFor$/
Thereareafewspecialvaluesthatyoucangive$/whichturnoninterestingbehaviours.Thefirstoftheseissettingittoundef.Thisturnson“slurpmode”andthenexttimeyoureadfromafilehandleyouwillgetalloftheremainingdatarightuptotheendoffilemarker.Thismeansthatyoucanreadawholefileinusingcodelikethis.
my$file=do{local$/;};
Adoblockreturnsthevalueofthelastexpressionevaluatedwithinit,whichinthiscaseisthefileinputoperator.Andas$/hasbeensettoundefitreturnsthewholefile.Noticethatwedon’tevenneedtoexplicitlyset$/toundefasallPerlvariablesareinitializedtoundefwhentheyarecreated.
Thereisabigdifferencebetweensetting$/toundefandsettingittoanemptystring.Settingittoanemptystringturnson“paragraph”mode.Inthismodeeachrecordisaparagraphoftextterminatedbyoneormoreemptylines.Youmightthinkthatthiseffectcanbemimickedbysetting$/to\n\n,butthesubtledifferenceisthatparagraphmodeactsasthought$/hadbeensetto\n\n+(althoughyoucan’tactuallyset$/equaltoaregularexpression.)
Thefinalspecialvalueistoset$/toeitherareferencetoascalarvariablethatholdsaninteger,ortoareferencetoanintegerconstant.Inthesecasesthenextreadfromafilehandlewillreaduptothatnumberofbytes(Isay“upto”becauseattheendofthefiletheremightnotbeenoughdatalefttogiveyou).Soyoureadafile2Kbatatimeandyoucandothis.
{
local$/=\2048;
while(){
#$_containsthenext2048bytesfromFILE
}
}
$/and$.
Notethatchanging$/altersPerl’sdefinitionofarecordandthereforeitaltersthebehaviorof$..$.doesn’tactuallycontainthecurrentlinenumber,itcontainsthecurrentrecordnumber.Soinourquotesexampleabove,$.willbeincrementedforeachquotethatyoureadfromthefilehandle.
WhatAbout$\?
ManyparagraphsbackImentionedboth$/and$\asbeingtheinputandoutputrecordseparators.ButsincethenI’vejustgoneonabout$/.Whathappenedto$\?
Well,tobehonest,$\isn’tanywherenearasusefulas$/.Itcontainsastringthatisprintedattheendofeverycalltoprint.Itsdefaultvalueistheemptystring,sonothinggetsaddedtodatathatyoudisplaywithprint.Butif,forexample,youlongedforthedaysofPascalyoucouldwriteaprintlnfunctionlikethis.
subprintln{
local$\="\n";
print@_;
}
Theneverytimeyoucalledprintln,alloftheargumentswouldbeprintedfollowedbyanewline.
OtherPrintVariables
ThenexttwovariablesthatIwanttodiscussareveryeasilyconfusedalthoughtheydocompletelydifferentthings.Toillustratethem,considerthefollowingcode.
my@arr=(1,2,3);
print@arr;
print"@arr";
Now,withoutlookingitupdoyouknowwhatthedifferenceisbetweentheoutputfromthetwocallstoprint?
Theansweristhatthefirstoneprintsthethreeelementsofthearraywithnothingseparatingthem(likethis-123)whereasthesecondoneprintstheelementsseparatedbyspaces(likethis-123).Whyisthereadifference?
Thekeytounderstandingitistolookatexactlywhatisbeingpassedtoprintineachcase.Inthefirstcaseprintispassedanarray.Perlunrollsthatarrayintoalistandprintactuallyseesthethreeelementsofthearrayasseparatearguments.Inthesecondcase,thearrayisinterpolatedintoadoublequotedstringbeforeprintseesit.Thatinterpolationhasnothingatalltodowiththecalltoprint.Exactlythesameprocesswouldtakeplaceif,forexample,wedidsomethinglikethis.
my$string="@arr";
print$string;
Sointhesecondcase,theprintfunctiononlyseesoneargument.Thefactthatitistheresultsofinterpolatinganarrayindoublequoteshasnoeffectonhowprinttreatsthestring.
Wethereforehavetwocases.Whenprintreceivesanumberofargumentsitprintsthemoutwithnospacesbetweenthem.Andwhenanarrayisinterpolatedindoublequotesitisexpandedwithspacesbetweentheindividualelements.Thesetwocasesarecompletelyunrelated,butfromourfirstexampleaboveit’seasytoseehowpeoplecangetthemconfused.
Ofcourse,Perlallowsustochangethesebehaviorsifwewantto.Thestringthatisprintedbetweentheargumentspassedtoprintisstoredinavariablecalled$,(becauseyouuseacommatoseparatearguments).Aswe’veseen,thedefaultvalueforthatisanemptystringbutitcan,ofcourse,bechanged.
my@arr=(1,2,3);
{
local$,=',';
print@arr;
}
Thiscodeprintsthestring1,2,3.
Thestringthatseparatestheelementsofanarraywhenexpandedinadoublequotedstringisstoredin$".Onceagain,it’ssimpletochangeittoadifferentvalue.
my@arr=(1,2,3);
{
local$"='+';
print"@arr";
}
Thiscodeprints1+2+3".
Ofcourse,$"doesn’tnecessarilyhavetousedinconjunctionwithaprintstatement.Youcanuseitanywherethatyouhaveanarrayinadoubledquotedstring.Anditdoesn’tjustworkforarrays.Arrayandhashslicesworkjustaswell.
my%hash=(one=>1,two=>2,three=>3);
{
local$"='