Bash

Slackware.SE Wiki

Version från den 22 maj 2008 kl. 23.39; Jenso (Diskussion | bidrag)
(skillnad) ← Äldre version | Nuvarande version (skillnad) | Nyare version → (skillnad)
Hoppa till: navigering, sök

Denna artikel handlar om arbete med GNU Bourne Again Shell (bash)

Det finns många små och stora detaljer man ofta missar om man inte letar efter dem, och bash är ett bra exempel på något som ser enkelt ut på ytan men kan användas till mycket mer.

Innehåll

[redigera] Tab-komplettering

De flesta som använder sig av bash (mer eller mindre vilket textbaserat shell som helst egentligen) vet säkert om att man kan komplettera halvfärdiga ord (kommandon, filnamn, ...) med TAB-knappen. Har du inte upptäckt detta ännu är det hög tid att du lär dig använda funktionen till din fördel:

Pröva till exempel att i din terminal skriva ett l och trycka tab (Utan blanksteg efter bokstaven)

$ l [tab]


Inget händer. Detta beror på att det finns mer än ett kommando (först på raden står alltid kommandot, som bekant) som börjar med bokstaven l. För att visa alla tänkbara träffar trycker vi tab en gång till

$ l [tab][tab]
Display all 138 possibilities? (y or n)


Uppenbarligen har mitt system 138 kommandon som börjar på "l". För att korta ner listan om man vet mer om kommandot man är ute efter kan man lägga till fler tecken för att komma närmare det faktiska kommandot:

$ l
Display all 138 possibilities? (y or n) [n]
$ ls [tab][tab]
ls          lsb         lskat       lsmod       lsof        lsz
lsattr      lsdev       lskatproc   lsmod.old   lss16toppm  
$ ls


Eftersom det inte finns fullt lika många kommandon som börjar på "ls" visar bash hela listan på skärmen. Om flera träffar finns och alla börjar på samma bokstäver (de har mer gemensamt än det du har skrivit in) kommer ditt kommando att förlängas så att det innefattar hela den gemensamma början:

$ pla [tab]
$ play [tab][tab]
play      playdv    plaympeg  
$ play


Alla träffar som började på "pla" hade det gemensamt att de började på "play", så bash tar för givet att det var det du menade.

När till slut enbart en "träff" finns kommer den att skrivas ut när du trycker tab en gång till.

Tab-komplettering fungerar precis likadant med filer som med kommandon, och du kan på ett enkelt sätt navigera genom katalogstrukturen eftersom du kan komplettera långa katalognamn utan att behöva skriva mer än 2-3 tecken i de flesta fall; med lite övning arbetar du säkert snabbare på detta vis än med en grafisk filhanterare!

[redigera] Redirects

Det finns tre "kanaler" ett program kan använda för att skriva ut data: stdout, stderr och log. log fångas upp av systemet och hamnar i systemloggen i de flesta fall. stdout är där det mesta skrivs, den text som skrivs ut i terminalen under en normal körning gör så via stdout stderr är den "kanal" programmen använder för att skriva ut felmeddelanden

genom att använda operatorn > kan stdout och\eller stderr hänvisas till en fil istället för terminalen:

$ ls > ./filer.txt
$ updatedb 2> ./errors.log


> omdirigerar stdout medan 2> gör samma sak för stderr. operatorn >> lägger till data i slutet av filen, medan > skapar filen om den inte finns eller tömmer den först om den redan finns operatorn < används för att skicka filer till stdin, som är den "kanal" där programmen läser sin indata från användaren.


$ cut -d " " -f 3 < tabell.txt


[redigera] `Fnuttar`

De bakåtriktade apostroferna kan användas för att substituera resultatet från ett givet kommando i din kommandorad; kommandot inom dessa körs först och dess resultat skrivs in på dess plats i kommandoraden

$ echo "Mitt namn är `whoami`"
Mitt namn är dukeman


Kommandot whoami körs först och dess utskrift hamnar i det yttre kommandot; när echo väl körs har kommandoraden alltså ändrats till

$ echo "Mitt namn är dukeman"

[redigera] Pipes

Pipes är ett verktyg du kan använda på kommandoraden för att sammanknyta flera kommandon på samma rad; en "pipe" agerar som en slags koppling för att ta stdout från ett program och skicka det till stdin för ett annat. Pipe-symbolen | sitter på svenska QWERTY-tangentbord till vänster om z, du kommer åt den genom att trycka AltGr + < Genom att sätta en pipe mellan två kommandon på samma rad kan du knyta dem samman. Detta används i mycket stor utsträckning för att "filtrera" text i början av en lång rad kommmandon (till exempel en lista över saker som inträffat från systemlog) genom ett antal små verktyg som steg för steg ändrar texten tills den kommer ut i andra änden i det format du ville ha. Ett exempel på ett sådant filter som används ofta är grep som söker efter rader i filer eller stdin (det som kommer från en pipe alltså) som matchar en söksträng:

$ ls /
bin   burner  etc    home  mnt  proc  sbin  tmp  var
boot  dev     files  lib   opt  root  sys   usr
$ ls / | grep "c"
etc
proc


Utskriften från "ls" skickas genom grep som ombeds ta ut de rader (I detta fall är en rad detsamma som en katalog i /) som innehåller strängen "c".

Med flera pipes kan du filtrera texten i fler steg. Filosofin bakom UNIX är "Varje program gör en sak och gör det bra", det finns förmodligen ett kommando som gör vad du vill ha gjort med din text.

$ ls / | grep s 
files
sbin
sys
usr
$ ls / | grep s | wc -l
4


Kommandot wc räknar ut olika värden från stdin (Word Count), i detta fall med flaggan -l räknar vi antalet rader (Lines). Hela kommandot ovan utför alltså i ordning:

  • Lista kataloger och filer i
  • Välj ut de rader som innehåller strängen "s"
  • Räkna antalet rader
  • Skriv ut resultatet

Sådana här långa kedjor av kommandon kan du skriva för att behandla text av olika anledningar, och de kan i många fall underlätta ditt arbete genom att datorn får sköta filtrering och låta dig koncentrera dig på den information som är viktig.

[redigera] Andra intressanta kommandon

Här är en lista på ofta använda "filter" och användbara kommandon samt några av deras flaggor en "Flagga" eller "Växel" är ett sätt att tala om för varje kommando exakt vad du vill göra. En flagga har vanligen en kort och en lång form;
--help
-h
där den korta formen ofta används på kommandoraden medan den långa bör användas på ställen där man läser kommandot igen senare (om man till exempel skriver ner ett kommando för att använda senare så kan det vara lättare att förstå funktionen i efterhand om flaggorna står på lång form). Vissa kommandon behöver argument, andra har valfria argument, och vissa flaggor behöver egna argument. Exempel:

$ cut -d " " -f 3 tabell.txt

där -d och -f är flaggor, med argumenten " " (en sträng innehållandes enbart ett mellanslag) respektive 3 (välj ut fält 3). tabell.txt är ett argument till kommandot.

Glöm inte att titta på kommandots hjälpsida (--help) där du kanske kan hitta fler funktioner för ett kommando

[redigera] cat

Beskrivning: Inget "filter" egentligen, används för att skriva ut en eller flera filer på stdout
Flaggor:
-n Skriver ut radnummera
- när flera filer specifieras används "-" för att beskriva stdin

[redigera] cut

Beskrivning: Väljer ut, rad för rad, fält ur stdin och skriver ut dem.
Flaggor:
-d talar om avgränsare (Delimiter) mellan fälten, argumentet skall vara exakt ett tecken
-f talar om vilket eller vilka av fälten som skall väljas. Siffra eller mängd på formen "a-b"
-b väljer ut enstaka tecken (Bytes) efter deras plats. Specificeras precis som -f

[redigera] find

Beskrivning: Söker reda på, och skriver ut namn på, filer rekursvt
Flaggor:
Argumenten uttrycks i form av uttryck såsom

$ find /home/ -type f -and -user dukeman

se find --help för mer info om uttrycken och vilka attribut som kan användas

[redigera] grep

Beskrivning: Söker ut rader som innehåller en sträng eller regexp matchande första argumentet
Flaggor:
-i Sök oberoende av gemener och versaler, "APpLE" matchar "apple"
-v Invertera resultat, skriv enbart ut rader som inte matchar

[redigera] gzip

Beskrivning: Komprimerar (standard) eller packar upp (med -d) data i gzip-format (.bz)
Flaggor:
-c Skriver resultatet till stdout för vidare piping eller utskrift i terminalen
-d packa upp (Decompress) indata som kommer i bzip-format

[redigera] less

Beskrivning Hanterar stora mängder indata och låter dig scrolla genom det på skärmen, så du kan läsa stora mängder text i lugn och ro.
Flaggor:
--help visar knappkombinationer för att hantera texten

[redigera] sort

Beskrivning: Sorterar raderna i stigande ordning
Flaggor:
-f sortera oberoende av gemener och versaler; Apple < beatle < CONE
-n sortera efter numeriskt värde 23 < 1234
-r sortera baklänges

