Tải bản đầy đủ - 0 (trang)
Chapter 36. Shell Programming for the Initiated

Chapter 36. Shell Programming for the Initiated

Tải bản đầy đủ - 0trang

Thischapterhasabunchoftricksandtechniquesforprogrammingwiththe

Bourneshell.Someofthemaredocumentedbuthardtofind;othersaren't

documentedatall.Hereisasummaryofthischapter'sarticles:

Thefirstgroupofarticlesisaboutmakingafiledirectlyexecutablewith#!

onthefirstline.OnmanyversionsofUnix,anexecutablefilecanstartwith

afirstlinelikethis:



#!/path/to/interpreter

Thekernelwillstarttheprogramnamedinthatlineandgiveitthefileto

read.ChrisTorek'sUsenetclassic,Section36.2,explainshow#!started.

Section36.3explainsthatyour"shellscripts"maynotneedashellatall.

Thenextbunchofarticlesareaboutprocessesandcommands.Theexec

command,Section36.5,replacestheshellwithanotherprocess;itcanalso

beusedtochangeinput/outputredirection(seebelow).The:(colon)

operatorevaluatesitsargumentsandreturnsazerostatus—Section36.6

explainswhyyoushouldcare.

Nextaretechniquesforhandlingvariablesandparameters.Parameter

substitution,explainedinSection36.7,isacompactwaytotest,set,and

givedefaultvaluesforvariables.Youcanusethe$0parameterandUnix

linkstogivethesamescriptmultiplenamesandmakeitdomultiplethings;

seeSection36.8.Section36.9showstheeasywaytogetthelastcommandlineargument.Section36.10hasaneasywaytoremoveallthecommandlinearguments.

Fourarticlescovershloops.Aforloopusuallyreadsalistofsingle

argumentsintoasingleshellvariable.Section36.11showshowtomakethe

forloopreadfromstandardinput.Section36.12hastechniquesformaking

aforloopsetmorethanonevariable.Thedirnameandbasename

commandscanbeusedtosplitpathnameswithaloop;seeSection36.13.A

whileloopcanhavemorethanonecommandlineatthestart;seeSection

36.14.

Nextisanassortmentofarticlesaboutinput/output.Section36.15



introducesopenfilesandfiledescriptors—there'smoretoknowabout

standardinput/output/errorthanyoumighthaverealized!Section36.16has

alookatfile-descriptorhandlingintheBourneshell,swappingstandard

outputandstandarderror.

Theshellcanreadcommandsdirectlyfromashellscriptfile.AsSection

36.17pointsout,ashellcanalsoreadcommandsfromitsstandardinput,

butthatcancausesomeproblems.Section36.18showsoneplacescripts

fromstdinareuseful:writingascriptthatcreatesanotherscriptasitgoes.

NextaretwoarticlesaboutmiscellaneousI/O.Onegotchawiththeheredocumentoperator(forredirectinginputfromascriptfile)isthatthe

terminatorsaredifferentintheBourneandCshells;Section36.19explains.

Section36.20showshowtoturnoffechoingwhileyourscriptreadsa

"secret"answersuchasapassword.

Twoarticles—Section36.22andSection36.23—showusesforthe

versatileexprexpression-handlingcommand.Section36.21isaquick

referencetoexpr.Section36.24coversmultiplecommandsubstitution

(Section28.14).

Section36.25showsatrickformakingonecasestatement(Section35.10)

testtwothingsatonce.Finally,Section36.27hasasimpletechniquefor

gettingexclusiveaccesstoafileorothersystemresource.

—JP



36.2TheStoryof:##!

Onceuponatime,therewastheBourneshell.Sincetherewasonly"the"shell,

therewasnotroubledecidinghowtorunascript:runitwiththeshell.Itworked,

andeveryonewashappy.

Alongcameprogressandwroteanothershell.Thepeoplethoughtthiswasgood,

fornowtheycouldchoosetheirownshell.Sosomechosetheone,andsomethe

other,andtheywroteshellscriptsandwerehappy.Butonedaysomeonewho

usedthe"other"shellranascriptbysomeonewhousedthe"otherother"shell,



andalas!itbombedspectacularly.ThepeoplewailedandcalledupontheirGuru

forhelp.

"Well,"saidtheGuru,"Iseetheproblem.Theoneshellandtheotherarenot

compatible.Weneedtomakesurethattheshellsknowwhichothershelltouse

