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

    1. Ongelmana tässä on lähinnä muuttujassa mahdollisesti olevat monet välilyönnit (jotka saa hoidettua lainausmerkeillä) ja kenoviivat (joita ei echolla saa hoidettua mitenkään).

      printf "'%s\n'" "$x"
      printf '"%s\n"' "$x"
      

      tai näin:

      cat <<EOF
      '$x'
      "$x"
      EOF
      

    2. Tässä echo on yllättäen ihan turvallinen. Vaihtoehtoja on enemmänkin kuin nämä:

      echo \'\"'#'\$\\\'\<\>\|
      echo "'\"#\$\\\`<>|"
      echo \''"#$\`<>|'
      

      Myös seuraava toimii mutta on vaarallinen:

      echo "'\"#$\\\`<>|"
      
      Jos esim. $:n ja #:n järjestys vaihtuu, tuo ei enää toimi (miksei?).

      Ilman echoa tämä kävisi myös esim. näin:

      cat <<\EOF
      '"#$\`<>|
      EOF
      

    3. ERR=$(date $f 2>&1 1>/dev/null)
      

      Huomaa uudelleensuuntausten järjestys, ja että shellin mahdollinen virheilmoitus (esim. jos date ei ole PATHissa tai $f ei ole olemassa) tulostuu kuitenkin, vain date:n virheilmoitus otetaan kiinni.

      Jos date:n varsinainen tulos halutaan päätteelle, voi /dev/nullin korvata /dev/tty:llä.

    1. y=${x%"${x#??}"}; y=${y#?} 
      y=${x#"${x%??}"}; y=${y%?} 
      
      tai esim.
      y=${x#?}; y=${y%"${y#?}"}
      y=${x%?}; y=${y#"${y%?}"}
      
      Lainausmerkit ovat tarpeen jos $x voi sisältää joitakin erikoismerkkejä (esim. '*--*'). (Miksi niitä ei tarvita enempää?)

    2. mydirname() { printf "%s\n" "${1%/*}" ; }
      mybasename() { printf "%s\n" "${1##*/}" ; }
      

      Huom. nuo eivät toimi aivan samoin kuin oikeat dirname ja basename, erityisesti jos argumentti päättyy kauttaviivaan, eivätkä ymmärrä basenamen toista argumenttia. Täydellisemmin (muttei aivan):

      mydirname() {
        case "$1" in 
        /|//) echo / ;;
        *?/?*) set -- "${1%/}"
               printf "%s\n" "${1%/*}" ;;
        /*) echo / ;;
        *) echo . ;;
        esac
      }
      
      mybasename() {
        case "$1" in
        /|//) echo / ;;
        *)    set -- "${1%/}" "${2-}"
              set -- "${1##*/}" "$2"
              printf "%s\n" "${1%$2}" ;;
        esac
      }
      

      Huomaa argumenttien arvon uudelleenasettelu (käyttö lokaaleina muuttujina) funktiossa. Missä tilanteissa nuo vielä käyttäytyvät eri tavoin kuin standardikomennot?

    3. fun() {
         p=${PATH:-/usr/bin}
         ls -- "${p%%:*}"
      }
      

      Huomaa toimivuus myös jos $PATH alkaa kaksoispisteellä (mikä siinä tarkoittaa oletushakemistoa) tai erikoismerkkejä sisältävällä nimellä.

    1. for i in "$x" "$y" "$z" 
      do printf "%s\n" "$i" 
      done | sort
      
      tai
      { printf "%s\n" "$x"
        printf "%s\n" "$y"
        printf "%s\n" "$z" 
      } | sort
      
      tai
      printf "%s\n" "$x" "$y" "$z" | sort
      
      tai
      sort <<EOF
      $x
      $y
      $z
      EOF
      
      Huomaa toiminta jos muuttujissa on useita peräkkäisiä välilyöntejä tai kenoviivoja (erityisesti \n tai \c).

    2. Virhe() { 
        printf "%s: %s\n" "${0##*/}" "${*-tuli virhe}" >&2 
        exit 1
      }
      
      Tuo sallii myös tyhjän virheilmoituksen (jos kutsutaan esim.  Virhe '' - bash 1.x ei toimi tässä oikein), ja vaikka virheilmoitus muodostuisi useasta sanasta (ts. Virhe nyt meni pieleen toimii ilman lainausmerkkejäkin). Skriptin nimestä poistetaan mahdollinen polku selvyyden vuoksi.

    1. Yksinkertaisin ``melkein toimiva'' ratkaisu:
      myls() { 
      ( cd ${1:-.}
        printf "%s " *
        echo
      ) 
      }
      
      Huomaa alishell (sulut), että alkuperäinen oletushakemisto ei vaihdu. Tuo ei toimi oikein jos hakemisto on tyhjä (tulostaa *:n), ei myöskään jos sen nimi alkaa viivalla tai sisältää tyhjiä. Parempi:
      myls() { 
      ( cd -- "${1:-.}"
        for i in * ;do
          [ -e "$i" ] && printf "%s " "$i"
        done && echo
      ) 
      }
      
      Kumpikaan noista ei tulosta pistealkuisia tiedostoja, kuten ei ls:kaan. (Miten ne saisi siihen?)

    2. Testausta varten halutaan yleensä funktio, jonka exit status on nolla jos tulos oli haluttu, muuten yksi (tai jotain muuta, tässä sillä voisi esim. kertoa mikä tai mitkä jäivät löytymättä), joskin virheilmoituksen tai onnistumisviestin tulostaminenkin kelpaisi tässä myös. Tehtävässä ei sanottu mitä pitää tehdä jos annetaan yli kolme argumenttia, joten sen saa päättää itse (dir3-nimiset alla jättävät ylimääräiset huomiotta viimeistä lukuunottamatta, dirn-nimiset testaavat nekin).

      Hakemistoja voi tutkia test-komennolla:

      dir3() {
        [ -d "$1" ] && [ -x "$1" ] &&
        [ -d "$2" ] && [ -x "$2" ] &&
        [ -d "$3" ] && [ -x "$3" ] 
      }
      
      tai kokeilla pääseekö niihin cd:llä (joka tuottaa virheilmoituksen stderriin statusarvon lisäksi):
      dir3() { 
        (cd "$1") && 
        ([ -z "$2" ] || cd "$2") && 
        ([ -z "$3" ] || cd "$3")
      }
      
      tai
      dir3() { 
      # cd / onnistuu aina!
        (cd "$1") && (cd "${2:-/}") && (cd "${3:-/}") 
      }
      
      Seuraavat toimivat useammallakin kuin kolmella hakemistolla:
      dirn() {   
        for dir in "$@" ;do
          (cd "$dir") || return 1
        done
      }
      
      dirn() {
        while [ $# -ne 0 ] ;do
          [ -d "$1" ] && [ -x "$1" ] || return 1
          shift
        done
      }
      

  1. Huom. Tämä on mahdollista vain ympäristömuuttujien kanssa, ellei skriptejä suoriteta nykyshellissä (. ./skripti1 jne). Tehtävä on yllättävän vaikea jos ottaa huomioon kaikki erikoismerkit (erityisesti newline muuttujan sisällä). Huom. muuttujanimessä ei voi olla erikoismerkkejä eikä sellaista mahdollisuutta seuraavissa testata, kuten ei myöskään aputiedostojen luonnin epäonnistumismahdollisuutta.

    #! /usr/bin/sh
    # skripti1
    eval a=\$$1
    printf "%s\n%s\n" $1 "$a" >/tmp/MyTmp
    
    #! /usr/bin/sh
    # skripti2
    { read var; value=$(cat) ; } </tmp/MyTmp
    eval new=\$$var
    printf "\$%s oli '%s', nyt se on '%s'\n" $var "$value" "$new"
    

    Kahdella aputiedostolla saman voi tehdä ehkä selkeämmin:

    #! /usr/bin/sh
    # skripti1
    eval a=\$$1
    printf "%s\n" $1 >/tmp/MyVar
    printf "%s\n" "$a" >/tmp/MyValue
    
    #! /usr/bin/sh
    # skripti2
    read var </tmp/MyVar
    value=$(cat /tmp/MyValue)
    eval new=\$$var
    printf "\$%s oli '%s', nyt se on '%s'\n" $var "$value" "$new"
    

    Jos erikoismerkeistä ei välitetä mitään, seuraavakin toimii (mitkä merkit rikkovat sen?):

    #! /usr/bin/sh
    # skripti1
    eval a=\$$1
    echo "echo $1 oli '$a', nyt se on '\$$1'" >/tmp/MyTmp
    
    #! /usr/bin/sh
    # skripti2
    . /tmp/MyTmp                                                                   

    Seuraava versio toimii jo paremmin (missä tilanteissa se ei toimi?):

    #! /usr/bin/sh
    # skripti1
    eval a=\$$1
    cat >/tmp/MyTmp <<EOF
    read value <<'ThE eNd'
    $a
    ThE eNd
    printf "\$%s oli '%s', nyt se on '%s'\n" '$1' "\$value" "\$$1"
    EOF 
    
    #! /usr/bin/sh
    # skripti2
    . /tmp/MyTmp                                                                    
    

  2. minish2.c kommentoituna (commented in English).


File translated from TEX by TTH, version 1.98.
On 30 Jan 2001, 16:39.