[redigera] tac

Beskrivning: Samma funktion som cat, läser filens rader i omvänd ordning
Flaggor:
- läser stdin istället för en fil

[redigera] tr

Beskrivning: Byter ut ett tecken mot ett annat
Flaggor:
-d tar bort alla instanser av ett visst tecken ur indata istället för att ersätta det med ett annat

[redigera] uniq

Beskrivning: Tar bort identiska rader som följer direkt efter varandra; kombinera med sort för att få bort samtliga dubletter
Flaggor:
-d skriv enbart ut rader som förekommit mer än en gång
-i sortera oberoende av gemener och versaler, "APPLE" och "apple" anses vara lika.

[redigera] wc

Beskrivning: Räknar tecken, ord, rader från stdin
Flaggor:
-c räkna antal Bytes
-m räkna antal tecken
-l räkna antalet rader
-w räkna antalet ord (Words)


Detta är bara några av de många "filter" som finns tillgängliga; du kan även titta på man-sidorna för dessa och andra kommandon för att få reda på mer om vad de gör och hur du använder dem. Kom ihåg att alla program som behandlar stdin och använder stdout kan användas med pipes!

Ett litet exempel på vad vi lärt oss så här långt; en rad som räknar hur många unika användare som är inloggade just nu:

 $ who | cut -d " " -f 1 | sort | uniq | wc -l
  • Lista alla aktiva inloggningar
  • Välj ut första fältet (användarnamn)
  • Sortera listan efter namn
  • Ta bort dubletter
  • Räkna antalet kvarvarande namn
  • Skriv ut resultatet

[redigera] Snabbknappar

Upp-pil bläddrar bakåt ett steg i dina tidigare kommandon
Ned-pil bläddrar framåt ett steg i dina tidigare kommandon
ctrl+c avbryter körande kommando eller ignorerar din kommandorad och ger dig en tom
ctrl+d är detsamma som "exit" eller "logout", ditt shell avslutas
ctrl+a hoppar till början av raden
ctrl+e hoppar till slutet av raden
ctrl+k tar bort allt till höger om markören
ctrl+u tar bort allt till vänster om markören
ctrl+7 ångrar senaste ändring på kommandoraden
ctrl+w tar bort ord till vänster om markören
ctrl+r söker bland dina tidigare kommandon
ctrl+s låser terminalens inmatning
ctrl+q låser upp terminalens inmatning


[redigera] Shellscript

För den som någonsin undrat varför man skulle vilja ha ett system som baserar sig på långa, obegripliga textkommandon istället för lätthanterliga grafiska program kommer här en mycket, mycket bra anledning: Allt som kan göras på kommandoraden kan även göras automatiskt i ett script Detta betyder att du kan göra precis _allt_ som dessa kommandon erbjuder, och om du kan hantera dem så kan du "lära" din dator att hantera dem också. Ett enkelt shellscript består av någon eller några instruktioner som utförs i ordning; Det är precis så Slackware hanterar uppstart. Vill man fördjupa sig finns dock mer avancerade funktioner att dra nytta av för att uppnå ett mer utvecklat beteende, och det är precis det denna artikel handlar om.

Låt oss först titta på den grundläggande strukturen hos ett bash-script

#!/bin/bash

Detta är för att systemet skall veta hur det ska tolkas när det körs.

Allt som kan göras på kommandoraden i bash kan även göras i shellscript och tvärs om.

[redigera] Kommentarer

Kommentarer börjar där tecknet # hittas och fortsätter tills raden tar slut. Dessa tolkas inte av bash.

#!/bin/bash
#Detta är en kommentar
ls /     #Här listar vi innehållet i roten

Om du sparar detta i en fil och gör den körbar med chmod +x så kommer den att bete sig som ett nytt kommando; du kan köra ditt script från kommandoraden eller genom något annat program som kör kommandon, till exempel via hotkeys i din fönsterhanterare.

Den första raden talar om att denna körbra fil inte är en kompilerad linuxbinär (ett "riktigt" program), utan att den skall tolkas av ett annat program, i vårt fall /bin/bash. bash läser ditt script under körning och ser till att det du har scriptat också inträffar. Nästkommande rader är själva innehållet: En kommentar först, som börjar vid # och fortsätter tills raden tar slut. kommandot echo skriver ut text på stdout (terminalen där scriptet körs i detta fall) ls / är ett typiskt kommando som kan köras, det gör precis samma sak i ett script som det hade gjort på kommandoraden.