toruneachscript.Andlo!theoneshellhasa`comment'called:,andtheothera

truecommentcalled#.Iherebydecreethathenceforth,theoneshellwillrun

scriptsthatstartwith:,andtheotherthosethatstartwith#."Anditwasso,and

thepeoplewerehappy.

Butprogresswasnotfinished.Thistimehenoticedthatonlyshellsranscripts

andthoughtthatifthekerneltoocouldrunscripts,thiswouldbegood,andthe

peoplewouldbehappy.Sohewrotemorecode,andnowthekernelcouldrun

scriptsbutonlyiftheybeganwiththemagicincantation#!,andiftheytoldthe

kernelwhichshellranthescript.Anditwasso,andthepeoplewereconfused.

Forthe#!lookedlikea"comment."Thoughthekernelcouldseethe#!and

runashell,itwouldnotdosounlesscertainmagicbitswereset.Andifthe

incantationweremispronounced,thattoocouldstopthekernel,which,afterall,

wasnotomniscient.Andsothepeoplewailed,butalas!theGurudidnot

respond.Andsoitwas,andstillitistoday.Anyway,youwillgetbestresults

froma4BSDmachinebyusing



#!/bin/sh

or:



#!/bin/csh

asthefirstlineofyourscript.#!/bin/csh-fisalsohelpfulonoccasion,

andit'susuallyfasterbecausecshwon'treadyour.cshrcfile(Section3.3).

—CT



36.3Don'tNeedaShellforYourScript?Don't

UseOne



IfyourUnixunderstandsfilesthatstartwith:



#!/interpreter/program

(andnearlyallofthemdobynow)youdon'thavetousethoselinestostarta

shell,suchas#!/bin/sh.Ifyourscriptisjuststartingaprogramlikeawk,

Unixcanstarttheprogramdirectlyandsaveexecutiontime.Thisisespecially

usefulonsmalloroverloadedcomputers,orwhenyourscripthastobecalled

overandover(suchasinaloop).

First,herearetwoscripts.Bothscriptsprintthesecondwordfromeachlineof

textfiles.Oneusesashell;theotherrunsawkdirectly:



%catwith_sh

#!/bin/sh

awk'

{print$2}

'$*

%catno_sh

#!/usr/bin/awk-f

{print$2}

%catafile

onetwothreefourfive

Let'srunbothcommandsandtime(Section26.2)them.(Thisisrunningona

veryslowmachine.Onfastersystems,thisdifferencemaybehardertomeasure

—thoughthedifferencecanstilladdupovertime.)



%timewith_shafile

two

0.1u0.2s0:0026%

%timeno_shafile

two

0.0u0.1s0:0013%



Oneofthethingsthat'sreallyimportanttounderstandhereisthatwhenthe

kernelrunstheprogramontheinterpreterline,itisgiventhescript'sfilenameas

anargument.Iftheintepreterprogramunderstandsafiledirectly,like/bin/sh

does,nothingspecialneedstobedone.Butaprogramlikeawkorsedrequires

the-foptiontoreaditsscriptfromafile.Thisleadstotheseeminglyoddsyntax

intheexampleabove,withacalltoawk-fwithnofollowingfilename.The

scriptitselfistheinputfile!

Oneimplicationofthisusageisthattheinterpreterprogramneedstounderstand

#asacomment,orthefirstinterpreter-selectionlineitselfwillbeactedupon

(andprobablyrejectedby)theinterpreter.(Fortunately,theshells,Perl,sed,and

awk,amongotherprograms,dorecognizethiscommentcharacter.)

[Onelastcomment:ifyouhaveGNUtimeorsomeotherversionthathasa

verbosemode,youcanseethatthemajordifferencebetweenthetwoinvocations

isintermsofthepagefaultseachrequires.OnarelativelyspeedyPentium

III/450runningRedHatLinux,theversionusingashellastheinterpreter

requiredmorethantwicethemajorpagefaultsandmorethanthreetimesas

manyminorpagefaultsastheversioncallingawkdirectly.Onasystem,no

matterhowfast,thatisusingalargeamountofvirtualmemory,thesedifferences

canbecrucial.Sooptforperformance,andskiptheshellwhenit'snotneeded.

—SJC]

—JPandSJC

AsSection36.3explains,youcanuse#!/path/nametorunascriptwith

theinterpreterlocatedat/path/nameinthefilesystem.Theproblemcomesifa

newversionoftheinterpreterisinstalledsomewhereelseorifyourunthescript

onanothersystemthathasadifferentlocation.It'susuallynotaproblemfor

Bourneshellprogrammers:/bin/shexistsoneveryUnix-typesystemI'veseen.

Butsomenewershells—andinterpreterslikePerl—maybelurkingalmost

anywhere(althoughthisisbecomingmoreandmorestandardizedasPerland

othertoolslikeitbecomepartofstandardLinuxdistributionsandthelike).Ifthe

interpreterisn'tfound,you'llprobablygetacrypticmessagelike

scriptname:Commandnotfound,wherescriptnameisthename

ofthescriptfile.



TheenvcommandwillsearchyourPATH(Section35.6)foraninterpreter,then

execute(exec(Section24.2),replaceitself)withtheinterpreter.Ifyouwantto

trythis,typeenvls;envwillfindandrunlsforyou.Thisisprettyuseless

whenyouhaveashellaroundtointerpretyourcommands—becausetheshell

candothesamethingwithoutgettingenvinvolved.Butwhenthekernel

interpretsanexecutablefilethatstartswith#!,there'snoshell(yet!).That's

whereyoucanuseenv.Forinstance,torunyourscriptwithzsh,youcouldstart

itsfilewith:



#!/usr/bin/envzsh

...zshscripthere...

Thekernelexecs/usr/bin/env,thenenvfindsandexecsthezshitfound.Nice

trick,eh?Whatdoyouthinktheproblemis?(Youhavetenseconds...tick,tick,

tick...)Thecatchis:iftheenvcommandisn'tin/usr/binonyoursystem,this

trickwon'twork.Soit'snotasportableasitmightbe,butit'sstillhandyand

probablystillbetterthantryingtospecifythepathnameofalesscommon

interpreterlikezsh.

Runninganinterpreterthiswaycanalsobeasecurityproblem.Someone'sPATH

mightbewrong;forinstance,itmightexecutesomerandomcommandnamed

zshintheuser'sbindirectory.AnintrudercouldchangethePATHtomakethe

scriptuseacompletelydifferentinterpreterwiththesamename.

Onemoreproblemworthmentioning:youcan'tspecifyanyoptionsforthe

interpreteronthefirstline.Someshelloptionscanbesetlater,asthescript

starts,withacommandlikeset,shopt,andsoon—checktheshell'smanual

page.

Finally,understandthatusingenvlikethisprettymucherasesanyperformance

gainsyoumayhaveachievedusingthetrickinthepreviousarticle.

—JPandSJC



36.5TheexecCommand

Theexeccommandexecutesacommandinplaceofthecurrentshell;thatis,it



terminatesthecurrentshellandstartsanewprocess(Section24.3)initsplace.

Historically,execwasoftenusedtoexecutethelastcommandofashellscript.

Thiswouldkilltheshellslightlyearlier;otherwise,theshellwouldwaituntilthe

lastcommandwasfinished.Thispracticesavedaprocessandsomememory.

(Aren'tyougladyou'reusingamodernsystem?Thissortofconservationusually

isn'tnecessaryanylongerunlessyoursystemlimitsthenumberofprocesses

eachusercanhave.)

execcanbeusedtoreplaceoneshellwithanothershell:



%execksh

$

withoutincurringtheadditionaloverheadofhavinganunusedshellwaitingfor

thenewshelltofinish.

execalsomanipulatesfiledescriptors(Section36.16)intheBourneshell.

Whenyouuseexectomanagefiledescriptors,itdoesnotreplacethecurrent

process.Forexample,thefollowingcommandmakesthestandardinputofall

commandscomefromthefileformfileinsteadofthedefaultplace(usually,your

terminal):



exec
—MLandJP



36.6TheUnappreciatedBourneShell":"

Operator

SomepeoplethinkthattheBourneshell's:isacommentcharacter.Itisn't,

really.Itevaluatesitsargumentsandreturnsazeroexitstatus(Section35.12).

Hereareafewplacestouseit:

ReplacetheUnixtruecommandtomakeanendlesswhileloop(Section

35.15).Thisismoreefficientbecausetheshelldoesn'thavetostartanew



processeachtimearoundtheloop(asitdoeswhenyouusewhile

true):



while:

do

commands

done

(Ofcourse,oneofthecommandswillprobablybebreak,toendtheloop

eventually.Thispresumesthatitisactuallyasavingstohavethebreaktest

insidetheloopbodyratherthanatthetop,butitmaywellbeclearerunder

certaincircumstancestodoitthatway.)

Whenyouwanttousetheelseinanif(Section35.13)butleavethethen

empty,the:makesanice"do-nothing"placefiller:



ifsomething

then:

else

commands

fi

IfyourBourneshelldoesn'thaveatrue#commentcharacter(butnearlyall

ofthemdonowadays),youcanuse:to"fakeit."It'ssafesttousequotesso

theshellwon'ttrytointerpretcharacterslike>or|inyour"comment":



:'readanswerandbranchif<3or>6'

Finally,it'susefulwithparametersubstitution(Section35.7)like

${var?}or${var=default}.Forinstance,usingthislineinyour

scriptwillprintanerrorandexitifeithertheUSERorHOMEvariables

aren'tset:



:${USER?}${HOME?}



—JP



36.7ParameterSubstitution

TheBourneshellhasahandysetofoperatorsfortestingandsettingshell

variables.They'relistedinTable36-1.

Table36-1.Bourneshellparametersubstitutionoperators

Operator



Explanation



${var:-default} Ifvarisnotsetorisempty,usedefaultinstead.

${var:=default} Ifvarisnotsetorisempty,setittodefaultandusethatvalue.

${var:+instead} Ifvarissetandisnotempty,useinstead.Otherwise,use

nothing(nullstring).



Ifvarissetandisnotempty,useitsvalue.Otherwise,print

${var:?message} message,ifany,andexitfromtheshell.Ifmessageismissing,

printadefaultmessage(whichdependsonyourshell).



Ifyouomitthecolon(:)fromtheexpressionsinTable36-1,theshelldoesn't

checkforanemptyparameter.Inotherwords,thesubstitutionhappens

whenevertheparameterisset.(That'showsomeearlyBourneshellswork:they

don'tunderstandacoloninparametersubstitution.)

Toseehowparametersubstitutionworks,here'sanotherversionofthebkedit

script(Section35.13,Section35.16):



+#!/bin/sh

ifcp"$1""$1.bak"

then



${VISUAL:-/usr/ucb/vi}"$1"

exit#Usestatusfromeditor

else

echo"`basename$0`quitting:can'tmakebackup?"1

exit1

fi

IftheVISUAL(Section35.5)environmentvariableissetandisnotempty,its

value(suchas/usr/local/bin/emacs)isusedandthecommandlinebecomes

/usr/local/bin/emacs"$1".IfVISUALisn'tset,thecommand

linedefaultsto/usr/ucb/vi"$1".

Youcanuseparametersubstitutionoperatorsinanycommandline.You'llsee

themusedwiththecolon(:)operator(Section36.6),checkingorsettingdefault

values.There'sanexamplebelow.Thefirstsubstitution

(${nothing=default})leaves$nothingemptybecausethe

variablehasbeenset.Thesecondsubstitutionsets$nothingtodefault

becausethevariablehasbeensetbutisempty.Thethirdsubstitutionleaves

$somethingsettostuff:



+nothing=

something=stuff

:${nothing=default}

:${nothing:=default}

:${something:=default}

SeveralBourne-typeshellshavesimilarstringeditingoperators,suchas

${var##pattern}.They'reusefulinshellprograms,aswellasonthe

commandlineandinshellsetupfiles.Seeyourshell'smanualpageformore

details.

—JP



36.8SaveDiskSpaceandProgramming:



MultipleNamesforaProgram

Ifyou'rewriting:

severalprogramsthatdothesamekindsofthings,

programsthatusealotofthesamecode(asyou'rewritingthesecond,third,

etc.,programs,youcopyalotoflinesfromthefirstprogram),or

aprogramwithseveraloptionsthatmakebigchangesinthewayitworks,

youmightwanttowritejustoneprogramandmakelinks(Section10.4,Section

10.3)toitinstead.Theprogramcanfindthenameyoucalleditwithand,

throughcaseortestcommands,workindifferentways.Forinstance,the

BerkeleyUnixcommandsex,vi,view,edit,andothersarealllinkstothesame

executablefile.Thistakeslessdiskspaceandmakesmaintenanceeasier.It's

usuallysensibleonlywhenmostofthecodeisthesameineachprogram.Ifthe

programisfullofnametestsandlotsofseparatecode,thistechniquemaybe

moretroublethanit'sworth.

Dependingonhowthescriptprogramiscalled,thisnamecanbeasimple

relativepathnamelikeprogor./prog—itcanalsobeanabsolute

pathnamelike/usr/joe/bin/prog(Section31.2explainspathnames).

Thereareacoupleofwaystohandlethisinashellscript.Ifthere'sjustonemain

pieceofcodeinthescript,asinthelfscript,acasethattests$0mightbebest.

Theasterisk(*)wildcardatthestartofeachcase(seeSection35.11)handlesthe

differentpathnamesthatmightbeusedtocallthescript:



case"$0"in

*name1)

...dothiswhencalledasname1...

;;

*name2)

...dothiswhencalledasname2...

;;



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Chapter 36. Shell Programming for the Initiated

Tải bản đầy đủ ngay(0 tr)

×