Date: Thu Mar 18 17:16:55 EST 1993
Ne morete ga - nikjer se ne shrani. Datoteke imajo podatke o času
zadnje spremembe (pokaže ga ,,ls -l
``), času zadnjega dostopa
(pokaže ga ,, ls -lu
``) in času zadnje spremembe inode
(pokaže ga ,,ls -lc
``). Zadnji se pogosto imenuje ,,čas
ustvarjenja`` - celo v nekaterih straneh referenčnega priročnika (za
man
) - a to je narobe; spremeni se tudi z operacijami kot so
mv
, ln
, chmod
, chown
in chgrp
.
Pojasnila so dostopna v poglavju referenčnega priročnika o stat(2)
.
rsh
, ne da bi rsh počakal konec oddaljenega ukaza?
Subject: How do I use "rsh" without having the rsh hang around ... ? Date: Thu Mar 18 17:16:55 EST 1993
(Glejte odgovor na vprašanje
,,Zakaj dobim ob ukazu rsh gostitelj ukaz {kakšno čudno sporočilo o napaki}?``, o
katerem ,,rsh
`` govorimo.)
Očitni odgovori odpovejo:
rsh stroj ukaz &
ali
rsh stroj 'ukaz &'
Na primer, poskusite narediti
rsh stroj 'sleep 60 &'
in videli boste, da rsh
ne bo takoj vrnil ukazne vrstice.
Počakal bo 60 sekund, dokler se ne bo oddaljeni ukaz ,,sleep
``
izvedel do konca, čeprav se ukaz na oddaljenem stroju požene v ozadju.
Kako torej pripravite rsh
do tega, da konča z delom takoj, ko
začne teči sleep
?
Rešitev - če uporabljate na oddaljenem stroju csh
:
rsh machine -n 'command >&/dev/null </dev/null &'
Če na oddaljenem stroju uporabljate sh
:
rsh machine -n 'command >/dev/null 2>&1 </dev/null &'
Zakaj? ,,-n
`` poveže standardni vhod ukaza rsh
na
/dev/null
, torej lahko poženete celotni ukaz rsh
v
ozadju na lokalnem stroju. Torej ima ,,-n
``
ekvivalenten pomen, kot da bi še enkrat določili ,,<
/dev/null
``. Še več, preusmeritve vhoda in izhoda na
oddaljenem stroju (znotraj enojnih narekovajev) zagotavljajo, da
rsh
misli, da se seja lahko prekine (ker ni več toka podatkov).
Opomba: Datoteko, na katero ali s katere preusmerjate na oddaljenem
računalniku ni nujno /dev/null
; katerakoli datoteka bo v
redu.
V veliko primerih različni deli tega zapletenega ukaza niso potrebni.
Subject: How do I truncate a file? Date: Mon, 27 Mar 1995 18:09:10 -0500
Funkcija BSD-ja ftruncate()
nastavi dolžino datoteke. (Toda
vse različice se ne obnašajo enako.) Kaže, da tudi druge različice
Unixa podpirajo neke vrste krajšanje.
Obstajajo tri znana obnašanja funkcije ftruncate
v sistemih,
ki jo podpirajo:
Ostali sistemi pridejo v štirih možnostih:
F_CHSIZE
F_CHSIZE
,
a ga ne podpirajoF_FREESP
F_FREESP
, a ga ne podpirajochsize()
chsize()
, a je ne podpirajotruncate
Moderatorjevo sporočilo: Spodnje funkcije sem posnel nekaj let nazaj.
Ne morem več ugotoviti prvotnega pisca. S. Spencer Sun
<spencer@ncd.com
> je tudi prispeval verzijo za
F_FREESP
.
Sledijo funkcije za neavtohtono funkcijo ftruncate
.
/* Emulacije ftruncate(), ki delujejo na nekaterih Systemih V. Ta datoteka je v javni lasti. */ #include #include #ifdef F_CHSIZE int ftruncate (fd, length) int fd; off_t length; { return fcntl (fd, F_CHSIZE, length); } #else #ifdef F_FREESP /* Naslednjo funkcijo je napisal kucharsk@Solbourne.com (William Kucharski) */ #include #include #include int ftruncate (fd, length) int fd; off_t length; { struct flock fl; struct stat filebuf; if (fstat (fd, &filebuf) < 0) return -1; if (filebuf.st_size < length) { /* Extend file length. */ if (lseek (fd, (length - 1), SEEK_SET) < 0) return -1; /* Write a "0" byte. */ if (write (fd, "", 1) != 1) return -1; } else { /* Truncate length. */ fl.l_whence = 0; fl.l_len = 0; fl.l_start = length; fl.l_type = F_WRLCK; /* Zapiši ključavnico na prostor datoteke. */ /* To se zanaša na NEDOKUMENTIRAN argument F_FREESP funkcije fcntl, ki skrajša datoteko tako, da se konča na položaju, ki ga določa fl.l_start. Se manjša čudesa nikoli ne bodo končala? */ if (fcntl (fd, F_FREESP, &fl) < 0) return -1; } return 0; } #else int ftruncate (fd, length) int fd; off_t length; { return chsize (fd, length); } #endif #endif
{}
`` ukaza find
ne naredi to, kar želim?
Subject: Why doesn't find's "{}" symbol do what I want? Date: Thu Mar 18 17:16:55 EST 1993
Ukaz find
ima izbiro -exec
, ki izvede podani ukaz na vseh
izbranih datotekah. Ukaz find
zamenja vse nize ,,{}
``, ki
jih vidi v ukazni vrstici, z imenom datoteke, ki jo trenutno obdeluje.
Tako lahko lepega dne poskusiti uporabiti find
, da bi pognali
ukaz na vsaki datoteki, imenik po imenik. Poskusite lahko tole:
find /pot -type d -exec ukaz {}/\* \;
upajoč, da bo find
izvajal ukaze takole:
ukaz imenik1/*
ukaz imenik2/*
...
Žal find
razvije simbol ,,{}
`` le, ko nastopa sam. Vse
drugo, kot na primer ,,{}/*
`` bo find
pustil pri miru,
torej bo namesto želenega dejanja izvedel
ukaz {}/*
ukaz {}/*
...
enkrat za vsak imenik. To je lahko hrošč, lahko je odlika,
a shajati moramo s takim vedenjem.
Kako se mu izognemo? En način je s trivialnim kratkim skriptom v
ukazni lupini, denimo ,,./naredi
``, ki vsebuje le
ukaz "$1"/*
Potem lahko uporabljate
find /pot -type d -exec ./naredi {} \;
Ali, če se želite izogniti skriptu ,,./naredi
``, lahko
uporabite
find /pot -type d -exec sh -c 'ukaz $0/*' {} \;
(To deluje, ker se znotraj ,ukaza` ,,sh -c 'ukaz' A B C ...
``,
$0
razvije v A
, $1
v B
, in tako naprej.)
Ali pa uporabite trik sestavljanja ukaza s pripomočkom sed
:
find /pot -type d -print | sed 's:.*:ukaz &/*:' | sh
Če na vsak način želite zmanjšati število klicev ukaza ,,ukaz
``,
lahko pogledate, če ima vaš sistem ukaz xargs
. Ukaz xargs
prebere argumente po vrsticah s standardnega vhoda in jih zbere,
kolikor le lahko, v eno samo ukazno vrstico. Uporabite lahko torej
find /pot -print | xargs ukaz
kar bo pognalo enkrat ali večkrat
ukaz datoteka1 datoteka2 datoteka3 imenik1/datoteka1 imenik1/datoteka2
Žal to ni popolnoma robustna ali varna rešitev. Xargs
pričakuje,
da se vhodne vrstice končajo z znakom za novo vrsto (angl. newline),
zato se bo zmedel ob datotekah, poimenovanih s čudnimi znaki, kakršni
so znaki za novo vrsto.
Subject: How do I set the permissions on a symbolic link? Date: Thu Mar 18 17:16:55 EST 1993
Zaščite (angl. permissions, dovolilnice tipa -rw-r--r-
) pri
simboličnih povezavah pravzaprav ne pomenijo ničesar. Edina
dovoljenja, ki štejejo, so zaščite datotek, na katere kažejo povezave.
Subject: How do I "undelete" a file? Date: Thu Mar 18 17:16:55 EST 1993
Nekoč boste po pomoti napisali nekaj kot ,,rm * .foo
`` in
ugotovili, da ste pravkar pobrisali ,,*
`` namesto ,,*.foo
``.
Mislite si, da je to obred minljivosti.
Seveda bi moral vsak spodoben upravitelj sistema redno delati varnostne kopije. Preverite pri svojem upravitelju, če je na voljo nedavna varnostna kopija vaše datoteke. Če te ni, berite naprej.
Pri vseh okoliščinah ko enkrat pobrišete datoteko z ukazom
,,rm
``, te ni več. Ko na datoteki uporabite ukaz ,,rm
``,
sistem popolnoma pozabi, kateri bloki, razsejani po disku, so bili del
vaše datoteke. Še huje - bloki pravkar pobrisane datoteke bodo prvi
uporabljeni in prepisani, ko bo sistem potreboval več diskovnega
prostora. Vendar nikoli ne recite nikoli. Teoretično je mogoče
vrniti delčke podatkov, če takoj po ukazu rm
zaustavite
sistem (angl. shut down). A tedaj raje imejte pri roki zelo
čarovniški tip osebe, ki ima na voljo dolge ure ali dneve, da vam
obnovi pobrisano.
Ko po pomoti pobrišete datoteko z rm
, bo prvi nasvet, da naredite
vzdevek v ukazni lupini ali pa proceduro, ki bo spremenila ukaz
,,rm
`` tako, da boste datoteko namesto pobrisali, raje prestavili
v koš za smeti. Tako lahko datoteke obnovite, če napravite napako, a
morate občasno prazniti koš za smeti. Dve opombi: prvič, splošno
mnenje je, da je to slaba ideja. Na novo lastnost ukaza se boste
vselej zanašali in ko se boste znašli na običajnem sistemu, kjer ukaz
,,rm
`` zares izvede ,,rm
``, boste prišli v težave. Drugič,
verjetno boste ugotovili, da je ob vsej zmešnjavi z diskovnim
prostorom in časom, porabljenim za vzdrževanje koša za smeti, vseeno
lažje malo bolj paziti pri uporabi ukaza ,,rm
``. Za začetnike
zadošča, da si v vašem priročniku ogledate izbiro ,,-i
`` ukaza
,,rm
``.
Če ste še vedno pogumni, potem je tukaj možen preprost odgovor.
Naredite si ukaz ,,can
``, ki bo premikal datoteke v
imenik-smetnjak trashcan
. V csh(1)
lahko postavite
naslednje ukaze v datoteko ,,.login
`` v vašem domačem imeniku:
alias can 'mv \!* ~/.trashcan' # vrzi datoteko/e v koš
alias mtcan 'rm -f ~/.trashcan/*' # nepovratno izprazni koš
if ( ! -d ~/.trashcan ) mkdir ~/.trashcan # zagotovi obstoj koša
Morda boste tudi želeli postaviti:
rm -f ~/.trashcan/*
v datoteko ,,.logout
`` v vašem domačem imeniku, da boste
samodejno izpraznili koš vselej, ko se odjavite. (Različici za ukazni
lupini sh
in ksh
sta prepuščeni bralcu kot vaja.)
Pri projektu Athena na MIT-u je nastal obsežen paket za
brisanje/vrnitev/izbris/čiščenje, ki lahko služi kot popolno
nadomestilo za rm
, vendar dopušča obnovitev datotek. Ta paket je
bil objavljen v
comp.sources.misc (volume 17, issue 023-026)
Subject: How can a process detect if it's running in the background? Date: Thu Mar 18 17:16:55 EST 1993Najprej razčistimo: želite vedeti, ali tečete v ozadju ali želite vedeti, ali tečete interaktivno? Če se odločate, ali boste izpisovali pozivnik in podobno, je to verjetno boljši kriterij. Poglejte, če je standardni vhod terminal:
sh: if [ -t 0 ]; then ... fi
C: if(isatty(0)) { ... }
V splošnem ne morete preveriti, ali tečete v ozadju. Osnovni problem
je, da imajo različne ukazne lupine in različne različice Unixa
različne predstave o tem, kaj pomenita ,,ospredje`` in ,,ozadje`` - in
na najbolj pogostem tipu sistema z dobro razčiščenima pojmoma se lahko
programi poljubno premikajo med ospredjem in ozadjem!
Sistemi Unix brez nadzora opravil tipično postavijo proces v ozadje
tako, da ignorirajo SIGINT
in SIGQUIT
ter preusmerijo
standardni vhod na ,,/dev/null
``; to naredi ukazna lupina.
Ukazne lupine, ki podpirajo nadzor opravil (angl. job control), na sistemih Unix, ki podpirajo nadzor opravil, postavijo proces v ozadje tako, da mu priredijo identifikacijsko številko ID različno od skupine procesov, ki pripadajo terminalu. V ospredje ga premaknejo tako, da popravijo skupinsko procesno številko terminala (angl. terminal's process group ID) s procesno številko. Ukazne lupine, ki ne podpirajo nadzora opravil, na sistemih Unix, ki podpirajo nadzor opravil, tipično naredijo isto kot ukazne lupine na sistemih, ki ne podpirajo nadzora opravil.
Subject: Why doesn't redirecting a loop work as intended? (Bourne shell) Date: Thu Mar 18 17:16:55 EST 1993
Oglejte si naslednji primer:
foo=bar
while read line
do
# naredi nekaj z $line
foo=bletch
done < /etc/passwd
echo "foo je zdaj: $foo"
Kljub prireditvi ,,foo=bletch
`` bo to izpisalo ,,foo je
zdaj: bar
`` v mnogih izvedbah Bournove ukazne lupine. Zakaj?
Zaradi naslednje, pogosto nedokumentirane lastnosti zgodovinskih
Bournovih ukaznih lupin: preusmerjanje kontrolne strukture (kot je
zanka ali stavek ,,if
``) naredi ukazno podlupino, v kateri se
struktura izvede; spremenljivke, nastavljene v podlupini (kot priredba
,,foo=bletch
``), seveda ne vplivajo na trenutno lupino.
Odbor za standardizacijo ukaznih lupin in vmesnika orodij POSIX 1003.2
prepoveduje zgornje neutemeljeno ravnanje, se pravi, na sistemu,
usklajenemu s P1003.2, v Bournovih ukaznih lupinah zgornji primer
izpiše ,,foo je zdaj: bletch
``.
V zgodovinskih (in s P1003.2 usklajenih) izvedbah uporabite naslednji trik, da se izognete težavam s preusmerjanjem:
foo=bar
# datotečni deskriptor 9 naj postane duplikat datotečnega deskriptorja 0 (stdin);
# potem poveži stdin s /etc/passwd; originalni stdin smo si zdaj
# ,zapomnili` v datotečnem deskriptorju 9; glej dup(2) in sh(1)
exec 9<&0 < /etc/passwd
while read line
do
# naredi nekaj z $line
foo=bletch
done
# naj bo stdin duplikat datotečnega deskriptorja 9, se pravi,
# spet ga povežemo z originalnim stdin; zatem zapremo deskriptor 9
exec 0<&9 9<&-
echo "foo je zdaj: $foo"
To bi moralo vselej izpisati ,,foo je zdaj; bletch
``.
Prav, vzemite naslednji primer:
foo=bar
echo bletch | read foo
echo "foo je zdaj: $foo"
To bo izpisalo ,,foo je zdaj: bar
`` v veliko izvedbah in
,,foo je zdaj: bletch
`` v nekaterih drugih. Zakaj? V
splošnem se vsak del cevovoda izvede v drugi ukazni podlupini; v
nekaterih izvedbah pa je zadnji ukaz v cevovodu izjema: če je to
vgrajeni ukaz, kakršen je ,,read
``, ga bo izvedla trenutna
lupina, sicer pa bo izveden v podlupini.
POSIX 1003.2 dovoljuje oba načina, zaradi česar se prenosljivi skripti ne morejo zanašati na nobenega od njiju.
passwd
, ftp
, telnet
, tip
in druge interaktivne programe v skriptu ukazne lupine v ozadju?
Subject: How do I run ... interactive programs from a shell script ... ? Date: Thu Mar 18 17:16:55 EST 1993Ti programi pričakujejo terminalski vmesnik. Ukazne lupine jim ga posebej ne priskrbijo. Torej, ti programi ne morejo biti avtomatizirani v skriptih ukaznih lupin.
Program ,,expect
`` poskrbi za programabilen terminalski vmesnik
za avtomatsko interakcijo s takimi programi. Naslednji skript za
expect
je primer neinteraktivne različice ukaza passwd(1)
.
# uporabniško ime je dano kot 1. argument, geslo kot 2.
set geslo [index $argv 2]
spawn passwd [index $argv 1]
expect "*password:"
send "$geslo\r"
expect "*password:"
send "$geslo\r"
expect eof
Program expect
lahko delno avtomatizira interakcijo, kar je
posebej uporabno za telnet
, rlogin
, razhroščevalnike in
druge programe, ki nimajo vgrajenega ukaznega jezika. Distribucija
priskrbi tudi poskusni skript za nenehen zagon igre rogue
,
dokler se ne pojavi dobra začetna postavitev. Potem se nadzor vrne
uporabniku, ki lahko uživa v igri.
Na srečo so bili napisani nekateri programi za obdelavo povezave na terminalu pseudo-tty, tako da lahko poganjate te vrste programov v skriptu.
Program expect
dobite tako, da pošljete ,,send
pub/expect/expect.shar.Z
`` na naslov
library@cme.nist.gov
ali uporabite anonimni FTP za prenos imenovane datoteke s strežnika
ftp.cme.nist.gov.
Še ena možnost rešitve je z uporabo programa pty 4.0, ki deluje kot
program pod sejo psevdo-tty, in ga dobite na
comp.sources.unix, volume 25.
Postopek, ki to naredi prek pty
z uporabo poimenovanih cevi, je
videti takole:
#!/bin/sh
/etc/mknod out.$$ p; exec 2>&1
( exec 4<out.$$; rm -f out.$$
<&4 waitfor 'password:'
echo "$2"
<&4 waitfor 'password:'
echo "$2"
<&4 cat >/dev/null
) | ( pty passwd "$1" >out.$$ )
Tukaj je ,,waitfor
`` preprost C-jevski program, ki bere s
standardnega vhoda znak po znak, dokler ti znaki niso enaki njegovemu
argumentu.
Preprostejša rešitev z uporabo pty
(ki ima to napako, da se ne
sinhronizira pravilno s programom passwd
) je
#!/bin/sh
( sleep 5; echo "$2"; sleep 5; echo "$2") | pty passwd "$1"
Subject: How do I find the process ID of a program with a particular name ... ? Date: Thu Mar 18 17:16:55 EST 1993
V skriptu ukazne lupine:
Doslej še ni pripomočka, ki bi bil posebej načrtovan za preslikovanje med imeni programov in procesnimi ID-ji. Še več, takšne preslikave so pogosto nezanesljive, saj lahko obstaja več procesov z enakim imenom in ker je možno, da proces med tekom spreminja svoje ime. Toda pogosto zadošča, da uporabimo naslednji cevovod, ki izpiše seznam procesov (v vaši lasti) z določenim imenom:
ps ux | awk '/ime/ && !/awk/ {print $2}'
Pri tem zamenjajte ,,ime
`` z imenom procesa, ki ga iščete.
Splošni postopek prestreže izhod ukaza ps
in z orodji awk
ali grep
in podobnimi poišče vrstice z določenim nizom znakov ter
izpiše polje PID v teh vrsticah. Opazili boste, da zgornji ukaz
,,!/awk/
`` iz izpisa izloči sočasni proces awk
.
Morda boste morali v odvisnosti od vrste Unixa, ki ga uporabljate,
spremeniti argumente za ps
.
V kodi programa v C-ju:
Prav tako kot ni nobenega pripomočka, posebej načrtovanega za preslikavo med imeni programov in procesnimi ID-ji, tudi ni (prenosljive) C-jevske knjižnice funkcij za to opravilo.
Nekateri proizvajalci sicer priskrbijo funkcije za branje pomnilnika
jedra; na primer, Sun vključuje funkcije ,,kvm_
`` in Data General
funkcije ,,dg_
``. Možno je, da jih sme uporabljati vsak
uporabnik, a morebiti so dostopne le super-uporabniku (ali uporabniku
v skupini ,,kmem
``), če je na vašem sistemu omejeno branje
pomnilnika jedra. Nadalje so te funkcije pogosto nedokumentirane ali
vsaj slabo dokumentirane in se lahko spreminjajo od izdaje do izdaje.
Nekateri proizvajalci naredijo datotečni sistem ,,/proc
``, ki
se kaže kot imenik s kupom datotek v njem. Datoteke so poimenovane s
števili, ki ustrezajo ID procesov. Te datoteke lahko odpirate in
berete ter tako dobite podatke o procesu. Kot rečeno pa je dostop do
tega imenika morebiti omejen in vmesnik se lahko spreminja od sistema
do sistema.
Če ne morete uporabiti posebnih proizvajalčevih knjižničnih funkcij in
če nimate sistema /proc
ter bi radi vse postorili v C-ju,
boste morali sami brskati po pomnilniku jedra. Za dober primer, kako
se to počne na večih sistemih, poglejte izvorno kodo ,,ofiles
``,
dostopno v arhivih novičarske skupine
comp.sources.unix. (Paket,
imenovan ,,kstuff
``, ki pomaga pri brskanju po jedru, je bil
poslan na
alt.sources v maju
1991 in je tudi dostopen po anonimnem FTP-ju kot
{329{6,7,8,9},330{0,1}}
v imeniku
usenet/alt.sources/articles/
na
wuarchive.wustl.edu.
rsh
?
Subject: How do I check the exit status of a remote command executed via "rsh"? Date: Thu Mar 18 17:16:55 EST 1993
Tole ne deluje:
rsh nek-stroj nek-zoprn-ukaz || echo "Ukazu je spodletelo"
Izhodno stanje ,,rsh
`` je 0 (uspeh), če se sam program rsh
konča uspešno, kar verjetno ni to, kar želite.
Če želite preveriti izhodni status oddaljenega programa, lahko
uporabite skript ,,ersh
`` Maartena Litmaatha, ki je bil poslan
na
alt.sources v oktobru 1994.
Skript ersh
je lupinski skript, ki pokliče rsh
, uredi, da
oddaljen stroj na koncu izpiše status zadnjega izvedenega ukaza, in
konča v tem izhodnem stanju.
awk
podati tudi spremenljivke ukazne lupine?
Subject: Is it possible to pass shell variable settings into an awk program? Date: Thu Mar 18 17:16:55 EST 1993
Za to obstajata dva različna načina. S prvim preprosto razvijemo spremenljivke povsod, kjer je to potrebno v programu. Na primer, seznam vseh terminalov tty, ki jih uporabljate, dobite takole:
who | awk '/^'"$USER"'/ { print $2 }'
Enojni narekovaji se uporabljajo za označevanje programa v awk
-u,
saj se v njem pogosto uporablja znak ,$
`, ki je lahko
interpretiran v ukazni lupini, če zaprete program v dvojne narekovaje,
ne pa tudi, če ga zaprete v enojne. V tem primeru hočemo, da
ukazna lupina interpretura ,$
` v ,,$USER
``,
zato zapremo enojne narekovaje in potem vstavimo ,,$USER
``
v dvojne. Pozorni bodite na to, da ni nikjer nobenih presledkov,
torej bo ukazna lupina vse skupaj videla kot en sam argument. Opazili
boste tudi, da dvojni narekovaji v tem konkretnem primeru verjetno
niso potrebni, lahko bi torej naredili tudi
who | awk '/^'$USER'/ { print $2 }'
a jih moramo vseeno vključiti, saj bi lahko lupinska spremenljivka
$USER
vsebovala posebne znake, kot so presledku.
Drugi način za podajanje spremenljivih nastavitev programu awk
je
uporaba pogosto nedokumentirane lastnosti awk
-a, ki dovoljuje, da
se spremenljivke podajajo v ukazni vrstici kot ,,lažna imena
datotek``. Na primer:
who | awk '$1 == uporabnik { print $2 }' uporabnik="$USER" -
Spremenljivke se uporabijo, ko se zasledijo v ukazni vrstici, torej,
na primer, lahko s to tehniko naročite programu awk
, kako naj
se obnaša za različne datoteke. Na primer:
awk '{ program, ki je odvisen od s }' s=1 datoteka1 s=0 datoteka2
Opazite, da nekatere verzije programa awk
povzročijo, da
se pred izvedbo bloka BEGIN
zasledijo spremenljivke pred vsemi
pravimi imeni datotek, toda nekatere ne, zato se na to ne gre
zanašati.
Opazite tudi, da awk
ob navedbi spremenljivk namesto pravih
datotek sam od sebe ne bo bral s stdin, zato morate dodati argument
,,-
`` na konec vašega ukaza, kot je to v predprejšnjem primeru.
Tretja možnost je uporaba novejše verzije programa awk
(nawk
),
ki dopušča neposreden dostop do spremenljivk okolja. Na primer:
nawk 'END { print "Vaša pot je " ENVIRON["PATH"] }' /dev/null
Subject: How do I get rid of zombie processes that persevere? From: Jonathan I. Kamens From: casper@fwi.uva.nl (Casper Dik) Date: Thu, 09 Sep 93 16:39:58 +0200
Žal je nemogoče posplošiti, kako naj se zaključi podproces, saj se na različnih vrstah Unixa mehanizmi medsebojno razlikujejo.
Predvsem morate narediti wait()
za podproces na vseh
vrstah Unixa. Ne poznam Unixa, ki bi avtomatično pospravil za
podprocesom, ki se je končal, če mu tega niste posebej naročili.
Drugič, če na nekaterih, iz SysV izpeljanih, sistemih naredite
,,signal(SIGCHLD, SIG_IGN)
`` (no, pravzaprav je lahko
SIGCLD
namesto SIGCHLD
, toda večina novejših sistemov SysV
ima ,,#define SIGCHLD SIGCLD
`` v datotekah z glavami), se
otroški procesi samodejno počistijo, brez nadaljnega truda na vaši
strani. Najpreprosteje to preverite na svojem stroju, tako da
poskusite, ali deluje, čeprav se ni priporočljivo zanašati na to, če
poskušate pisati prenosljivo kodo. Žal POSIX tega ne predpisuje.
Nastavitev SIGCHLD
na SIG_IGN
je v POSIX-u nedefinirana,
torej tega ne morete uporabiti v svojem programu, če naj bo skladen s
standardom POSIX.
Kakšen je potemtakem način, skladen s POSIX? Kot rečeno, morate
namestiti upravljalnik signalov (angl. signal handler) in počakati z
wait
. V POSIX-u so upravljalniki signalov nameščeni s funkcijo
sigaction
. Ker vas ne zanimajo ,,zaustavljeni`` podprocesi, pač
pa prekinjeni, dodajte zastavicam sa_flags
vrednost
SA_NOCLDSTOP
. Čakanje brez blokiranja se izvede z
waitpid()
. Prvi argument funkciji waitpid
naj bo -1
(čakaj na katerikoli PID), tretji naj bo WNOHANG
. To je danes
najbolj prenosljiv način in bo verjetno v prihodnosti postal še bolj
prenosljiv.
Če vaš sistem ne podpira standard POSIX, obstaja vrsta rešitev.
Najpreprosteje je uporabiti signal(SIGCHLD, SIG_IGN)
, če to
deluje. Če samodejnega čiščenja ne morete doseči s SIG_IGN
,
morate napisati upravljalnik signalov, da bo to storil. Zaradi
naslednjih nekonsistentnosti ga sploh ga ni preprosto napisati tako,
da bi deloval na vseh vrstah Unixa:
Na nekaterih vrstah Unixa se upravljalnik signalov SIGCHLD
pokliče, če se konča eden ali več podprocesov. Če vaš
upravljalnik signalov le enkrat pokliče wait()
, to pomeni, da
ne bo počistil za vsemi podprocesi. Prepričan sem, da ima na srečo
programer na teh Unixih, na katerih to drži, vselej na voljo klica
wait3()
ali waitpid()
, ki z izbiro WNOHANG
preverita,
ali morata počistiti še kaj podprocesov. Vaš upravljalnik signalov na
sistemih, ki poznajo funkciji wait3()
/waitpid()
,
toliko časa kliče eno od teh funkcij z izbiro WNOHANG
, dokler ni
več nobenih otrok za počiščenje. Boljša izbira je waitpid()
, ker
je vključena v POSIX.
Na sistemih, izpeljanih iz SysV, se signali SIGCHLD
regenerirajo,
če po izhodu upravljalnika signala SIGCHLD
še vedno ostane kakšen
otroški proces za počistiti. Torej je na večini sistemov SysV ob
klicu upravljalnika signalov varno predpostaviti, da morate počistiti
le en signal, in pričakovati, da se bo upravljalnik poklical ponovno,
če je po izhodu iz njega ostalo še kaj nepočiščenih signalov.
Na starejših sistemih ni nobenega načina, da bi preprečili
upravljalnikom signalov, da se ob klicu samodejno nastavijo na
SIG_DFL
. Na takih sistemih morate kot zadnjo stvar upravljalnika
signalov postaviti ,,signal(SIGCHILD, catcher_func)
`` (kjer
je ,,catcher_func
`` ime upravljalniške funkcije), da se
resetirajo.
Na srečo novejše izvedbe dovoljujejo upravljalnikom signalov, da se
namestijo, ne da bi bili resetirani na SIG_DFL
, ko se požene
upravljalniška funkcija. Temu problemu se na sistemih, ki nimajo
wait3()
/waitpid()
, a imajo SIGCLD
, izognete
tako, da morate resetirati upravljalnik signalov s klicem
signal()
, ko ste izvedli v upravljalniku vsaj en wait()
,
vsakič, ko se kliče. Zaradi zgodovinskih združljivostnih razlogov bo
System V obdržal staro pomenoslovje (resetiral upravljalnik ob klicu)
funkcije signal()
. Upravljalniki signalov, ki se prilepijo, se
lahko namestijo s sigaction()
ali sigset()
.
Povzetek vsega tega je, da morate funkcijo waitpid()
(POSIX) ali
wait3()
na sistemih, ki jo imajo, tudi uporabljati in, da mora
teči vaš upravljalnik signalov v zanki, na sistemih, ki pa teh funkcij
nimajo, morate imeti ob vsaki zahtevi po upravljalniku signalov po en
klic wait()
.
Še ena stvar - če ne želite iti skozi vse te težave, obstaja
prenosljiv način, da se izognete temu problemu, čeprav je malce manj
učinkovit. Vaš starševski proces naj se razveji (s fork
) in
potem čaka na mestu, da se konča otroški proces. Otroški proces se
potem spet razveji, in vam da otroka in vnuka. Otrok se takoj prekine
(in torej starš, ki ga čaka, izve za njegov konec in nadaljuje z
delom), vnuk pa počne, kar bi moral prvotno narediti otrok. Ker je
njegov starš končan, ga nasledi proces init
, ki bo opravil vse
potrebno čakanje. Ta metoda je neučinkovita, ker potrebuje dodatno
razvejanje, a je popolnoma prenosljiva.
Subject: How do I get lines from a pipe ... instead of only in larger blocks? From: Jonathan I. Kamens Date: Sun, 16 Feb 92 20:59:28 -0500
Knjižnica stdio trenutno dela vmesno pomnenje (angl. buffering) različno, odvisno od tega, ali misli, da teče na tty ali ne. Če misli, da je na tty, dela vmesno pomnenje po posameznih vrsticah; sicer uporablja večji vmesni pomnilnik kot je ena vrstica.
Če imate izvorno kodo odjemnika, za katerega bi radi onemogočili
vmesno pomnenje, lahko za to uporabite setbuf()
ali
setvbuf()
.
Če ne, je najboljši način, da prepričate program, da teče na tty tako,
da ga poženete pod pty, se pravi, da uporabite program ,,pty
``,
omenjen v vprašanju
,,Kako poženem passwd, ftp, telnet, tip in druge interaktivne programe v skriptu ukazne lupine v ozadju?``.
Subject: How do I get the date into a filename? From: melodie neal <melodie@comtech.ct.oz.au> Date: Fri, 7 Oct 1994 09:27:33 -0400
To ni težko, a je na prvi pogled malce kriptično. Začnimo najprej s
samim ukazom date
: date
lahko vzame niz s formatom izpisa in
ga upošteva. Niz za formatiranje mora biti objet z narekovaji, da
preprečimo ukazni lupini poskus njegove interpretacije, preden ga dobi
sam ukaz date
. Poskusite tole:
date '+%d%m%y'
Morali bi dobiti nekaj kot 130994. Če želite imeti vmes ločila,
preprosto postavite znake, ki bi jih radi uporabljali, v niz za
formatiranje (brez nagibnic ,/
`):
date '+%d.%m.%y'
Obstaja veliko simbolov, ki jih lahko uporabite v nizu za formatiranje:
o njih izveste v strani referenčnega priročnika (man date
).
Zdaj pa še to spravimo v ime datoteke. Denimo, da bi radi ustvarili
datoteke imenovane report.130994
(ali katerikoli datum že
je danes):
FILENAME=report.`date '+%d%m%y'`
Opazite, da tukaj uporabljamo dva nabora narekovajev: notranji nabor
preprečuje formatirnemu nizu prezgodnjo interpretacijo; zunanji nabor
pove ukazni lupini naj izvede objeti ukaz, in zamenja rezultat v izraz
(zamenjava ukazov, angl. command substitution).
#!...
``?
Subject: Why do some scripts start with #! ... ? From: chip@@chinacat.unicom.com (Chip Rosenthal) Date: Tue, 14 Jul 1992 21:31:54 GMT
Chip Rosenthal je v preteklosti v novičarski skupini comp.unix.xenix odgovoril na zelo podobno vprašanje.
Mislim, da ljudi bega, da obstajata dva različna mehanizma, ki se oba
začneta z znakom ,#
`. Oba rešujeta isti problem na zelo omejenem
naboru primerov - a nista nič manj različna.
Nekaj ozadja. Ko se jedro sistema UNIX odloči pognati program (eden
sistemskih klicev družine exec()
), najprej pogleda prvih 16 bitov
datoteke. Teh 16 bitov se imenuje magična številka. Prvič,
magična številka preprečuje jedru, da bi naredilo kaj neumnega, kot na
primer poskušalo izvesti datoteko z bazami podatkov o vaših strankah.
Če jedro ne prepozna magične številke, se pritoži z napako
ENOEXEC
. Program požene le, če je magična številka prepoznavna.
Drugič, sčasoma so se uvajali različni formati izvedljivih datotek in
magična številka ni le povedala jedru ali
naj izvede datoteko,
pač pa tudi kako naj jo izvede. Na primer, če prevedete program
na sistemu SCO XENIX/386 in nesete binarno datoteko na sistem
SysV/386, bo jedro prepoznalo magično številko in reklo ,,Aha! To je
binarna datoteka tipa x.out!`` in se nastavilo tako, da bo uporabljalo
klice, združljive s sistemom XENIX.
Pomnite, da jedro lahko poganja le binarne izvedljive slike pomnilnika.
Kako torej, se vprašujete, tečejo skripti? Konec koncev, lahko
napišem v pozivniku ukazne lupine ,,moj.skript
`` in ne dobim
napake ENOEXEC
. Odgovor: skripti se ne izvajajo v jedru, pač
pa v ukazni lupini. Koda v ukazni lupini lahko izgleda podobno:
/* poskusi pognati program */ execl(program, basename(program), (char *)0); /* klic exec je spodletel - morda gre za skript ukazne lupine? */ if (errno == ENOEXEC) execl ("/bin/sh", "sh", "-c", program, (char *)0); /* Oh, ne, g. Bill!! */ perror(program); return -1;
Če je klic execl()
uspešen in se program zažene, se koda za
execl()
nikoli ne požene. V zgornjem primeru uspešen
execl()
programa ,program
` pomeni, da se spodnje vrstice
primera ne izvedejo. Namesto tega sistem izvaja binarno datoteko
,program
`.
Če, po drugi strani, prvi klic execl()
spodleti, ta hipotetična
ukazna lupina pogleda, zakaj ji je spodletelo. Če klic execl()
ni uspel, ker ,program
` ni bil prepoznan kot binarna izvedljiva
datoteka, ga ukazna lupina poskuša naložiti kot skript ukazne lupine.
Ljudje iz Berkeleya so imeli čedno idejo, kako razširiti način, na
katerega jedro zaganja programe. Popravili so jedro, da prepozna
magično številko ,#!
` (magične številke so velike 16 bitov in
dva 8-bitna znaka tvorita 16 bitov, prav?). Ko je jedro prepoznalo
magično številko ,#!
`, je prebralo tudi ostanek vrstice in ga
obravnavalo kot ukaz, ki ga naj požene na vsebini datoteke. S tem
popravkom lahko zdaj počnete podobne stvari:
#! /bin/sh
#! /bin/csh
#! /bin/awk -F:
Ta popravek je obstajal le v svetu Berkeleya, in je prešel na jedra
USG kot del sistema System V Release 4. Pred V.4 jedro ni imelo
možnosti ničesar drugega, kot nalaganje in zaganjanje binarnih
izvedljivih slik pomnilnika, razen v primerih proizvajalčeve dodane
vrednosti.
Vrnimo se še nekaj let v preteklost, v čas, ko je več in več ljudi, ki
so poganjali Unixe na jedrih USG, govorilo ,,/bin/sh
je zanič
kot interaktivni uporabniški vmesnik! Hočem csh
!``. Nekateri
proizvajalci so dodali csh
v njihove distribucije, čeprav
csh
ni bil del distribucije USG UNIX.
To pa je predstavljalo problem. Denimo, da spremenite svojo prijavno
ukazno lupino na /bin/csh
. Denimo, nadalje, da ste bebec in
vztrajate na programiranju skriptov csh
. Vsekakor bi radi bili
sposobni napisati ,moj.skript
` in ga s tem pognati, čeprav je
to skript za csh
. Namesto, da bi ga pognali skozi
/bin/sh
, želite, da se skript požene takole:
execl ("/bin/csh", "csh", "-c", "moj.skript", (char *)0);
Kaj pa vsi tisti obstoječi skripti -- nekateri od njih so deli
sistemske distribucije? Če se poženejo skozi csh
, se bodo stvari
kvarile. Potrebujete torej način, da poženete nekatere skripte skozi
csh
, in nekatere druge skozi sh
.
Vpeljana rešitev je bila, da se popravi csh
tako, da pogleda prvi
znak skripta, ki ga želite pognati. Če je ta ,#
`, bo csh
zagnal skript skozi /bin/csh
, sicer bo pognal skript skozi
/bin/sh
. Primer zgornje kode bi zdaj izgledal takole:
/* poskusi pognati program */ execl(program, basename(program), (char *)0); /* klic exec je spodletel - morda gre za skript ukazne lupine? */ if (errno == ENOEXEC && (fp = fopen(program, "r")) != NULL) { i = getc(fp); (void) fclose(fp); if (i == '#') execl ("/bin/csh", "csh", "-c", program, (char *)0); else execl ("/bin/sh", "sh", "-c", program, (char *)0); } /* Oh, ne, g. Bill!! */ perror(program); return -1;
csh
`. V jedru
se ni nič spremenilo in drugim ukaznim lupinam ni bilo nič dodano. Če
poskušate pognati skript s klicem execl()
, boste še vedno
dobili napako ENOEXEC
, pa naj se skript začne z znakom ,#
`
ali pa ne. Če poskušate pognati skript, ki se začne z znakom ,#
`
v čem drugem kot csh
(na primer v /bin/sh
), ga bo
obravnavala lupina sh
, ne csh
.
Drugič, čarovnija je v tem, da se bodisi skript začne z ,#
`, ali
pa se ne začne z ,#
`. Stvari kot so ,:
` in ,:
/bin/sh
` na začetku skriptov dela čarobne preprosto dejstvo, da
niso ,#
`. Torej je vse to identično, če je na začetku skripta:
:
: /bin/sh
<--- prazna vrstica
: /usr/games/rogue
echo "Hm ... sprašujem se, pod katero ukazno lupino tečem???"
V vseh teh primerih bodo vse ukazne lupine poskušale izvesti skript
z /bin/sh
.
Podobno, tudi vse naslednje je identično, če je na začetku skripta:
#
# /bin/csh
#! /bin/csh
#! /bin/sh
# Hm ... sprašujem se, pod katero ukazno lupino tečem???
Vse te vrstice se začnejo z znakom ,#
`. To pomeni, da se bo
skript pognal le, če ga boste poskušali pognati iz csh
,
sicer se bo pognal z /bin/sh
.
(Opomba: če poganjate ksh
, zamenjajte v zgornjem besedilu
,,sh
`` s ,,ksh
``. Kornova ukazna lupina je teoretično
združljiva z Bournovo, torej poskuša sama pognati te skripte. Vaše
izkušnje so lahko drugačne z nekaterimi drugimi dostopnimi ukaznimi
lupinami, kot so zsh
, bash
, itd.)
Očitno postane popravek za ,#
` nepotreben, če imate v jedru
podporo za ,#!
`. Pravzaprav je lahko nevaren, saj ustvarja zmedo
pri ugotovitvi, kaj naj bi se zgodilo v primeru ,,#!
/bin/sh
``.
Uporaba ,#!
` bolj in bolj prevladuje. System V Release 4
pobira številne lastnosti z Berkeleya, vključno s to. Nekateri
proizvajalci sistemov System V Release 3.2 nadgrajujejo na nekatere
bolj vidne dobrote V.4 in vam poskušajo prepričati, da je to dovolj
in, da ne potrebujete stvari, kot so pravi, delujoči tokovi ali
dinamično nastavljivi parametri jedra.
XENIX ne podpira ,#!
`. Ukazna lupina /bin/csh
na Xenixu
nima popravka za ,#
`. Podpora za ,#!
` v Xenixu bi bila
fina, a sam ne bi zadrževal diha med čakanjem nanjo.