På detta vis kan man "bunta samman" flera kommandon eller uppgifter i ett script och sedan köra alla tillsammans med ett enda kommando. Trevligt, inte sant?

[redigera] Variabler

Variabler tilldelas med ett enkelt "=" OBS! INGA MELLANSLAG FÖRE ELLER EFTER =

#!/bin/bash
SIFFRA=3

Variabler kan läsas på två sätt: antingen bara med ett $ framför namnet eller med namnet inneslutet i ${} Saker skrivs ut på skärmen med kommandot echo

#!/bin/bash
echo $SIFFRA
echo ${SIFFRA}

input från användaren kan sparas i en variabel med hjälp av kommandot read följt av variabelnamnet

#!/bin/bash
echo "Stoppa in data"
read GREJOR
echo $GREJOR

Output från kommandon kan fångas i variabler med hjälp av $()

#!/bin/bash
LOGINS=$(who | cut -d " " -f 1)

[redigera] Villkor - Full kontroll

Men om man vill ha villkor i koden då? Vissa saker kanske du bara vill köra ibland men inte varje gång scriptet körs? Här kommer if-satsen väl till rätta:

#!/bin/bash
MYNAME=$(whoami)
if [ "$MYNAME" = "root" ]; then
	echo "Fy skäms, skaffa dig ett användarkonto genast!
else
	echo "Välkommen, $MYNAME"
fi

Till att börja med hämtar vi in användarnamnet på den nuvarande användaren i en variabel MYNAME genom att använda konstruktionen $() och kommandot [i]whoami[/i] if-satsen kontrollerar sedan om namnet är "root" eller inte. Notera att mellanslagen kring [ och ] är mycket viktiga! koden mellan if och else körs om uttrycket mellan [ ] anses vara sant, i övriga fall körs koden i else-blocket istället. else är frivilligt, en if-sats kan bestå av enbart if...fi om du så vill. För att få flera villkor som strider mot varandra kan nyckelordet elif användas:

#!/bin/bash
MYNAME=$(whoami)
if [ "$MYNAME" = "root" ]; then
	echo "Välkommen ombord, kapten"
elif [ "$MYNAME" = "sclaus" ]; then
	echo "Bjällerklang"
elif [ "$MYNAME" = "kwalker" ]; then
	echo "..."
else
	echo "Okänd användare"
fi


På så vis kan olika kodblock köras beroende på olika villkor.


[redigera] Operatorer för [ ]

Utöver = finns ett flertal operatorer att använda, såsom:

TAL
OPERATOR    BESKRIVNING           EXEMPEL    VÄRDE
-eq         Lika med              5 -eq 3    FALSK
-ne         Icke lika med         5 -ne 3    SANN
-gt         Större än             5 -gt 5    FALSK
-ge         Större eller Lika     5 -ge 5    SANN
-lt         Mindre än             5 -lt 6    SANN
-le         Mindre eller Lika     5 -lt 5    SANN
STRÄNGAR
OPERATOR    BESKRIVNING           EXEMPEL          VÄRDE
=           Lika                  "s" = "q"        FALSK
!=          Icke lika             "s" != "q"       SANN
\<          Alfabetiskt före      "a" \< "b"       SANN
\>          Alfabetiskt efter     "a" \> "b"       FALSK
-z          Tom sträng            -z "kalle"       FALSK
-n          Icke-tom sträng       -n "kalle"       SANN


[redigera] [[ Dubbel haknotation ]]

Mellan [ och ] kommer bash att försöka "justera" all data, variabler substitueras, kommandon körs och ord delas bland annat. Detta kan kringgås genom att använda dubbla hakparenteser

#!/bin/bash
SUBJECT="geografi"
if [ $SUBJECT = "matematik" ]; then	     #Fungerar INTE som väntat
   echo "kommandot \"geografi\" kördes och hade returkoden \"matematik\""
fi 

if [[ $SUBJECT = "geografi" ]]; then        #Fungerar
   echo "Ämnet är geografi."
fi

if [ "$SUBJECT" = "geografi" ]; then       #Fungerar
   echo "Ämnet är fortfarande geografi."
fi

if [[ $SUBJECT = *grafi ]]; then           #Fungerar
   echo "Ämnet har med uppritning att göra"
fi


Notera även att sträng-operatorerna < och > inte kräver escape om de används inom dubbla hakparenteser.

[redigera] Större programkonstruktioner

Case-satser och loopar kan användas för attt uppnå ett mer avancerat beteende:

[redigera] case-satsen

