Unix ja shell-ohjelmointi 2001, demo 4 Unix ja shell-ohjelmointi 2001, demo 4 mallivastaukset

Vaikka tehtävät olikin ajateltu tehtäviksi ilman awkia, seuraavassa on joistakin myös awk-ratkaisu (vaikkapa vihjeeksi seuraaviin...).

  1. Yleensä jos halutaan vain etsiä tietyn kriteerin täyttäviä rivejä, grep on helpoin ratkaisu. Joskus awk on kätevämpi, erityisesti jos halutaan tutkia vain tiettyä kenttää.

    Seuraavat ratkaisut olettavat nimien olevan normaaleja, ts. ei mitään erikoismerkkejä tms.

    1. Etsitään viivaa jonka perässä on ei-tyhjiä ja sitten mahdollisesti tyhjiä mutta ei enää muuta:
      grep -E -- '-[^ ]+ *$' "$@"
      Awkilla voidaan yksinkertaisesti katsoa onko viimeisessä kentässä viiva:
      awk '$NF ~ /-/' "$@"

    2. Etsitään viivaa jonka jäljessä on tyhjä ja sen perässä vielä ei-tyhjä (eli viivallista nimeä seuraa vielä muu nimi):
      grep -E -- '-.* [^ ]' "$@"

    3. Etsitään isoa kirjainta muun kuin tyhjän tai viivan perässä siten että sen perässä on ei-tyhjiä ja sitten mahdollisesti tyhjiä mutta ei enää muuta:
      grep -E ' *[^- ][[:upper:]][^ ]* *$' "$@"
      Awkilla voidaan etsiä isoa kirjainta viimeisestä kentästä jonkin muun merkin perässä:
      awk '$NF ~ /.[[:upper:]]/' "$@"

    4. Etsitään pientä kirjainta rivin alusta tai tyhjän perässä:
      grep -E '(^| )[[:lower:]]' "$@"
      Tuo ei löydä kaksiosaisia nimiä joissa toinen alkaa pienellä kuten Meikäläinen-d'Arnot, jos nekin halutaan voidaan tehdä näin:
      grep -E '(^|[- ])[[:lower:]]' "$@"

    5. Etsitään lueteltuja etuliitteitä joiden perässä on tasan yksi pötkö ei-tyhjiä:
      grep -E '(von|af|van|van der|van den|von der) +[^ ]+ *$' "$@"

      Jos tyydytään etsimään mitä tahansa sukunimen edessä olevaa pienellä kirjaimella alkavaa sanaa päästään helpommalla:

      grep -E ' [[:lower:]]+ [^ ]+ *$' "$@"
      tai
      awk '$(NF-1) ~ /^[[:lower:]]/' "$@"

    6. grep -E '(nen|l[aä]) *$'

    7. Ideana seuraavissa on grep-mallin rakentaminen dynaamisesti muuttamalla rivinvaihdot putkimerkeiksi, ts. tehdään seuraavantapainen malli:
      '((Pekka|Kalle|Jussi|Ville|Armas|Matti) +)+[^ ]+ *$'

      Rivinvaihtojen muuttaminen on helpointa tr:llä, pitää vain muistaa poistaa viimeinen lopusta:

      pat=$(tr '\n' '|' <etunimet.txt)
      grep -E "((${pat%|}) +)+[^ ]+ *$" "$@"
      
      Sedillä se käy hieman hankalammin mutta ilman apumuuttujia joten se voidaan tehdä komentokorvauksella:
      grep -E "(($(
        sed -n -e :a -e N -e '$!ba' -e '$s/\n/|/gp' etunimet.txt
      )) +)+[^ ]+ *$" "$@"
      
      Samoin awkilla:
      grep -E "(($(
        awk 'NR>1 { print "|" } { print }' ORS='' etunimet.txt
      )) +)+[^ ]+ *$" "$@"
      

      Nuo saattavat kaatua komentorivin maksimipituuteen jos nimiä on paljon, joten voi olla parempi käyttää aputiedostoa:

      sed -n -e :a -e N -e '$!ba' -e '$s/\n/|/gp' etunimet.txt >nimimalli
      grep -Ef nimimalli "$@"
      

    1. tr a-zA-Z b-zaB-ZA
      
      tr a-zA-Z za-yZA-Y
      

    2. Vaihdetaan sana-välimerkkejä-sana -kolmikossa sanat keskenään:
      sed 's/\([[:alpha:]]\{1,\}\)\([^[:alpha:]]\{1,\}\)'\
      '\([[:alpha:]]\{1,\}\)/\3\2\1/g' "$@"
      

    3. sed 's/,/,0,/3' "$@"

    4. Seuraava tulostaa rivit jotka eivät täytä annettua ehtoa:
      grep -vEix "[a-f0-9]{12}
      ([a-f0-9][a-f0-9]:){5}[a-f0-9]{2}
      [a-f0-9]{6}-[a-f0-9]{6}" "$@"
      

    5. tr '[:lower:]' '[:upper:]' | tr -d ':-'
      tai
      sed -e 'y/abcdef/ABCDEF/' -e 's/[-:]//g'

    6. #! /bin/sed -f
      /^.\{6\}+/w 18XX
      /^.\{6\}-/w 19XX
      /^.\{6\}A/w 20XX
      

    1. Seuraava olettaa viittauksen mahtuvan yhdelle riville. Idea on yksinkertaisesti lisätä rivinvaihdot jokaisen <:n eteen ja >:n jälkeen jolloin jokainen html-tag jää omalle rivilleen, minkä jälkeen loppu onkin helppoa.
      #! /bin/sh
      sed 's/>/>\
      /g
      s/</\
      </g' "$@" |
      grep -i '<a href'
      

    2. Tässä on varauduttu usempiriviseen viitetekstiin (se usein on monirivinen):
      #! /bin/sed -f
      /<[Aa]  *[Hh][Rr][Ee][Ff]/!d
      s+\(<[Aa]  *[Hh][Rr][Ee][Ff]\)+\
      \1$+
      s+.*\n++
      :check
      /<\/[Aa]>/!{
      N
      bcheck
      }
      s+\(.\)\(<[Aa]\)+\1¤\2+
      s+\(</[Aa]>\)[^¤]*+\1+
      s/\n/ /g
      s/¤/\
      /
      P
      D
      

  2. Sed-komennot voi generoida lennosta sedillä (hakasuluissa on välilyönti ja tabulaattori):
    sed "$(sed 's+\(.\)[ 	]*\(.*\)+s/\1/\\\2/g+' htmlspecials)" "$@"
    
    sed "$(sed 's+\(.\)[ 	]*\(.*\)+s/\2/\1/g+' htmlspecials)" "$@"
    
    tai awkilla:
    sed "$(awk '{print "s/" $1 "/\\" $2 "/"}' htmlspecials)" "$@"
    
    sed "$(awk '{print "s/" $2 "/" $1 "/"}' htmlspecials)" "$@"
    

  3. #! /bin/sh
    
    while [ -s "$file" ] ;do
    
    sed -n '
    /^$/,${
      /^From /,${
        w restfile
        d
      }
    }
    w firstfile' "$file"
    
    if sed '/^$/q' firstfile | grep -q "^Subject:.*kalakukko" ;then
    
       cat firstfile >>$HOME/censored
    
    else
    
      sed '1a\
    X-note: approved by censorship board' firstfile
    
    fi
    
    rm firstfile
    mv restfile "$file"
    
    done
    

    1. Tämä on hyvä esimerkki tehtävästä, johon regexp ei oikein sovellu, vaikka sitä käyttää voidaankin. Seuraavassa ensin sotkuisia ratkaisuja:

      Rakennetaan yksi regexp joka vastaa juuri halutunkokoisia palindromeja, jokaiselle pituudelle oma malli ja ne omille riveilleen:

      #! /bin/sh
      # brute-force palindrome finder
      
      case "${1}" in
        *-*)  BEGIN=${1%-*}; END=${1#*-} ;;
        ?*)   BEGIN=${1}; END=$BEGIN ;;
        *)    BEGIN=2; END=19 ;;
      esac
      
      PAT=
      i=$BEGIN
      while [ $i -le $END ] ;do
      
        case "$i" in
        2) PAT="$PAT
      \([[:alpha:]]\)\1" ;;
        3) PAT="$PAT
      \([[:alpha:]]\)[[:alpha:]]\1" ;;
        4) PAT="$PAT
      \([[:alpha:]]\)\([[:alpha:]]\)\2\1" ;;
        5) PAT="$PAT
      \([[:alpha:]]\)\([[:alpha:]]\)[[:alpha:]]\2\1" ;;
        6) PAT="$PAT
      \([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\3\2\1" ;;
        7) PAT="$PAT
      \([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)[[:alpha:]]\3\2\1" ;;
        8) PAT="$PAT
      \([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\4\3\2\1" ;;
        9) PAT="$PAT
      \([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)[[:alpha:]]"\
      "\4\3\2\1" ;;
        10) PAT="$PAT
      \([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)"\
      "\5\4\3\2\1" ;;
        11) PAT="$PAT
      \([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)"\
      "[[:alpha:]]\5\4\3\2\1" ;;
        12) PAT="$PAT
      \([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)"\
      "\([[:alpha:]]\)\6\5\4\3\2\1" ;;
        13) PAT="$PAT
      \([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)"\
      "\([[:alpha:]]\)[[:alpha:]]\6\5\4\3\2\1" ;;
        14) PAT="$PAT
      \([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)"\
      "\([[:alpha:]]\)\([[:alpha:]]\)\7\6\5\4\3\2\1" ;;
        15) PAT="$PAT
      \([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)"\
      "\([[:alpha:]]\)\([[:alpha:]]\)[[:alpha:]]\7\6\5\4\3\2\1" ;;
        16) PAT="$PAT
      \([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)"\
      "\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\8\7\6\5\4\3\2\1" ;;
        17) PAT="$PAT
      \([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)"\
      "\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)[[:alpha:]]\8\7\6\5\4\3\2\1" ;;
        18) PAT="$PAT
      \([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)"\
      "\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)"\
      "\9\8\7\6\5\4\3\2\1" ;;
        19) PAT="$PAT
      \([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)"\
      "\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)\([[:alpha:]]\)[[:alpha:]]"\
      "\9\8\7\6\5\4\3\2\1" ;;
        esac
        
        i=$((i+1))         
      done
      PAT="${PAT%?}"
      
      tr -cs '[:alpha:]' '[\n*]' |
      grep -x "$PAT"
      

      Seuraavassa on sama idea mutta mallin osat rakennetaan dynaamisesti:

      #! /bin/sh 
      
      case "${1}" in
        *-*)  BEGIN=${1%-*}; END=${1#*-} ;;
        ?*)   BEGIN=${1}; END=$BEGIN ;;
        *)    BEGIN=2; END=19 ;;
      esac
      
      PAT=
      i=$BEGIN
      while [ $i -le $END ] ;do
        mid=$((i/2))
        j=1
        while [ $j -le $mid ] ;do
          PAT="$PAT\\([[:alpha:]]\\)"
          j=$((j+1))
        done
        [ $((i%2)) = 1 ] && PAT="$PAT[[:alpha:]]"
        j=$mid
        while [ $j -ge 1 ] ;do
          PAT="$PAT\\$j"
          j=$((j-1))
        done
        PAT="$PAT
      "
        i=$((i+1))
      done
      
      tr -cs '[:alpha:]' '[\n*]' |
      grep -x "${PAT%?}"
      
      Kumpikaan edellisistä ei muuten toimi Gnu grep 2.4.2:lla (siinä on bugi, mutta kuulemma ''it will be fixed in the next release'').

      Seuraava valitsee ensin yhdellä grepillä halutun mittaiset sanat ja sitten toisella niistä palindromit pituuteen katsomatta (enintään 19-kirjaimiset):

      # /bin/sh
      
      case "${1}" in
        *-*)  BEGIN=${1%-*}; END=${1#*-} ;;
        ?*)   BEGIN=${1}; END=$BEGIN ;;
        *)    BEGIN=2; END=19 ;;
      esac
      
      tr -cs '[:alpha:]' '[\n*]' | 
      grep -x '.\{'$BEGIN','$END'\}' |
      grep -x '\(.\{0,1\}\)\(.\{0,1\}\)\(.\{0,1\}\)\(.\{0,1\}\)'\
      '\(.\{0,1\}\)\(.\{0,1\}\)\(.\{0,1\}\)\(.\{0,1\}\)\(.\)'\
      '.\{0,1\}\9\8\7\6\5\4\3\2\1' 
      

      Sama idea voidaan toteuttaa yhdellä sed-kutsulla:

      # /bin/sh
      
      case "${1}" in
        *-*)  BEGIN=${1%-*}; END=${1#*-} ;;
        ?*)   BEGIN=${1}; END=$BEGIN ;;
        *)    BEGIN=2; END=19 ;;
      esac
      
      tr -cs '[:alpha:]' '[\n*]' |
      sed '/^.\{'$BEGIN','$END'\}$/!d
      /^\(.\{0,1\}\)\(.\{0,1\}\)\(.\{0,1\}\)\(.\{0,1\}\)'\
      '\(.\{0,1\}\)\(.\{0,1\}\)\(.\{0,1\}\)\(.\{0,1\}\)\(.\)'\
      '.\{0,1\}\9\8\7\6\5\4\3\2\1$/!d'
      

      Helpompaa ehkä on testata jokainen sana kirjain kerrallaan, mikä lisäksi toimii pitemmillekin, mutta sh:lla tehtynä tämä on paljon hitaampaa:

      #! /bin/sh
      # palindrome finder
      
      case "${1}" in
        *-*)  BEGIN=${1%-*}; END=${1#*-} ;;
        ?*)   BEGIN=${1}; END=$BEGIN ;;
        *)    BEGIN=2; END=19 ;;
      esac
      
      tr -cs '[:alpha:]' '[\n*]' |
      while read word ;do
        if [ ${#word} -lt $BEGIN ] || [ ${#word} -gt $END ] ;then
          continue
        fi
        tmp=$word
        while [ ${#tmp} -gt 1 ] ;do
          [ ${tmp#${tmp%?}} = ${tmp%${tmp#?}} ] || continue 2
          tmp=${tmp%?}
          tmp=${tmp#?}
        done
        echo $word
      done
      

      Awkia käyttäen sama käy helpommin ja nopeammin:

      #! /bin/sh
      # palindrome finder
      
      case "${1}" in
        *-*)  BEGIN=${1%-*}; END=${1#*-} ;;
        ?*)   BEGIN=${1}; END=$BEGIN ;;
        *)    BEGIN=2; END=19 ;;
      esac
      
      tr -cs '[:alpha:]' '[\n*]' |
      awk -v min=$BEGIN -v max=$END '
      length() >= min && length() <= max { 
        for (i=int(length()/2); i; --i) 
           if (substr($0,i,1) != substr($0,length()-i+1,1)) next
        print
      }'
      

      Awkilla voi hoitaa myös sanojen erottelun ja argumenttien käsittelyn:

      #! /bin/awk -f
      # palindrome finder
      BEGIN{ 
        split(ARGV[1],a,"-")
        if (!a[1]) a[1]=2
        if (!a[2]) a[2]=19
        FS="[^[:alpha:]]+"
        ARGC=1
      }
      { 
        for (n=0; ++n<=NF;) {
          len=length($n)
          if (len<a[1] || len>a[2]) continue
          p=1
          for (i=int(len/2); p && i; --i) 
            p = (p && (substr($n,i,1) == substr($n,len-i+1,1)))
          if (p) print $n
        }
      }
      

      Mainittakoon vielä että yhdessä koneessa noiden suoritusajat megatavun tiedostolle olivat järjestyksessä noin 4s, 4s, 20s, 20s, 60s, 6s ja 8s. Luettavuuden ja tehokkuuden kompromissina sh-tr-awk -yhdistelmä lienee paras.

    2. Lisätään sanan keskelle putkimerkki osoittamaan avaimen alkukohtaa, lajitellaan ja poistetaan putkimerkki:
      #! /bin/sh
      while read pal ;do
        first=$(printf "%s\n" "$pal" | cut -c1-$((${#pal}/2)))
        printf "%s|%s\n" "$first" "${pal#$first}"
      done |
      sort -t'|' -k2 |
      tr -d '|'
      

      Tämäkin kävisi awkilla helpommin:

      #! /bin/sh
      awk '{ l=int(length()/2); print substr($0,1,l)"|"substr($0,l+1) }' |
      sort -t'|' -k2 |
      tr -d '|'
      


File translated from TEX by TTH, version 1.98.
On 17 Feb 2001, 11:03.