Naprej Nazaj Vsebina

4. Vmesna vprašanja

4.1 Kako lahko ugotovim čas, ko je bila datoteka ustvarjena?

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).

4.2 Kako lahko uporabim 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.

4.3 Kako lahko skrajšam datoteko?

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:

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

4.4 Zakaj simbol ,,{}`` 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.

4.5 Kako lahko zaščitim simbolične povezave?

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.

4.6 Kako lahko ,,obnovim`` pobrisano datoteko?

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)

4.7 Kako lahko proces ugotovi, ali teče v ozadju?

Subject: How can a process detect if it's running in the background?
Date: Thu Mar 18 17:16:55 EST 1993
Najprej 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.

4.8 Zakaj preusmerjanje v zanki ne dela, kot bi moralo? (Bournova lupina)

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.

4.9 Kako poženem 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 1993
Ti 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"

4.10 Kako lahko v skriptu ali programu ugotovim ID procesa, ki pripada programu z določenim imenom?

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.

4.11 Kako preverim izhodni status oddaljenega ukaza, pognanega z 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.

4.12 Je mogoče programu 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

4.13 Kako se znebim procesov-zombijev, ki vztrajajo?

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.

4.14 Kako dobim vrstice iz cevovoda tako, kot se pišejo, namesto le v večjih blokih?

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?``.

4.15 Kako lahko vstavim datum v ime datoteke?

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).

4.16 Zakaj se nekateri skripti začnejo z ,,#!...``?

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;

(Ta primer je močno poenostavljen. Vpletenih je veliko več zadev, a to ponazarja bistvo, ki ga poskušam opisati.)

Č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;

Dve pomembni stvari, Prvič, to je popravek lupine ,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.


Naprej Nazaj Vsebina