A "minta-zh" feladatainak megoldása(i)

(Egyenlõre még hiányzanak belõle a VMS feladatok.)

Elsõ rész

  1. Irjon parancsot vagy parancs-sorozatot, ami az aktuális directory összes aldirectoryjának "ls -l"-beli sorát adja meg.

    1. ls -l | grep "^d"

    2. for i in *
       do
        if [ -d $i ]
         then ls -ld $i
        fi
       done

    3. find * -type d -prune -exec ls -ld {} \;

    4. find -type d -maxdepth 1 -exec ls -ld {} \;

    5. ls -ld `find -type d -print | grep -v ".*\/.*\/"`

    Az a. megoldás azt használja ki, hogy a megjelenítendõ sorok "d" beûvel kezdõdnek.
    A b. és c. megoldások lényeges része az, hogy az "ls" parancs "-d" opciója letiltja a directory(k) belsejének listázását.
    A c. megoldásban a "find" parancs "-prune" opciója ugyanezt teszi a "find" szintjén. (Nem gondolom azt, hogy valaki fejbõl tudja a "-prune" opciót, a c. megoldást inkább csak a zh. második részében képzelném el.)
    Az a-c. megoldásból hiányzanak az esetleges "rejtett" directoryk, de ezek az "ls -l" listából is kimaradnak.
    A d. - csak Linux alatt mûködõ - megoldás a rejtett directorykat is listázza.
    Az e. megoldás - amiben a "find" belsõ parancsként hajtódik végre - ugyanezt tudja minden Unixban, de sokkal lassúbb is lehet, mert belemegy az aldirectory strukrúrába, de az abból kapott sorokat a grep kihagyja azon az alapon, hogy legalább két "/" jelet tartalmaznak.

  2. Irja le, amit a hard- és symbolic link-rõl tud.

    Mindkettõvel elérhetjük azt, hogy egy újabb névvel hivatkozhassunk egy már létezõ fájlra.

    Az "ls -l" parancsban az alábbi módon látszanak az "index.htm" fájlhoz az
      ln index.ht index.htm
      ln -s index.html index.htm

    parancsokkal létrehozott fájlok:
      -rw-r--r-- 2   csa   users   6436 Nov 22 22:55 index.ht
      -rw-r--r-- 2   csa   users   6436 Nov 22 22:55 index.htm
      lrwxrwxrwx 1   csa   users      9 Sep 28 18:30 index.html -> index.htm

    A hard link egy ellenõrzött, szoros kapcsolat, a fájl, amire linkeltünk, "tud róla, hogy hány egyenrangú neve van", csak egy fájlrendszeren belül, csak fájokra hozható létre. Egy fizikai fájl, egy i-node tartozik hozzá.

    A symbolic link egy laza kapcsolatot teremt, különbözõ fájlrendszerek között is létrejöhet, directoryra is alkalmazható. A létrehozott pointer-fájl új i-node-ot jelent, a létrehozott kapcsolat nem egyenrangú, elõfordulhat, hogy a link nem létezõ vagy számunkra nem elérhetõ fájlra mutat.

  3. Adja meg a "pwd" parancs algoritmusát, ismerve, hogy a parancs az aktuális directorynak az i-node számát kapja meg, ebbõl kell meghatároznia a fájl nevét.
    .saját i-node
    ..szülõ i-node
    A directory elsõ két fájlbejegyzése közül a ".." alapján megtalálható a szülõ directory. Annak jobboldali oszlopából kikereshetjük a "saját i-node"-ot, aminek a bal oldalán a vizsgált directory neve áll. Ezt az eljárást folytathatjuk a directory struktúrán lefele addig, amig a szülõ i-node-ja meg nem egyezik a "saját i-node"-dal. Ennek a directorynak a neve "/". Emögé rakva - egymástól "/" jellel elválasztva - a kapott directory neveket, megkapjuk a feladat megoldását.
  4. Törölheti-e egy másik felhasználó, aki nem a "root", az alábbi parancsot követõen a "fontos_file" nevû fájlunkat ?
    chmod 700 fontos_file

    A válasz ettõl a "chmod"-tól nem függ. Ha a felhasználónak van "w" és "x" joga a fájlt tartalmazó directoryra, akkor törölheti a fájlt, a fájlra vonatkozó jogaitól függetlenül.

  5. Irjon parancsot vagy parancs-sorozatot, ami az összes háromjegyû számot a képernyõre írja.

    1. for i in 1 2 3 4 5 6 7 8 9
       do
        for j in 0 1 2 3 4 5 6 7 8 9
         do
          for k in 0 1 2 3 4 5 6 7 8 9
           do
            echo $i$j$k
           done
         done
       done

    2. i=99
      while [ $i != 999 ]
       do
        i=`expr $i + 1`
        echo $i
       done

    3. echo {1,2,3,4,5,6,7,8,9}{0,1,2,3,4,5,6,7,8,9}{0,1,2,3,4,5,6,7,8,9}

    4. echo x | awk '{for (i=100;i<1000;++i) print i}'

    5. awk 'BEGIN {for (i=100;i<1000;++i) print i}'

    A c. megoldás a leggyorsabb, de az "sh" shell nem tudja.

    A b. megoldásnak az "i"-t növelõ utasítása az alábbiak valamelyike is lehetne:
      i=`echo $i+1 | bc`
      i=`echo $i | awk '{print $0+1}'`
      i=$(($i+1))  
    # csak "bash" és "ksh" esetén
      i=$[$i+1]    
    # csak "bash" esetén

    A d. megoldásban az "echo x" jobb híján kell arra, hogy legyen input fájlja az "awk"-nak. (Amitõl az awk program ciklusa teljesen független !) Az e. megoldásban ez nem kell. (A BEGIN helyett END is szerepelhetne a sorban.)