#!/bin/bash
read SIFFRA
case $SIFFRA in
	3)
		echo "En trea"
		;;	#Motsv. break i C, avslutar just detta fall
	4)
		echo "En fyra"
		;;
	*)
		echo "allt annat"
		;;
esac

[redigera] while-loopen

#!/bin/bash
SIFFRA=0
while [ "$SIFFRA" -lt 5 ]; do
	SIFFRA=$(( $SIFFRA + 1)) #Enkel matematik ska vara inom sina egna a parenteser
	echo $SIFFRA
done

[redigera] for-loopen

#!/bin/bash
MAT="Makaroner Korv Ketchup Saltgurka"
for INGREDIENS in $MAT; do
	echo "Jag gillar $INGREDIENS"
done


[redigera] Särskilda variabler

Bash har inbyggt ett antal speciella variabler som kan användas i script:
$0 - $9 är de första argumenten till ditt script, där $0 är kommandot (scriptets filnamn)
${10} Ger argument 10 och uppåt (ändra siffran)
$# Antal argument
$* Hela kommandoraden (Alla argument som en enda sträng)
$? Returvärdet från det senast körda kommandot (vanligen 0 om inget gick fel)
$$ Process-ID (PID) för detta script

Med hjälp av dessa kan du till exempel göra ett script som beter sig mer som ett program, i och med att du kan ta in argument från användaren.

[redigera] Returvärden

Returvärden (även kända som "felkoder") är ett sätt för program att veta hur det gick när deras underprocesser (andra kommandon som körts) skulle göra sitt arbete. Ditt shellscript kanske använder wget för att ladda ner en fil från en HTTP-server, och behöver bete sig annorlunda beroende på om allt gick som det skulle eller om wget misslyckades med att ladda ner filen. Detta rapporterar wget (och alla väldesignade UNIX-program) med hjälp av returkoder. Vedertagen konvention är att 0 betyder "Allt gick bra", medan alla andra tal signalerar något slags fel. Ett enkelt sätt att kontrollera hur det gick är alltså att kontrollera den speciella variabeln $? efter körning för att se om något gick fel. Ditt script kan också returnera felkoder; detta görs tillsammans med exit som avbryter körningen. exit 1 är allt som behöver göras för att sluta köra scriptet och signalera till föräldern att något gick åt pipan. Vill du använda fler felkoder finns heller inget som hindrar det!

[redigera] Funktioner

En "funktion" är ett block av kod som defineras i förväg och sedan kan anropas med ett förutbestämt namn. i bash beter sig funktioner som egna kommandon som bara existerar i det kontext där de definerats (de funktioner du skriver i ditt shellscript existerar bara under körningen och försvinner samtidigt som scriptet avslutas.)

Funktioner defineras i bash-script med nyckelordet function


#!/bin/bash
#Ett meningslöst men väldokumenterat shellscript 

function usage {
  echo "superkass.sh v1.0"
  echo "Argument: Inga"
  echo "Användningsområden: Inga"
  echo "syntax:"
  echo "./superkass.sh"
  exit 0    #felkod 0 betyder att allt är OK
}

if [ -z "$1" ]; then   #Scriptet anropades utan några argument, eftersom det första är en tom sträng
   usage    #vår funktion ovan anropas precis som om det vore ett kommando
else #det fanns ett första argument, så vi klagar lite.
   echo "Detta script accepterar inga argument från användaren."
   exit 1	#felkod != 0 betyder att något inte gick som det skulle
fi 

Det är god praxis att dela upp större projekt i mindre funktioner, eftersom det gör din kod lättare att överblicka i efterhand och underlättar återvinning av kod på flera ställen.

Funktioner kan även ta argument, precis som kommandon. Dessa hanteras internt precis som scriptets egna argument med variablerna $1-$9 och de andra som synes ovan. utdata från funktioner hanteras lättast med echo och $()


#!/bin/basn
#Exempel på funktioner med in- och utdata

function compare {    #returnerar -1 för $1 < $2, 0 för $1 == $2, 1 för $1 > $2
   if [ "$1" -eq "$2" ]; then
      echo "0"
   elif [ "$1" -gt "$2" ]; then
      echo "1"
	else
      echo "-1"
   fi
}

CMP=$(compare 4 5)

echo "4 cmp 5 ger, enligt funktionen, $CMP"

Tack till lema2.0 för fler knappkombinationer, jenso för påminnelse om >> och find, Nille för tips om tac, och TLE för stavningskorrektur

Den här artikeln är hämtad från http://wiki.slackware.se/index.php/Bash
Personliga verktyg