Användarverktyg

Webbverktyg


teknik:bash_-_jag_och_mitt_skal

Bash - Jag och mitt skal

Observera att detta är en av mina gamla, gamla artiklar från minst fyra år sedan. Jag ska fokusera mer på exempel och mindre på brödtext i framtida artiklar.

Den här artikeln kommer löpande uppdateras eftersom jag tänkte använda den för att skriva ner roliga och knepiga saker jag lär mig i Bash-skalet.

Så en liten varning är att artikeln inte behandlar en nybörjares intåg i Bash-världen så bra, och språket är självklart anpassat efter mig precis som alla andra artiklar på sidan.

Tips för en nybörjare som undrar saker jag inte skrivit ner är att öppna bash manualen och söka i den med /-tecknet. Att lära sig söka genom manualer i Unix är nog det bästa man kan göra i början. Det fungerar så som sökningen i less eller more, beroende på vilken PAGER ni använder för att visa manualer.

Vad är bash?

I operativsystem som Linux, FreeBSD eller Solaris får man oftast en kärna som hanterar drivrutiner, processorer och minne, sedan får man även ett skal som är en kommandotolk för att interagera med vad kärnan gör. För de flesta är skalet det första de ser i ett operativsystem eftersom det startar så fort man loggar in.

Det finns även grafiska skal men Bash är en kommandotolk för textinmatning. Den låter dig skriva rader med kommandon, samt dela upp kommandon över flera rader, och exekvera dessa genom att starta en ny process eller anropa en intern funktion.

Så skalet Bash har ett antal interna funktioner den kan använda för att göra enkla saker som att gå upp och ner i katalogstrukturen av filsystemet, se om filer och kataloger existerar och är skrivbara, eller upprepa saker med en loop.

En viktig sak att notera är att varje kommando Bash startar som inte är en intern funktion kommer ta längre tid eftersom Bash måste starta en ny barnprocess för kommandot. Bash är i sig självt en process och det mesta vi exekverar i skalet startar en ny underprocess från huvudprocessen.

Varje underprocess följer fork(2) principen, eftersom Bash internt använder sig av samma systemanrop, och det betyder att varje ny process är en exakt kopia av huvudprocessen.

Eftersom vissa operativsystem erbjuder externa versioner av program som Bash har inbyggt så kan det vara bra att känna till builtin(1) manualen som kommer med Bash för en lista över vad Bash har inbyggt.

Öppna och stänga filer

Bash kan stänga och öppna filer för läsning, skrivning, eller bägge, med hjälp av exec.

$ exec 3>&1 4>test.file 1>&4
$ echo 'loltest'
$ exec 1>&3 4>&- 3>&-
$ echo 'lolout'
lolout
$ cat test.file
loltest
$ echo 'lolfail' >&4
-bash: 4: Bad file descriptor
$ echo 'test3' >&3
-bash: 3: Bad file descriptor

Vad som händer här är att exec kan användas för att manipulera ett skals öppna filer. Varje öppen fil har en siffra förknippad med sig och varje process har sina öppna filer som de håller reda på. Varje skal är ju en process och mycket av det som körs i skalet blir underprocesser av den ursprungliga skalprocessen. Så om vi tänker oss att varje siffra i texten ovan är en siffra associerad med skrivning och läsning av en fil.

Nu finns det tre ”filer” som är speciella, de första tre siffrorna faktiskt, 0, 1 och 2. 0 står för stdin, 1 är stdin och 2 är stderr. Data läses av processesn från stdin, data skrivs till stdout och stderr beroende på om det ska läsas av något som hanterar eventuella felmeddelanden till stderr.

0. stdout 1. stdin 2. stderr

Så nu kan vi kanske förstå den första raden bättre, där jag kopierar stdout till den nya filreferensen 3, direkt därefter öppnar jag en ny fil med namn test.file och nummer 4. Till sist på den raden kopierar jag adress 4 till var stdout var tidigare, adress 1.

$ exec 3>&1 4>test.file 1>&4

Detta sättet att tänka på saken tycker jag känns mest naturligt, påminner om pekare i C.

int *tre, *ett;
tre = ett; /* Kopierar alltså adressen från höger till vänster. */
tre = NULL; /* Så exec 3>&- kommer endast skriva över den ena adressen. */

Sedan kör jag echo som bash får att skriva till sin stdout, som nu är kopierad till en temporär plats, 3, och i dess plats har vi adressen till filen test.file som vi precis skapade. Då skrevs datan till filen, en rad bara.

$ echo 'loltest'

Sedan kör jag exec igen för att kopiera tillbaka stdout adressen från 3, till 1 där den hör hemma. Till sist stänger jag filen på adress nummer 4 och tar bort värdet i adress nummer 3 så det inte skräpar. Då kan man se att echo återigen skriver i vår terminal istället för en fil, och filen innehåller det vi skrev när stdout var omriktad. Till sist demonstrerar jag bara att 4 inte längre är öppen och inte går att skriva till.