Második rész

  1. Egy "tar" archív fájlt ki akarunk csomagolni. Elõtte szeretnénk tudni, mennyi helyet foglal majd el. Irjon parancsfájlt (szûrõt), ami ezt megadja a paraméterként vagy standard inputként adott, "tar -tvf"-el készült fájlra.
    A "tar -tvf archiv_file" outputja az alábbi formájú:

    -rw-r--r-- csa/users   6016 Nov 22 13:07 1998 index.htm
    -rwxr-xr-x root/bin    7108 May  4 06:21 1994 mkfs.minix

    1. s=0
      for i in `sed "s/ */ /g" $1 | cut -d" " -f3`
       do
        s=`expr $s + $i`
       done
      echo $s

    2. sed "s/ */ /g" $1 | cut -d" " -f3 | awk '{s=s+$1};END{print s}'

    3. awk '{s=s+$3};END{print s}' $1

    Az a. és b. megoldások mindegyikének tekintettel kell lennie a "cut" parancs azon gyengeségére, hogy az elhatárolójel pontosan egy helyköz. (Egy második helyköz már egy újabb "field".)

    A c. megoldás a b.-nél egyszerûbb azért is, mert az "awk" számára már "egy vagy több helyköz" az elhatárolójel.

    Az a. megoldás összegzõ (s=`expr $s + $i`) utasításáról ugyanaz mondható el, mint az 1.5 feladat b. megoldásának "i"-t növelõ utasításáról.

    Az ilyen formájú input sorok feldolgozására a "cut" parancs "-c" opciója is szóbajöhet. Azt használva nagyobb az esélye annak, hogy egy másik Unixban - ahol a sor egyes mezõinek esetleg nem ugyanaz a hossza - hibásan fut a parancs.

  2. Az elõzõ feladathoz kapcsolódóan írjon parancsfájlt (szûrõt), ami megadja a paraméterként vagy standard inputként adott, "tar -tvf"-el készült fájlra, hogy az "archívum"-ban szereplõ fájlok ill. directoryk közül melyik hónapban módosították utoljára a legtöbbet. (Évtõl függetlenül.)

    1. sed "s/ */ /g" $1 | cut -d" " -f4 | sort | uniq -c | sort | tail -1

    2. awk '{print $4}' $1 | sort | uniq -c | sort | tail -1

    3. awk '{++x[$4]; if (x[$4]>m) {m=x[$4];ho=$4}}
           END {print m, ho}' $1

    4. awk '{++x[$4]}
           END {for (i in x) if (x[i]>m) {m=x[i];ho=i}
                print m, ho}' $1

    Az a. és b. megoldások mindegyikének a "sort | uniq -c | sort" a lényege, megelõzve egy olyan programrésszel, ami a "hónap"-ot kivágja az inputból.

    A c. megoldás az "awk"-nak azt a lehetõségét használja ki, hogy egy tömb (jelen esetben az x) indexe tetszõleges string (jelen esetben a hónap neve) lehet. A tömbelemek értékeinek maximumát az "m" változóba, a hozzá tartozó hónapnevet (tömbindexet) a "ho" változóba teszi a program.

    A d. megoldás a maximumkeresést külön menetben, a tömb felépítése után végzi el. Azt akarom vele mutatni, hogyan lehet egy létrehozott "asszociatív tömb"-öt egy ciklusban feldolgozni.

  3. Irjon szûrõt, ami a paraméterként kapott, "txt" formába konvertált Excel táblázatot html táblázattá alakítja.
    A "txt" fájl sorai a táblázat sorai, a sorokban az oszlopokoat tabulátorjel (9-es kódú karakter) választja el egymástól.
    A html táblázat sorait "<TABLE>" tartalmú sor elõzi meg, "</TABLE>" tartalmú sor követi. Minden sor "<TR><TD>" jelsorozattal kezdõdik, és "</TD></TR>" jelsorozattal zárul. Soron belül az oszlopokat "</TD><TD>" jelsorozat választja el egymástól.

    1. TAB=`echo -ne "\011"`
      echo "<TABLE BORDER>"
      sed "s/^/<TR><TD>/
           s/$/<\/TR><\/TD>/
           s/$TAB/<\/TD><TD>/g" $1
      echo "</TABLE>"

    2. awk 'BEGIN {print "<TABLE BORDER>"}
           { printf "<TR><TD>"
             for (i=1;i<NF;i++) printf "%s</TD><TD>", $i
             printf "%s</TR></TD>\n", $NF
           }
           END {print "</TABLE>"}' $1

    Az a. megoldásban a "sed"-del helyettesítjük a "sor elejét", "sor végét" ill. a TAB jeleket a megfelelõ jelsorozatokkal. (A BORDER keretezett html-táblázatot eredményez.)

    A b. megoldásban kihasználjuk azt, hogy a TAB is automatikus elválasztójel az "awk" input sorban. Az NF változó az aktuális sor elemeinek számát tartalmazza. A "printf"-ben a "%s" egy változó tartalmának string formában történõ kiírását írja elõ. A "\n" csinál html-táblázat-soronként új sort, ami nem okvetlenül szükséges.

  4. A mail fájlok (pl. $MAIL, ~/mbox) szerkezete a következõ:
    Minden levél egy olyan "From " kezdetû sorral indul, amit vagy egy üres sor (BSD-Unix, Linux), vagy egy csak Ctrl+A-kat tartalmazó(AT&T-Unix) sor (vagy semmilyen sor sem) elõz meg. Az ezzel kezdõdõ "levél fejléc" egy üres sorig tart, azt követi a levél szövege.
    Irjon parancsfájlt, ami a paraméterként adott levélfájlról megmondja, hogy hány levél van benne. Azzal az egyszerûsítéssel oldja meg a feladatot, hogy minden levél pontosan egy "From " kezdetû sort tartalmaz.

    1. grep "^From " $1 | wc -l

    2. grep -c "^From " $1

    3. awk '{if (substr($0,1,5) == "From ") s++}; END{print s}' $1

    Az a. megoldásban a "wc" parancs számolja meg a "grep" által talált sorokat, a b. megoldásban a "grep" maga.
    A c. megoldásban az "if" feltétele rövidebben ($1 == "From") is lehetne.

  5. A mail fájlnak az elõzõ feladatban leírt "From " sorában a "From " után a levél küldõjének címe található. Irjon parancsfájlt, ami megmondja, hogy kitõl kaptuk a legtöbb levelet. A parancs paramétere a levélfájl neve, ha nem adjuk meg, akkor a $MAIL környezetváltozóban adott fájl.

    1. grep "^From " ${1:-$MAIL} | cut -d" " -f2 | sort | uniq -c | sort | tail -1

    2. awk '/^From /{++x[$2]; if (x[$2]>m) {m=x[$2];ki_kuldi=$2}}
           END {print m, ki_kuldi}' ${1:-$MAIL}

    A megoldások a 2. feladatéhoz hasonlóak.

    A b. megoldás arra mutat példát, hogy egy "awk" programrész feltételesen is végrehajtható, és a feltétel reguláris kifejezés is lehet. (A   /^From /   feltétel "if"-ben is szerepelhetne, az elõzõ feladat c) megoldásáéhoz hasonló módon.) Emellett az is látható, hogy egészen "cifra" stringek (pl. e-mail címek) is lehetnek tömbindexek az "awk"-ban.

    A ${1:-$MAIL} hivatkozás a legrövidebb megoldás arra, hogy a hiányzó paramétert a $MAIL értékével helyettesítsük. Hosszabban írva azt tehetnénk, hogy a

            case $# in
              0) PAR=$MAIL;;
              *) PAR=$1;;
            esac
    elõkészítést követõen (mindkét megoldásban) a $PAR-t tennénk a ${1:-$MAIL} helyére.
  6. Mi az alábbi parancsfájl részlet feladata? (A "read" parancs exit státusza akkor nem 0, ha fájl vége jelet - a billentyûzetrõl CTRL+D-t - kap inputként.)

    rm $L/alma 2>/dev/null
    while read alma
      do
        echo $alma >>$L/alma
      done

    Milyen egyszerûbb megoldást javasolna ugyanerre a célra az elõbbi helyett ?

    A parancs-sorozat a billentyûzetrõl (standard inputról) olvas Ctrl+D-ig (fájl végéig), és a beolvasottakat a $L/alma fájlba teszi. Helyettesíthetõ pl. az alábbi utasítással:

    cat >$L/alma

    A "cat" helyén sok egyéb szûrõ is állhatna. (Pl. tr "" "", sed "", tail +1, awk "{print}", grep ".", stb.)

  7. Irjon parancsfájlt, ami a(z elsõ) paraméterként kapott stringrõl eldönti, és a képernyõre írja, hogy az alábbi kategóriák melyikébe tartozik:
      csak számjegyeket tartalmaz
      csak nagybetûket tartalmaz
      csak kisbetûket tartalmaz
      csak betûket tartalmaz, de kis és nagybetû(ke)t is
      egyéb jeleket is tartalmaz

    1. KV=`echo $1 | sed "s/^$/ /
                         s/^[A-Z]*$/A/
                         s/^[a-z]*$/a/
                         s/^[0-9]*$/0/
                         s/^[a-zA-Z][a-zA-Z][a-zA-Z]*$/b/"`
      case $KV in
        A) echo nagybetuk;;
        a) echo kisbetuk;;
        b) echo betuk;;
        0) echo szamjegyek;;
        *) echo egyeb jelek;;
      esac

    2. NAGY=N; KIS=N; SZAM=N; EGYEB=N
      P1=$1
      while [ x$P1 != x ]
       do
        case $P1 in
         [A-Z]*) NAGY=I;;
         [a-z]*) KIS=I;;
         [0-9]*) SZAM=I;;
              *) EGYEB=I;;
        esac
        P1=`echo -n $P1 | tail +2c`
       done
      case ${NAGY}${KIS}${SZAM}$EGYEB in
       INNN) echo nagybetu;;
       NINN) echo kisbetu;;
       IINN) echo betu;;
       NNIN) echo szamjegyek;;
          *) echo valami mas;;
      esac

    Az a. megoldás azért nem lehet egyszerûbben egy "case" elágazás, mert a "case" feltételeiben a "*" nem reguláris kifejezés része, hanem a fájlnevekben szereplõ *-hoz hasonló "akárhány akármilyen jel" értékû "joker" karakter. Fontos, hogy az utolsó sed-helyettesítés "legalább 2" betûbõl álló, "elejétõl a végéig mind betû"-t helyettesít "b"-vel.

    A b. megoldás az elsõ jelet vizsgálja, aztán elhagyja és a maradékkal folytatja ugyanezt. Végül aszerint ágazik el a program, hogy miféle jeleket találtunk összesen. (Kihasználva azt, hogy amennyiben egy "case" ág végrehajtódik, a továbbiak feltétele már nem is lesz vizsgálva.)

  8. A "nice man woman" parancs eredménye a következõ üzenet:
      No manual entry for woman

    Melyik parancs írja a képernyõre, és hogyan irányítható az "xyz" fájlba ?

    Egy "csökkentett prioritással" végrehajtott "man" parancs hibaüzenetét látjuk a képernyõn, ami "2>xyz"-vel irányítható az "xyz" fájlba.


Csizmazia Albert csa_a_valerie.inf.elte.hu
Maulis Ádám maulis_a_ludens.elte.hu