Resurs  

UNIX-skript

Behandlar: Enkel introduktion till kommandoskalsprogrammering.


Introduktion

Denna handledning går igenom ett par saker som är viktiga att tänka på vid kommandon i kommandoradsmiljöer (skal, från eng. shell) på UNIX. Översiktligt visas hur kraftfulla enkla kommandon kan bli när man kedjar ihop dem samt hur man skapar skript. Skript är praktiska för att hålla ordning på det man har skrivit och slippa göra jättelånga kommandorader. Dessutom blir det lättare att pröva olika versioner av kommandon, spara varianter och skriva kommentarer.


Att skapa skript

Ett skript i UNIX är en textfil och kan skapas i vilket textediteringsprogram som helst, t.ex. gedit eller emacs. På första raden specificerar man hur filen ska köras som ett program, den vanligaste varianten sh-skript och ser ut så här:

#! /bin/sh

I resten av textfilen kommer rader som börjar med tecknet '#' att tolkas som kommentarer (och alltså inte köras), till skillnad från andra rader som körs som skalkommandon.
För att kunna köra skriptet så berättar man för systemet att det är exekverbart. Det krävs att man först sparar skriptet som en fil, gärna med ett namn som slutar på '.sh' för att ange att det är ett skript. Sedan kan man i ett terminalfönster använda kommandot chmod med argumentet u+x som säger att filens ägare ('u' för user, den som har skapat filen) får (+) köra ('x' för exkevera) det eller de filnamn som följer.

Ex. Om man skriver följande ('$>' symboliserar en kommandopromt):

   $> chmod u+x uppgift1.sh
   $> ./uppgift1.sh
   [Skriptet körs ]
Man behöver bara gör en fil exekverbar en gång.


In- och ut-data för kommandon

De flesta UNIX-kommandon är gjorda så att de om inget annat specificeras läser data från standard in-kanalen (STDIN)och skriver utdata till standard out-kanalen (STDOUT). Detta har flera fördelar när det gäller att kontrollera vad program arbetar med för data. Vissa kommandon kan istället läsa in data från en fil, medan andra alltid läser direkt från STDIN. Därför kan det ibland vara praktiskt att dirigera om data, vilket betyder att man flyttar data in eller ut ur de olika kanalerna.

Indata

Man kan skicka data till STDIN för ett program från en fil genom mindre än-tecknet (vänsterpil '<'). Unix-kommandot tr är ett exempel på ett kommando som inte kan ta ett in-fil-argument utan alltid läser direkt från STDIN.

    tr '[A-Z]ÅÄÖ' '[a-z]åäö'  < indatafil
Om man utelämnar indata, antingen filnamn eller omdirigering, så börjar ett program som förvänta sig indata att läsa från STDIN och fortsätter med det tills det vet att indata är slut. För den som givit kommandot märks det som att ingenting händer alls, vilket då beror på att programmet bara väntar. Detta kan man nyttja till att skriva in data, rad för rad, och testa hur kommandot beter sig för varje rad. När man är nöjd avslutar man genom att trycka CTRL-D.

Utdata

Det som ett program returnerar, utdata, kan man omdirigera till en fil med större än-tecknet (högerpil '>').

     sort < unordered_list.txt > ordered_list.txt
Det är väldigt praktiskt om man vill spara ner sitt resultat för att återanvända det eller redovisa det efter slutförd lab. Om man vill skriva över en tidigare befintlig fil med samma namn (om det t.ex. redan finns en fil med namnet ”ordered_list.txt”) så kan man skriva '>!' i terminalfönstret. Omdirigering i ett skript skriver alltid över eventuella tidigare filer, och där bör man inte använda '>!' då detta tolkas annorlunda.

Kedjning

Det händer ofta att man vill utföra flera kommandon på en text innan man får ett färdigt resultat. I skript kan man relativt enkelt skriva ned alla kommandon man vill utföra på enskilda rader och spara ned resultatet av varje kommando så att man kan återanvända det på nästa rad, men det finns ett ännu enklare sätt:

Så länge man fortsätter arbeta på samma data kan man bara skicka den vidare till nästa kommando, vilket ofta kallas för "pipe'ning" (från eng. pipe), men "kedjning" vore kanske bättre svenska. Det är tecknet '|' som används till detta, och rent praktiskt innebär det att STDUT från föregående kommando kopplas ihop med STDIN på nästa, som genom ett rör (därav pipe).

I nedanstående exempel så hämtas alla rader som börjar med datum, alla stora bokstäver görs om till små, raderna sorteras och skrivs sedan till en fil. Ponera att indata är en rapport där vissa rader som börjar med datum innehåller intressanta fakta, fast de kommer inte i ordning.

  $> /sw/gnu/bin/grep -E '[0-9]{4}-[0-9]{2}-[0-9]{2}' < rapport.txt | tr '[A-Z]ÅÄÖ' '[a-z]åäö' | sort > sorted-facts.txt


Mer hjälp

Mycket av det här finns i IDA:s datorkurs STONE som ni kan repetera. Har ni ingen inloggning av någon anledning finns det gamla och något mindre kursmaterialet här.

Som extern resurs finns Nikolaj Lindbergs "Egrep for the linguist" som innehåller en bra genomgång av några vanliga UNIX-kommandon och hur man kan använda dem (notera att syntaxen kan skilja sig något från den vi använder, men i stort sätt stämmer överrens)



Skydda argument

Kommandon som utförs vid prompten i en skalmiljö tolkas av skalet. Ibland är detta ett hjälpmedel vid skriptprogrammeringen, ibland är det ett hinder. För att skydda sig mot det här kan man skydda teckensekvenser, så att skalet inte tolkar dem utan hanterar dem som ett argument. Detta görs genom att skriva argumenten innanför citationstecken. Enkla citationstecken är det som används för att hindra all tolkning.

   $> grep år 2002 test.txt 
grep: 2002: Det finns ingen fil eller katalog med det namnet
test.txt:Världens befolkning år 2002, 31 Dec, uppgår till ungefär
test.txt:Med nuvarande ökningstakt, kommer siffran att passera 8 miljarder år
test.txt:och går upp till c:a 9,3 miljarder vid halvseklet.
$> grep 'år 2002' test.txt
Världens befolkning år 2002, 31 Dec, uppgår till ungefär
$> grep .*går test.txt
/sw/gnu/bin/grep: No match.
$> grep '.*går' test.txt
Världens befolkning år 2002, 31 Dec, uppgår till ungefär
och går upp till c:a 9,3 miljarder vid halvseklet.
$>

Nästan alla reg.uttryck innehåller tecken som behöver skyddas från skalet för att skickas vidare som de är till kommandot. Om man skriver argument innehållande mellanslag så måste man använda citationstecken för att det ska tolkas som ett enda argument och inte flera separerade av ett mellanslag.


Specialtecknet '!'

I det kommandoskal (tcsh) som används mest på IDA är utropstecknet ett metatecken som kan användas för att komma åt tidigare kommandorader. Det speciella är att kvotning inte hjälper, utan tcsh tolkar utropstecken trots att de står innanför citationstecken. Nedanstående utskrift visar med ett exempel hur fel det kan bli:

   $> ps 
PID TTY TIME CMD
26834 pts/8 0:01 tcsh
$> ls *.sed
noComments.sed spaceToNewline.sed
$> cat noComments.sed
# noComments.sed

# sed -n -f noComments.sed
# Print all lines except comments

/ *#/!p;

$> sed -n '/ *#/!p;' spaceToNewline.sed
sed -n '/ *#/ps;' spaceToNewline.sed
sed: / *#/ps; kan inte tolkas
$> sed -n -f noComments.sed spaceToNewline.sed
s/ */\
/g;

$>
Notera att tcsh tolkar utropstecknet trots att det är inom citationstecken; i det här fallet byts det ut mot tidigare utfört kommando som börjar på 'p'. Det går däremot utmärkt att ha utropstecknet i en egen kommandofil till sed.