$ exec 1>&3 4>&- 3>&-

Hela min nya webbsida drivs av Bash och ett C program numer, så roligt har jag med utforskningen av skalet. :)

* Mer info på engelska

Variabler

Kan definieras utan dollartecken, men hänvisas till senare, och expanderas, med hjälp av dollartecken.

$ NY_VARIABEL=hej
$ gammal_variabel="$NY_VARIABEL hej"

Det är oftast väldigt viktigt att citera variabler inom citationstecken, varje situation har egna krav men oftast undviks problem om variabler citeras.

Expansion av variabler

Expansion av en variabel är bokstavligt talat hur skalet hanterar variabler och andra specialtecken, de expanderas in i en annan form som dikteras av variabeln eller strängen med specialtecken man använder. Ett typiskt exempel på expansion är när man skriver ls katalog/star\_\* för exempel. Asterisken expanderas till alla kataloger och filer i katalog som börjar med star\_. Just den typen av expansion specifikt heter file globs i manualen.

När man skriver variabler i Bash kan man utan problem skriva $variabel men det går även att använda det alternativa syntaxet ${variabel}. Till en början kan det verka som ett bra sätt att skilja variabler från annan text, men det finns mycket kraft i de där måsvingarna.

Det kallas Parameter Expansion på engelska och är så kraftfullt att det t.om ersätter behovet för basename programmet som jag ofta använt tidigare.

Detta är ett bra exempel faktiskt, säg att vi har en fil full av rader som är eller påminner om sökvägar.

$ cat filenames.txt
filer/fil1.data
filer/fil2.data

Den mer kända, men även betydligt långsammare, metoden är att använda basename.

$ exec 4<filenames.txt
$ while read -r -u 4 line; do filename=`basename "$line" .data`; echo $filename; done
fil1
fil2
file5 
$ exec 4<&-

Det är önskade resultat, om vi nu ville ha namnet på filerna utan filändelsen, men som sagt är det väldigt långsamt eftersom basename inte är inbyggd i Bash så den anropar den binära filen av programmet från filsystemet och startar en ny underprocess.

Istället gör vi det med expansion av variabeln.

$ while read -r -u 4 line; do filename=${line%.data}; echo ${filename##*/}; done
fil1
fil2
file5 

Komplettera ssh-servernamn automatiskt

Med följande rad i er .bashrc kommer alla värdnamn ni har konfigurerade i .ssh/config att kompletteras automatiskt när TAB-tangenten trycks ner i skalet.

if [[ -f $HOME/.ssh/config ]]; then
        complete -W "$(grep '^Host ' $HOME/.ssh/config | sort -u | sed 's/^Host //')" ssh autossh mosh
fi

Detta fungerar för både autossh och ssh programmen, det innefattar alla värdnamn som börjar med Host värdnamn i .ssh/config, får inte vara mellanrum framför Host på raden.

Vanliga misstag

Sedan jag hittade Gregs Bash Wiki har jag förbättrat mina bashkunskaper tiofaldigt, minst! Här går jag snabbt igenom lite saker folk brukar göra fel.

Testa tomma strängar

if [ "x$string" -eq "x" ]; then
	echo "Tom sträng på fel sätt"
fi

Detta är en kvarleva från gamla Bourne Shell då man inte kunde testa om strängar var tomma, en extremt gammal kvarleva, sluta upp med detta omedelbart alla ni 60-talister!

if [ -z "$string" ]; then
	echo "Tom sträng på rätt sätt"
fi

Använd inte ``

Väldigt vanlig syn även från unga bash-kodare.

myVar=`echo "Fel!"`

Använd istället $() som går bättre att nästla och orsakar inte oväntat beteende.

Använd inte enbart versaler i egna variabelnamn

Bara för att miljövariabler i Unix skrivs med versaler betyder inte att du ska göra det, faktum är att du löper en mycket högre risk att komma i konflikt med ett annat programs miljövariabel om du gör så.

Jag föredrar en form av camel-case.

minVariabel=$(minFunktion "${enAnnanVariabel}" "$PATH")

Citera alla sakerna!

Använd citationstecken på rätt sätt, det är inte alltid man behöver dubbla citationstecken, precis som det inte alltid finns behov för enkla citationstecken. Läs mer här.

Använd read närhelst det är möjligt

Det inbyggda funktionsanropet read har en otrolig kraft i nyare Bash-versioner som ofta förbises. Läs mer här..

teknik/bash_-_jag_och_mitt_skal.txt · Senast uppdaterad: 2013-09-27 17:47 av stemid