Ubuntu som Server
Ubuntu som Server
Jonas Björk
Buy on Leanpub

Förord

Den här boken är skriven för den svenska Yrkeshögskolan under åren 2006-2024. Den passar lika bra för gymnasieskolan. Kursen jag använt boken till har ofta kallats “Linux” eller “Linux grundkurs” och det är precis vad det är. Under fem till åtta veckor har jag haft eleverna och lärt dem Linux utifrån denna bok. Texten är skriven för att någon som inte varit i kontakt med Linux skall kunna lära sig. Ofta har eleverna varit datorvana och ibland har de haft kunskap om serveradministration i Microsoft Windows. Många elever har haft det kämpigt i början av kurserna eftersom de är ovana med att skriva kommandon på en kommandorad, men efter någon vecka har de börjat vänja sig och många har till och med tyckt att det är ett bättre sätt att använda en dator än mus och ikoner.

Till min stora glädje har ett flertal elever i varje kurs upptäckt det fina med Linux och öppen- och fri programvara. Många har installerat Linux hemma på en gammal PC och fortsatt lära sig. För en del var min kurs en ögonöppnare, vilket gjorde att de bytte inriktning från Windows till Linux. Flera av mina gamla elever jobbar med Linux idag, faktum är att jag själv, efter ett par år, anställt ett par av dem på arbetsplatser jag varit på.

Jag vet att allt i boken inte är perfekt. Det finns andra sätt att göra sakerna på. Jag har försökt att hålla mig till standarder rakt genom hela boken. Att jag valde Ubuntu att använda för kurserna är för att Ubuntu är mycket enkelt att installera och lyckas med. Mitt mål har hela tiden varit att väcka ett intresse för Linux hos eleverna och göra det så enkelt jag kan för att de inte skall tycka Linux är krångligt och omständigt. Jag presenterar därför inga avancerade “smarta” lösningar i den här boken. Däremot kommer en nybörjare ha ett självförtroende att logga in på en linuxserver, felsöka och utföra enklare administration.

An icon of a graduation-cap

Jobbar du inom skola och vill använda boken i din undervisning erbjuder jag klasslicenser. Då ingår också fler övningar som är kopplade till de olika kapitlen i boken. Kontakta mig för mer information.

An icon of a image

Bokens omslag är ett fotografi över området där jag bor. Vattnet på bilden är Öresund och det mörka strecket i horisonten är Danmark.

Har du frågor, tankar, idéer på bokens innehåll eller bara vill höra av dig? Du kontaktar mig enklast via epost jonas.bjork@gmail.com .

An icon of a copyright

Detta verk är skyddat av upphovsrättslagen (Lag 1960:729)! Mångfaldigande, överlåtelse, försäljning, överföring eller annan form av utnyttjande av materialet får inte ske utan tillstånd från upphovsrättsinnehavaren. Den som bryter mot lagen om upphovsrätt kan åtalas av allmän åklagare och dömas till böter eller fängelse i upp till två år samt bli skyldig att erlägga ersättning till upphovsman/rättsinnehavare.

Introduktion till Ubuntu

Vad är Linux?

Allting började med ett meddelande till nyhetsgruppen comp.os.minix på USENET den 25 augusti 1991. Linus Torvalds som då var 21 år gammal och studerade på Helsingfors tekniska universitet skrev så här:

Hello everybody out there using minix -

I’m doing a (free) operating system (just a hobby, won’t be big and professional like gnu) for 386(486) AT clones. This has been brewing since april, and is starting to get ready. I’d like any feedback on things people like/dislike in minix, as my OS resembles it somewhat (same physical layout of the file-system (due to practical reasons) among other things).

I’ve currently ported bash(1.08) and gcc(1.40), and things seem to work. This implies that I’ll get something practical within a few months, and I’d like to know what features most people would want.

Any suggestions are welcome, but I won’t promise I’ll implement them :-)

Linus (torvalds@kruuna.helsinki.fi)

PS. Yes - it’s free of any minix code, and it has a multi-threaded fs. It is NOT portable (uses 386 task switching etc), and it probably never will support anything other than AT-harddisks, as that’s all I have :-(.

Från början var Linux licensierad under egen licens, men i december 1992 publicerades version 0.99 av Linux under GNU-projektets General Public License (GPL). 1994 släpptes version 1.0 av Linux, då Linus tyckte att Linux var färdigt för en första utgåva.

När detta skrivs (hösten 2024) är Linux uppe i version 6.10.6 och är översatt till ett flertal processorarkitekturer. Linux finns överallt: i tvättmaskiner, i digital signage (digitala reklamskyltar), i robotar, i bilar, i mobiltelefoner (Android bygger på Linux), i flygplan, servrar, skrivbord, surfplattor, .. Ja, i princip överallt där man behöver ett operativsystem för att köra programkod.

Linux är en operativsystemskärna, det vill säga den programkod som låter program att kommunicera med hårdvaran. Linux i sig är inte ett komplett system, vi behöver program som kan användas ovanpå Linux. De flesta av de grundläggande programmen kommer ifrån GNU-projektet. GNU (GNU is Not Unix) skapades av Richard Stallman år 1983. Stallman ville bygga ett helt fritt operativsystem (inte fritt som i gratis, utan fritt som i frihet). Operativsystemskärnan i GNU heter HURD och skulle kunna ha blivit vad Linux är idag, men lyckades aldrig få tillräckligt många utvecklare för att få upp farten på utvecklingen. HURD är än idag ganska enkel och saknar en hel del hårdvarustöd, den senaste versionen av Hurd är version 0.9 från december 2016. Stallman skapade också den licens, GNU General Public License, som används av Linux och tusentals andra mjukvaruprojekt runt om i världen. GPL licensen ger användaren fyra friheter:

Frihet 0
Friheten att använda programvaran i valfritt syfte.
Frihet 1
Friheten att undersöka programmet för att förstå hur det fungerar och använda dessa kunskaper i egna syften.
Frihet 2
Friheten att fritt få dela med sig av kopior för att hjälpa andra.
Frihet 3
Friheten att förbättra programmet, anpassa det till egna krav och dela med sig av förbättringarna så att andra kan dra nytta av ändringarna.

Under 1990-talet var det här riktigt stort. Det är fortfarande stort, men då var det större. De flesta mjukvaruföretag använde sig av en licensmodell som gick ut på att du betalar en summa pengar och får rätten att använda programvaran. Saknar du en funktion i programvaran får du snällt be utvecklaren att lägga till den funktionen, och hoppas på att funktionen kommer in i nästa version.

Med programvara som är öppen och fri (open source) har du tillgång till källkoden själv och kan bygga vidare på den, lägga in dina funktioner som du saknar och sprida dina förändringar till andra. Utan att bryta mot något avtal. Avtalet med utvecklaren är att du äger rätten att göra detta. Så, har fri programvara ingen upphovsrätt? Nej, upphovsrätten finns alltid där. Skriver du en programvara och släpper den med en fri licens (till exempel GPL) äger du fortfarande upphovsrätten till programvaran. Den kan du inte avsäga dig från, men du ger andra rätten att använda ditt program, ändra ditt program och kopiera det till sina vänner.

Microsoft och andra stora mjukvaruföretag gillade inte alls licensmodellen. Många användare fick också för sig att mjukvara som är öppen och fri är osäkrare och man får vad man betalar för. Verkligheten hann i fatt dem och idag jobbar så gott som alla större företag med öppen programvara, till och med Microsoft släpper programvara med öppen källkod idag. Öppen och fri programvara är här för att stanna.

  • Har du en mobiltelefon eller surfplatta med Android som operativsystem? Den kör linuxkärnan och Android i sig är öppen källkod. Källkoden till Android finns på source.android.com.

  • Har du en iPhone, iPad eller Mac? Apple använde BSD-projektet för att skapa OSX och senare iOS. Kärnan för de operativsystemen heter Darwin och fanns tidigare att ladda ner från developer.apple.com.

  • Smart-TV från Philips, LG, Samsung, …? I princip alla av dem använder Linux för smart-tv-funktionerna.

  • Bredbandsroutrar från D-Link, Netgear, ASUS, Unifi med flera. Linux driver dem också. När tillverkarna slutar uppgradera systemet i routern kan vi ofta ladda ner OpenWrt och uppgradera vår router för att få senare versioner och säkerhetsuppdateringar.

För att kunna köra Linux behöver vi linuxkärnan och programvara som körs på kärnan. Detta kan vi lösa själva genom att kompilera allt från källkod. När vi kompilerar källkod tar vi textfiler som innehåll programkoden skriven i något programmeringsspråk och gör om den till binärkod (ettor och nollor) som datorn kan tolka och förstå. Att kompilera ett operativsystem är en tidskrävande process och därför skapades något som kallas linuxdistributioner. En linuxdistribution paketerar den färdigkompilerade linuxkärnan med kompilerad programvara och erbjuder ett enkelt installationsgränssnitt för att installera Linux på datorn.

Det finns hundratals olika distributioner och det kan vara svårt att veta vilken vi skall använda. Till att börja med finns det två som är mer kommersiella än andra: Red Hat Enterprise Linux (RHEL) och SUSE Enterprise Linux (SLE). Dessa två erbjuder support och kostar runt 5000 kronor per år och server att köra. De är välanvända hos större företag som vill ha stabilitet och support tillgänglig. Även om Linux och programvaran är öppen och fri så får vi alltså ta betalt för den. Det Red Hat och SUSE tar betalt för är arbetet med att uppdatera och paketera Linux, ja och supporten så klart.

Den andra grenen av linuxdistributioner är de som kallas community. Distributioner som går här under drivs av communityn (vi som använder dem). En del av dem erbjuder support mot betalning, men många hänvisar till forum på internet för support. Den absolut vanligaste linuxdistributionen i Sverige är Ubuntu. Ubuntu bygger i sin tur på distributionen Debian GNU/Linux, en av få som använder GNU i sitt namn. Från Ubuntu har det också skapats andra linuxdistributioner som Linux Mint, Kubuntu, Lubuntu med flera. Alla är inriktade på något som skiljer sig från ursprunget. Linux Mint är till exempel skapad för att vara så enkel som möjligt att komma igång med. Red Hat har en communitydistribution som heter CentOS och SUSE har openSUSE. En del användare gillar känslan av att ha kontroll över sitt system och väljer distributioner som till exempel Gentoo Linux eller Arch Linux, där de kan detaljstyra hur systemet skall byggas.

Skall vi använda Linux som skrivbordssystem, det vill säga Desktop, behöver vi en fönstermiljö. I Linux finns det ett flertal sådana också, de vanligaste och största är KDE och GNOME. Det är skrivbordsmiljöerna som gör att vi får en grafisk miljö med fönster och ikoner, utan den använder vi Linux i textläge - normalt en svart bakgrund med grå text.

An icon of a tv

Det har gjorts en dokumentär om Linux som heter The Code, den finns att se på till exempel YouTube - sök efter the code linux documentary.

Installera Ubuntu

Den första versionen av Ubuntu släpptes den 20 oktober 2004 och hette Warty Warthog. Version 4.10 var början på något helt nytt. Mark Shuttleworth från Sydafrika hade gjort sig en förmögenhet på att sälja Thawte som på den tiden var en av de största certifikatsutgivarna, de certifikat som används i till exempel webbläsare för att kryptera kommunikationen mellan din webbläsare och webbservern. Mark skrev den första buggrapporten den 20:e augusti 2004: Microsoft has a majority market share och ville skapa ett fritt operativsystem som alla kan använda. Det är Marks företag Canonical som ligger bakom Ubuntu. År 2013 stängde Mark buggen, med hänvisning till att Linux kommit långt sedan 2004, launchpad.net.

Ubuntu kommer i en ny version var sjätte månad, ofta i april och oktober. Varje version har ett projektnamn som kommer från ett djur och varje version numreras med aktuellt år och månad. Versionen som släpptes i april 2024 blev version 24.04 (24 efter årtalet 2024 och 04 efter månad 04 - april). Varannat år blir aprilversionen också en Long Term Support version (LTS). Det innebär att versionen kommer få uppdateringar under en längre tid - åtta år. De versioner som inte är LTS får uppdateringar under nio månader.

Version Namn Release End of Life
24.04 Noble Numbat april 2024 juni 2029
22.04 Jammy Jellyfish april 2022 juni 2027
20.04 Focal Fossa april 2020 april 2025

För system som skall användas i produktion är det lämpligt att använda Long Term Support (LTS) versionerna av Ubuntu, eftersom de kommer få uppdateringar under en längre tid. Den senaste Long Term Support (LTS) versionen av Ubuntu är 24.04 (Noble Numbat). Den version som inte är LTS innehåller ofta de senaste versionerna av olika programvaror och lämpar sig till att till exempel använda för vår arbetsdator.

Ubuntu kommer i flera olika utgåvor: Ubuntu Desktop, Ubuntu Server, Ubuntu for IoT och Ubuntu Cloud. De har samma grund och skillnaden är vad som installeras med dem. Vi kan till exempel installera Ubuntu Server och sedan installera de paket som behövs för att få igång Ubuntu Desktop på systemet. I den här boken kommer vi kommer använda Ubuntu Server som bas.

Systemkrav för att köra Ubuntu Server

För att kunna köra Ubuntu Server behöver vi ha en 64-bitars processor med lägsta klockfrekvensen 1GHz: Intel/AMD (amd64), ARM (arm64), POWER8/POWER9 (ppc64el) eller IBM Z/LinuxONE (s390x). Det vanligaste är att vi har en processor från Intel eller AMD i vår dator/server. Denna processorarkitektur kallas amd64. Vi behöver också ha 1GiB RAM-minne och 5 GiB disklagring.

  • 64-bitars processor med minst 1GHz från Intel/AMD, ARM, POWER8/POWER9 eller IBM Z/LinuxONE.
  • Minst 1GiB RAM-minne, gärna 3 GiB eller mer
  • Minst 5 GiB disklagring, gärna 25 GiB eller mer

Som vanligt gäller att mer RAM-minne och kraftfullare processor ger bättre prestanda. Likaså ger snabbare hårddiskar som SSD och NVMe bättre prestanda.

Ladda ner Ubuntu Server

Vi laddar ner installationen av Ubuntu Server från https://ubuntu.com/download/server .

När vi skall ladda ner installationen ser vi till att det är version 24.04 och vi väljer fliken Manual Installation. Vi klickar på den gröna knappen Download 24.04 LTS för att påbörja nedladdningen.

Nedladdningen startar automatiskt i vår webbläsare och vi får möjligheten att prenumerera på Ubuntus nyhetsbrev och ladda ned ett dokument med vanliga kommandon (Ubuntu CLI cheatsheet).

Installera Ubuntu Server

Om vi har möjlighet att ge vår server mer resurser är det bra, ju mer resurser - desto bättre prestanda kommer vi få ut. För att det inte skall bli allt för långsamt när vi jobbar med servern bör vi ha minst:

  • 4 GiB RAM (4096 MiB)
  • 2 CPU (cores)
  • 30 GiB DISK

Installationen steg för steg:

  • Ubuntu Server, tryck ENTER

  • Välj språk: ‘English’

  • Keyboard configuration. Layout: ‘Swedish’ och Variant: ‘Swedish’ . Hoppa ner till ‘Done’ och tryck ‘ENTER’.

  • Network connections. Här trycker vi ‘Done’ och går vidare.

  • Configure proxy Om vi har en proxy för att nå internet i vårt nätverk behöver vi ange den här. Om vi har en sådan vet vi om det. Oftast är det bara att välja ‘Done’ och gå vidare här.

  • Configure Ubuntu archive mirror Här anger vi vilken adress det är till paketförrådet vi vill använda. Ubuntu hittar oftast rätt själv, http://se.archive.ubuntu.com/ubuntu . Tryck ‘Done’ för att gå vidare.

  • Guided storage configuration Här väljer vi ‘Use an entire disk’ och ‘Set up this disk as an LVM group’. Vi väljer ‘Done’ för att komma vidare.

  • Storage configuration Här får vi se en sammanfattning på hur vår disk kommer konfigureras. Vi väljer ‘Done’ för att komma vidare.

  • Confirm destructive action Här kommer en dialogruta upp som frågar om vi är riktigt säkra på att vi vill göra detta med vår disk. Om vi skapar en virtuell maskin kommer inget raderas från vår dator, men om vi installerar Ubuntu direkt på en dator - utan virtualisering kan detta vara farligt. Om vi installerar Ubuntu direkt på vår dator (alltså inte i en virtuell maskin) måste vi se till att vi har gjort en säkerhetskopia på hårddisken innan vi går vidare! I sämsta fall kommer allt på disken att raderas. Är vi säkra på att gå vidare väljer vi ‘Continue’, annars väljer vi ‘No’.

  • Profile setup Här skall vi skapa ett användarkonto och välja ett namn på vårt ubuntusystem:

    Notera att vi bör undvika å,ä och ö här. Inte heller mellanslag och andra specialtecken är bra att använda - förutom i lösenordet så klart.

    • Your name Här skriver vi in vårt namn, till exempel: ‘jonas bjork’
    • Your server’s name Här väljer vi ett namn för vårt system, till exempel: ‘ubuntuserver’
    • Pick a username Här väljer vi ett användarnamn, till exempel: ‘jonas’ . Det är det här namnet som vi kommer logga in på Ubuntu med.
  • Choose a password Här väljer vi ett lösenord. Vi ser till att det inte är alldeles för enkelt.

  • Confirm your password Här skriver vi samma lösenord igen. Sedan väljer vi ‘Done’ för att komma vidare.

  • SSH Setup SSH är en tjänst som gör att vi kan ansluta till vår ubuntuserver över nätverk. Denna vill vi installera, så vi markerar ‘Install OpenSSH server’ genom att trycka på ‘Mellanslag’ när vi står i rutan. Sedan går vi ner till ‘Done’ för att gå vidare med installationen.

  • Featured Server Snaps Här väljer vi ingenting, utan bara hoppar ner till ‘Done’ för att komma vidare.

  • Install complete! Nu installeras Ubuntu på vår dator. Vi väntar ett tag. När installationen är klar kan vi välja ‘Reboot’ för att starta om datorn.

  • Vi avmonterar cd-skivan vi installerat från och trycker ‘Enter’. Om vi använt ett USB-minne för att installera Ubuntu Server drar vi ut det från datorn och trycker sedan ‘Enter’ för att starta om datorn.

  • När datorn startat om kommer vi mötas av en svart skärm med grå text. Här finns en rad som säger: ubuntuserver login: (ubuntuserver är det datornamn vi valde i punkt 10.2 under installationen). Här skriver vi det användarnamn vi valde (under 10.3), till exempel ‘jonas’ och trycker sedan på ‘Enter’.

  • Nu kommer en ny rad fram med texten Password: . Här skriver vi in det lösenord vi valde under installationen. Notera att Ubuntu inte kommer visa något alls när vi skriver in lösenordet. Skriv lösenordet och tryck på ‘Enter’. Är det rätt lösenord kommer vi loggas in på systemet och mötas av lite text och en kommandopromt jonas@ubuntuserver:~$ . Det är här vi skriver våra kommandon i Linux.

Uppdatera Ubuntu Server

An icon indicating this blurb contains information

Notera att vi visar kommandoprompten som $ här nedan, tecknet $ i början av raderna skall inte skrivas in!

Det är alltid bra att ha ett uppdaterat system, så vi loggar in på vår ubuntuserver och skriver följande kommando:

$ sudo apt update
[sudo] password for jonas:

Här kommer vi få frågan om ett lösenord, [sudo] password for jonas: (jonas är det användarnamn vi loggat in med i det här fallet). Vi skriver in samma lösenord som vi använder för att logga in på Ubuntu med vårt användarkonto. Kommandot apt update går ut på internet och hämtar listan över de senast tillgängliga versionerna av programpaket. Denna kommer användas för att se vilka paket som kan uppdateras på vårt system. För att utföra uppdateringen kan vi använda kommandot apt upgrade eller apt dist-upgrade. Skillnaden på dem är att dist-upgrade också uppgraderar linuxkärnan och inte bara systemkomponenterna. Ibland vill vi inte uppgradera linuxkärnan, så det är bra att det finns möjlighet att välja bort uppgradering av den (med upgrade).

När kommandoprompten kommer tillbaka skriver vi följande kommando för att uppdatera vårt system:

$ sudo apt dist-upgrade -y

sudo är ett kommando som ger oss tillfällig administratörsbehörighet i Ubuntu, det behöver vi eftersom vi skall uppgradera den installerade programvaran. apt är kommandot för att hantera programpaket. dist-upgrade betyder att vi vill uppgradera alla installerade paket och slutligen -y betyder att vi svarar ‘ja (yes)’ på alla frågor. Vill vi se vad Ubuntu kommer installera och välja om vi skall göra uppgraderingen eller inte så tar vi bort -y . Då får vi svara själva på de frågor som kommer.

När uppdateringen är klar vill vi starta om systemet, det gör vi med kommandot reboot:

$ sudo reboot

Hitta hjälp i Ubuntu

I Linux finns hjälpen alltid nära, nästan alla kommandon vi använder har en manualsida skriven för sig. I den hittar vi information om vad kommandot gör och vilka parametrar vi kan använda tillsammans med kommandot. Vi hittar också information om vem som har skrivit kommandot och hur vi kan skicka buggrapporter om vi upptäcker fel.

För att läsa en manualsida använder vi kommandot man. Vi tittar på manualsidan för kommandot man:

$ man man
An icon indicating this blurb contains information

Om du inte har kommandot man tillgängligt behöver du installera paketet man-db. Skriv följande kommando för att installera:

$ sudo apt install man-db -y

Manualsidorna är uppdelade i olika sektioner där manualsidor som handlar om samma område samlas.

| 1 | Executable programs or shell commands | | 2 | System calls (functions provided by the kernel) | | 3 | Library calls (functions within program libraries) | | 4 | Special files (usually found in /dev) | | 5 | File formats and conventions, e.g. /etc/passwd | | 6 | Games | | 7 | Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7) | | 8 | System administration commands (usually only for root) | | 9 | Kernel routines [Non standard] |

Det kan finnas fler man-sidor med samma namn i systemet. Till exempel printf som dels kan användas för utmatning i Bash och dels som en funktion i programmeringsspråket C.

Här är vi på kommandoraden och skriver printf:

$ printf "Du är: %s\n" ${USER}
Du är: jonas

Här är källkod skriven i programspråket C som också använder printf, fast en annan printf. Här är det en funktion som finns i standardbiblioteket för programspråket C.

Figur 1. Filen hello.c
#include <stdio.h>

int main(void) {
    char* namn = "Jonas";
    printf("Du är: %s\n", namn);
    return 0;
}

Skulle vi skriva in ovanstående program skrivet i programspråket C och vill kompilera (göra det körbart) det till ett program behöver vi installera ett paket som heter gcc. Det paketet innehåller en kompilator för programspråket C:

$ sudo apt install gcc -y

Nu kan vi kompilera programmet:

$ gcc -o hello hello.c

-o hello anger vad vi vill att filen skall heta, och hello.c anger vilken fil vi vill läsa in. Sedan kör vi det genom att skriva programmets namn:

$ ./hello
Du är Jonas

Vi använder ./ innan programmets namn för att berätta för Bash att programmet finns i den här katalogen, där vi befinner oss. ./hello betyder alltså: kör programmet hello som finns i katalogen där jag befinner mig just nu.

För att hitta rätt man-sida för den printf vi vill läsa om kan vi ange vilken sektion vi vill använda:

$ man 1 printf
$ man 3 printf

Vi kan också skriva sektionen efter namnet, som i printf.1 och printf.3.

$ man printf.1
$ man printf.3

Hitta kommandon

När vi inte vet vilket kommando vi skall använda kan vi söka efter ett lämpligt kommando med man -k:

$ man -k sftp
sftp (1)             - OpenSSH secure file transfer
sftp-server (8)      - OpenSSH SFTP server subsystem

Vi kan också använda kommandot apropos för att söka efter kommandon:

$ apropos sftp
sftp (1)             - OpenSSH secure file transfer
sftp-server (8)      - OpenSSH SFTP server subsystem

Vill vi veta vad ett kommando används till kan vi använda kommandot man -f:

$ man -f sftp
sftp (1)             - OpenSSH secure file transfer

Vi kan också använda kommandot whatis istället för man -f:

$ whatis sftp
sftp (1)             - OpenSSH secure file transfer

De flesta kommandon kan också ge hjälp själva, genom att vi använder argumentet -h, eller ibland --help:

$ sftp -h
usage: sftp [-46AaCfNpqrv] [-B buffer_size] [-b batchfile] [-c cipher]
          [-D sftp_server_path] [-F ssh_config] [-i identity_file]
          [-J destination] [-l limit] [-o ssh_option] [-P port]
          [-R num_requests] [-S program] [-s subsystem | sftp_server]
          destination

Manualsidan för ett kommando visar också ofta bra hjälp:

$ man sftp
An icon indicating this blurb contains information

För att komma tillbaka från manualsidan till kommandoprompten trycker vi på bokstaven q på tangentbordet.

Användare och grupper

I Ubuntu finns användarkontona i systemet i två filer, dels i filen /etc/passwd som innehåller användarkontot och dels i filen /etc/shadow där lösenordet för användarkontot lagras.

Skapa användare

Med kommandot useradd skapar vi användare i systemet. För att skapa en användare med användarnamnet jonas skriver vi:

$ sudo useradd -m jonas

När vi skapar användaren jonas skapas en post i filen /etc/passwd som ser ut så här:

jonas:x:1001:1001::/home/jonas:/bin/bash

I filen /etc/shadow skapas följande:

jonas:!!:17070:0:99999:7:::

I filen /etc/group skapade en grupp för användaren jonas:

jonas:x:1001:

Och slutligen skapade en hemkatalog som heter /home/jonas/, i den katalogen skapades några filer som kommer ifrån katalogen /etc/skel/.

Om vi vill ange var hemkatalogen för användaren skall skapas använder vi flaggan --home-dir KATALOG, vill vi ange vilken kommandotolk användaren skall använda anger vi det med flaggan --shell KOMMANDOTOLK. För att skapa användaren lisa med hemkatalogen /home/lotta/ och kommandotolken /bin/bash skriver vi:

$ sudo useradd -d /home/lotta/ -m -s /bin/bash lisa
-d /home/lotta/
anger vilken hemkatalog användaren skall ha.
-m
anger att vi vill skapa hemkatalogen till användaren.
-s /bin/bash
anger att användaren skall få kommandotolken bash.

Vi har inte skapat något lösenord för användarna vi skapat, så de kan inte logga in än. För att sätta lösenord för en användare använder vi kommandot passwd:

$ sudo passwd jonas
Changing password for user jonas.
New password:
Retype new password:
passwd: all authentication tokens updated successfully.
An icon indicating this blurb contains information

När vi skriver in lösenordet syns inget på skärmen och vi skriver in lösenordet två gånger (vid New password och Retype new password).

Användarna kan själva byta sitt lösenord när de loggat in genom att använda kommandot passwd och sätta det lösenord de vill ha. Användaren root kan sätta lösenord för alla användare genom att använda kommandot sudo passwd ANVÄNDARNAMN.

An icon indicating this blurb contains information

Det finns ett kommando som heter adduser också. Det kan verka enklare att använda då vi får svara på frågor om det nya kontot. Kommandot adduser finns inte i alla linuxdistributioner, medan useradd gör det. Kommandot useradd är också lämpligare att använda i bashskript. Det är ingen skillnad på det användarkonto som skapas av kommandot adduser och kommandot useradd.

Ta bort användare

An icon indicating this blurb contains a warning

När vi tar bort en användare från systemet kan vi inte återskapa användaren eller dennes filer igen, om vi inte har backup. Så var noga med vilken användare du väljer att ta bort.

För att ta bort en användare från systemet använder vi kommandot userdel. För att ta bort användaren jonas (användarnamnet måste naturligtvis existera i systemet för att kunna tas bort) skriver vi:

$ sudo userdel jonas

När vi tar bort en användare är det bara användarkontot som raderas, användarens hemkatalog finns kvar i filsystemet. För att ta bort en användare och radera hennes hemkatalog samtidigt lägger vi till flaggan --remove till kommandot:

$ sudo userdel --remove jonas
An icon indicating this blurb contains information

Det kan vara en bra idé att göra en säkerhetskopia på våra användares hemkataloger innan vi raderar dem. Kommandot tar zcf /home/användarnamn.tar.gz /home/användarnamn skapar ett komprimerat arkiv med allt innehåll i användarens hemkatalog.

Om vi glömde bort flaggan --remove när vi tog bort användarkontot finns hemkatalogen för användaren kvar i filsystemet. Vi kan använda kommandot rm för att radera katalogen:

$ sudo rm -rf /home/jonas
An icon indicating this blurb contains a warning

Kommandot rm raderar filer för alltid. Det finns ingen papperskorg att hämta upp filer vi har raderat av misstag från. En borttagen fil är borat. Kommandot rm med flaggan -rf skall användas med stor försiktighet då det raderar en katalog och alla filer och kataloger under den katalogen.

Filen /etc/passwd

Det är i filen /etc/passwd systemet lagrar informationen om användarkonton. Varje rad i filen innehåller ett användarkonto och det finns sju fält på varje rad:

ubuntu:x:1000:1000:ubuntu user,,,:/home/ubuntu:/bin/bash

Nedan följer en beskrivning av fälten ovanför, värdet inom parantes är motsvarande vad som står i raden (exemplet som börjar med ubuntu) ovanför.

användarnamn
Det användarnamn som användaren loggar in med. (ubuntu)
lösenord
Här finns normalt ett x som betyder att lösenordet finns i filen /etc/shadow. (x)
användar-id
Användar-id är det numeriska id som användarkontot har. I Ubuntu är alla användar-idn under 1000 systemkonton och alla användar-idn från 1000 och uppåt är vanliga användarkonton. (1000)
grupp-id
Fältet anger vilken primär grupp användaren tillhör. Varje användare i systemet måste tillhöra en primär grupp, vill vi att de skall ingå i flera grupper är det filen /etc/group som hanterar dem. (1000)
kommentarsfält
Kommentarsfältet kan användas till vad som helst. Ubuntu lagrar informationen om fullständigt namn här. Undvik att använda kommatecken (,) i kommentarsfältet, eftersom kommatecken är fältseparatorn i filen. (ubuntu user)
hemkatalog
Fältet för hemkatalog anger vilken katalog som är användarens hemkatalog. När en användare loggar in i systemet är det hemkatalogen som är startpunken. Normalt finns alla användares hemkataloger under katalogen /home/ (/home/ubuntu)
kommandotolk
Fältet anger vilken kommandotolk en användare skall få efter att hon loggat in. (/bin/bash)
An icon indicating this blurb contains information

Alla användare i systemet måste tillhöra en primär grupp. När användaren loggar in sätts den primära gruppen och alla filer som användaren skapar kommer automatiskt kopplas till den gruppen. Som standard kommer Ubuntu skapa en primär grupp som har samma namn som användarkontot. Skapar vi användaren kalle kommer det också skapas en grupp som heter kalle som användaren kalle kommer få som primär grupp, om vi inte väljer något annat.

Alla användarkonton i Ubuntu kan också tillhöra en, flera eller ingen sekundär grupp. De sekundära grupperna används för att ge rättigheter för användaren att komma åt filer, program och kataloger.

Byta användarnamn

Vi kan byta användarnamnet för en användare med kommandot usermod. För att byta användarnamn på jonas till lasse skriver vi:

$ sudo usermod -l lasse jonas

Byta primär grupp för en användare

Med kommandot usermod kan vi byta användarens primära grupp. För att byta primär grupp för användaren jonas till gruppen video skriver vi:

$ sudo usermod -g video jonas

Vi kan också använda ett grupp-id (GID) när vi väljer vilken grupp användaren skall ha som primär grupp:

$ sudo usermod -g 44 jonas

Alla grupper i systemet finns i filen /etc/group som vi kan visa med kommandot cat:

$ cat /etc/group

Vi kan också använda kommandot getent för att visa grupper i systemet:

$ getent group

Uppdatera kommentarsfältet för en användare

Med kommandot usermod kan vi uppdatera kommentarsfältet för en användare. Vi skriver så här för att uppdatera fältet för användaren jonas:

$ sudo usermod -c "en liten kommentar" jonas

Om vi tittar i filen /etc/passwd nu så kommer det stå:

jonas:x:1001:1001:en liten kommentar:/home/jonas:/bin/bash

Byta hemkatalog för en användare

Med kommandot usermod kan vi byta hemkatalog för en användare. För att byta hemkatalog för användaren jonas till /tmp/ skriver vi:

$ sudo usermod -d /tmp/ jonas

När vi byter hemkatalog för en användare ändras referensen till hemkatalogen i filen /etc/passwd, den gamla hemkatalogen finns kvar under katalogen /home/ eller där den var. Om vi vill flytta innehållet i den gamla hemkatalogen till den nya använder vi flaggan -m till kommandot:

$ sudo usermod -d /home/jonasnya -m jonas

Byta kommmandotolk för en användare

För att byta kommandotolk för en användare använder vi kommandot chsh. För att byta kommandotolk för användaren jonas till /bin/bash skriver vi:

$ sudo chsh -s /bin/bash jonas

De kommandotolkar vi kan använda defineras i filen /etc/shells. I den filen står alla kommandotolkar som är godkända att använda. Användaren root kan ändra kommandotolk för alla användare och en användare kan bara ändra sin egen kommandotolk.

$ cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/dash
/bin/bash
/bin/rbash
/usr/bin/tmux
/usr/bin/screen

Om vi av någon anledning vill inaktivera inloggning för en användare kan vi sätta användarkontots kommandotolk till /sbin/nologin, vilket innebär att användaren kan logga in men får ingen giltig kommandotolk så hon loggas ut direkt igen. Ett bättre alternativ för att inaktivera användarkonton är att låsa kontot med kommandot sudo passwd -l ANVÄNDARNAMN. För att låsa upp kontot igen använder vi kommandot sudo passwd -u ANVÄNDARNAMN.

Användare kan själva byta kommandotolk med kommandot chsh, men den kommandotolk de väljer måste finnas med i filen /etc/shells som anger vilka kommandotolkar som är tillåtna i systemet. Det är bara användaren root som kan byta kommandotolk till vad som helst för användarkonton. Om vi loggar in som användaren jonas och försöker byta kommandotolk till /bin/banan ser det ut så här:

$ chsh -s /bin/banan
chsh: /bin/banan is an invalid shell

Filen /etc/shadow

Lösenorden för användarkonton i systemet finns i filen /etc/shadow som bara är läsbar för användaren root. Lösenordsfilen har en säkerhetskopia i filen /etc/shadow-. Om vi tittar i filen så hittar vi flera rader som ser ut så här:

$ sudo getent shadow
jonas:$6$S6EJ9IbW$oENmCoYL2ZXVyF55ernS/om...6xHGRtJypfHE/48MU1g2MI0:16387:0:99999\
:7:::

Varje rad i filen /etc/shadow består av nio olika fält som åtskiljs av kommatecken (:) enligt följande:

Användarnamn
Det användarnamn som användaren loggar in med.
Lösenord
Det krypterade lösenordet, uppbyggt enligt mönstret $kryptering$salt$krypterat lösenord$. De olika krypteringsalgoritmerna som kan användas är: $1$ som betyder att lösenordet är hashat med md5-algoritmen, $2$ och $2a$ som betyder att lösenordet är hashat med blowfish, $5$ som betyder att lösenordet är hashat med sha-256 och slutligen $6$ som betyder att lösenordet är hashat med sha-512. Ett lösenord som börjar med ett utropstecken (!) innebär att användarkontot är låst och inte går att logga in med.
Senast ändrat
Anger vilket datum (antal dagar efter den 1 januari 1970) som lösenordet senast ändrades. Står det en nolla (0) här betyder det att användaren måste byta lösenord nästa gång hon loggar in. Är fältet tomt innebär det att systemet inte tar hänsyn till att lösenordet måste bytas efter ett visst antal dagar.
Lägsta tid
Anger hur många dagar användaren måste ha ett lösenord innan hon får byta det igen. En nolla (0) eller ett tomt fält här innebär att inställningen är avstängd.
Högsta ålder
Anger hur många dagar användaren får ha samma lösenord innan hon behöver byta det. När de här antalet dagarna har passerats kommer systemet be henne byta lösenord när hon loggar in. En nolla (0) eller ett tomt fält innebär att funktionen är inaktiverad. Notera att om högsta ålder är lägre än minsta ålder kan användaren inte byta lösenord alls.
Lösenordsvarning
Anger hur många dagar innan lösenordets upphörande en användare skall varnas för detta. En nolla (0) eller ett tomt fält innebär att funktionen är inaktiverad.
Inaktiveringsperiod
Anger hur många dagar efter att ett lösenord har upphört som det fortfarande skall gå att logga in på systemet med det. Efter den här perioden låses kontot helt och en administratör måste låsa upp det. Ett tomt fält anger att funktionen är inaktiverad.
Utgångsdatum
Anger ett datum, räknat från den 1 januari 1970, som ett användarkonto skall upphöra. På det här datumet kommer användaren inte längre kunna logga in i systemet med sitt lösenord. Ett tomt fält anger att funktionen är inaktiverad.
Reserverat fält
Det här fältet är reserverat för framtida användande.

Om vi har två användare i vårt system som har samma lösenord, säg att lösenordet är hemligt kommer de få samma hashade lösenord när lösenordet körs genom en hashningsalgoritm. Genom att använda salt i lösenordet kommer de hashade lösenorden se olika ut, även om lösenorden är samma. Så om vi kommer åt filen /etc/shadow kommer vi inte kunna se att två användare har samma lösenord i systemet.

Titta på följande exempel:

$ echo "hemligt" | md5sum
a5cd101aef627c48222e6def444a36d7 -
$ echo "hemligt" | md5sum
a5cd101aef627c48222e6def444a36d7 -

Resultatet av båda körningarna blir samma, vi får svaret a5cd101aef627c48222e6def444a36d7 båda gångerna. Om vi istället lägger till salt kommer det se ut så här:

$ echo "salt-hemligt" | md5sum
6359645a980afbb72b0091d4f401a093 -
$ echo "peppar-hemligt" | md5sum
ea9e4947dd2b716f5f172f18020ebd84 -

Vi har fortfarande samma lösenord (hemligt), men salt skiljer sig (salt och peppar) vilket resulterar i helt olika resultat. Eftersom vi vet vilket salt vi skall använda för att generera hashen kommer det fungera att logga in - om vi har angett rätt lösenord.

Hantera lösenord och konton

För att ändra ett lösenord för en användare använder vi kommandot passwd:

$ sudo passwd jonas
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully

Ovanstående kommando kommer byta lösenord för användaren jonas. Alla användare kan byta sina egna lösenord genom att använda kommandot passwd när de loggat in. Användaren root kan byta lösenord för alla användare i systemet genom att använda kommandot passwd ANVÄNDARNAMN.

För att tillfälligt låsa ett användarkonto för inloggning kan vi använda kommandot sudo passwd -l ANVÄNDARNAMN (-l för lock). För att låsa upp kontot igen använder vi kommandot sudo passwd -u ANVÄNDARNAMN (-u för unlock). Ett användarkonto som låses upp har samma lösenord som det hade när det låstes. Det är bara användaren root som kan låsa och låsa upp användarkonton i systemet.

$ sudo passwd -l jonas
passwd: password expiry information changed.
$ sudo passwd -u jonas
passwd: password expiry information changed.

För att hantera policyn för lösenord använder vi kommandot chage. Det första vi provar är att hämta ut informationen för det användarkonto vi loggat in med genom att skriva sudo chage -l jonas (där jonas är användarnamnet vi loggar in med):

sudo chage -l jonas
Last password change                              : jan 19, 2013
Password expires                                  : never
Password inactive                                 : never 
Account expires                                   : never
Minimum number of days between password change    : 0
Maximum number of days between password change    : 99999
Number of days of warning before password expires : 7

Ovanför ser vi att lösenordet senast ändrades den 19:e januari 2013 (last password change), lösenordet upphör aldrig att gälla (password expires), vi har ingen inaktivitetsperiod på lösenordet (password inactive), användarkontot har ingen upphörandedag (account expires), vi kan byta lösenordet när vi vill (minimum number of days between password change), det får gå 99999 dagar (nästan 274 år) innan vi måste byta lösenord igen (maximum number of days between password change) och när vi börjar närma oss de 99999 dagarna kommer vi börja få varningar om att vi behöver byta lösenord från sju dagar innan (number of days of warning before password expires).

Om vi vill ändra datumet till julafton 2014 för senaste gången vi bytte lösenord på ett användarkonto skriver vi:

$ sudo chage -d 2014-12-24 jonas

För att se att datumet ändrades använder vi kommandot sudo chage -l jonas.

$ sudo chage -l jonas
Last password change                              : Dec 24, 2014
Password expires                                  : never
Password inactive                                 : never 
Account expires                                   : never
Minimum number of days between password change    : 0
Maximum number of days between password change    : 99999
Number of days of warning before password expires : 7

För att ställa in ett utgångsdatum för användarkontot använder vi flaggan -E. Vi anger datumet i antalet dagar räknat från den 1 januari 1970 eller ett datum i formatet ÅÅÅÅ-MM-DD. Värdet -1 gör att kontot aldrig upphör att gälla. Vill vi ställa in att ett användarkonto skall upphöra att gälla den sista december 2013 skriver vi följande kommando:

$ sudo chage -E 2013-12-31 jonas

För att kontrollera att det blev inställt skriver vi:

$ sudo chage -l jonas
Last password change                              : Dec 24, 2014
Password expires                                  : never
Password inactive                                 : never 
Account expires                                   : Dec 31, 2013
Minimum number of days between password change    : 0
Maximum number of days between password change    : 99999
Number of days of warning before password expires : 7

Om vi vill bestämma hur många dagar en användare skall ha sitt nya lösenord innan hon får byta det använder vi flaggan -m tillsammans med antal dagar. Värdet noll (0) anger att användaren får byta lösenord när hon vill.

$ sudo chage -m 3 jonas

Vill vi bestämma hur många dagar en användare får använda sitt lösenord innan det är dags att byta igen använder vi flaggan -M tillsammans med antalet dagar. Värdet -1 inaktiverar funktionen.

$ sudo chage -M 42 jonas

Och för att sätta hur många dagar innan lösenordet upphör att gälla som användaren skall få en varning om att hon måste byta lösenord varje gång hon loggar in använder vi flaggan -W tillsammans med antal dagar.

Kontrollera att användardatabasen är korrekt

Med kommandot pwck kan vi kontrollera att användardatabasen är korrekt. Kommandot pwck kontrollerar att vi har rätt antal fält, unika och godkända användarnamn, korrekta grupptillhörigheter, korrekt primär grupp, en hemkatalog som existerar, en fungerande kommandotolk, att användare i /etc/passwd-filen finns i /etc/shadow-filen, att vi har lösenord i /etc/shadow-filen och att vi inte har bytt våra lösenord i framtiden (vilket är ett tecken på att klockan varit felaktig, eller att någon manipulerat med filerna). Vi kör kommandot pwck:

$ sudo pwck
user 'ftp': directory '/var/ftp' does not exist
user 'avahi-autoipd': directory '/var/lib/avahi-autoipd' does not exist
pwck: no changes

Här ser vi att användarkontona ftp och avahi-autoipd har en hemkatalog som inte finns. Eftersom det är systemanvändare så spelar det ingen roll.

Att bli en annan användare

Ibland kan vi vilja bli en annan användare. Det kan till exempel vara när vi vill felsöka eller testa om en specifik användare kan komma åt en katalog eller läsa en fil. För att bli en annan användare behöver vi ha rättighet att använda kommandot sudo. Vi anger användarnamnet vi vill bli och sedan -i som är en parameter till kommandot sudo som gör att vi kommer logga in som användaren för att få med alla inställningar hon har. Här blir vi användaren jonas:

$ sudo -u jonas -i

Vi kan också använda kommandot su för att bli en annan användare, då skulle det se ut så här:

$ su - jonas

Grupper

Grupper i Ubuntu finns i filen /etc/group. Filen är en ren textfil och består av gruppinformation på en rad, separerad av fyra fält:

Gruppnamn
Gruppens namn.
Lösenord
Det krypterade lösenordet för gruppen (används sällan).
Grupp-ID
Det numeriska värde som identifierar gruppen i systemet (GID).
Användarlista
En lista med de användare som ingår i gruppen, varje användarnamn är separerad med ett kommatecken (,).

En grupp defineras så här:

jonas:x:1000:

För att skapa en ny grupp använder vi kommandot groupadd, vi skapar gruppen elever:

$ sudo groupadd elever

För att ta bort en grupp använder vi kommandot groupdel. Vi kan inte ta bort en grupp som någon användare har som primär grupp (finns i användarkontot i filen /etc/passwd).

$ sudo groupdel elever

För att byta namn på en grupp använder vi kommandot groupmod. Vi testar detta genom att skapa gruppen elever igen:

$ sudo groupadd elever

Vi kontrollerar att gruppen finns i filen /etc/group:

$ grep elever /etc/group
elever:x:1005:

Kommandot grep används för att hitta reguljära uttryck (mönster) i texter och filer. Här använder vi det för att hitta elever i filen /etc/group.

An icon indicating this blurb contains information

Om vi inte får tillbaka kommandoprompten när vi använder kommandot grep beror det ofta på att vi glömt att skriva den andra parametern (vad vi vill söka i). Avsluta grep genom att hålla inne CTRL och samtidigt trycka på bokstaven c på tangentbordet.

Nu byter vi namn på gruppen elever till students:

$ sudo groupmod -n students elever

Vi kontrollerar att ändringen genomfördes:

$ grep students /etc/group
students:x:1005:

För att lägga till en användare i en grupp använder vi kommandot gpasswd:

$ sudo gpasswd -a jonas students
Adding user jonas to group students
$ grep students /etc/group
students:x:1005:jonas

Användaren måste logga ut och in igen för att få den nya grupptillhörigheten.

För att ta bort en användare från en grupp använder vi kommandot gpasswd:

$ sudo gpasswd -d jonas students
Removing user jonas from group students
$ grep students /etc/group
students:x:1005:

Om vi vill ta reda på vilka grupper en användare tillhör kan vi använda kommandot groups. För att se vilka grupper användaren jonas tillhör skriver vi:

$ groups jonas

För att ta reda på vilka användare som tillhör en grupp använder vi kommandot getent. För att visa användarna i gruppen students skriver vi:

$ getent group students

Filrättigheter

Filrättigheter handlar om åtkomstkontroll till filer och kataloger för användare. Vilka användare får läsa filen? Vilka användare får skriva till och radera filen? Vilka användare får visa innehållet i en katalog? Det är inte alla filsystem som stödjer filrättigheter, men filsystemen ext4 och xfs som är de vanligaste i Linux har stöd för filrättigheter. Från Microsoft Windows kanske du känner till FAT32 och NTFS som är de filsystem vi använder i Microsoft Windows. NTFS har stöd för rättigheter i filsystemet, FAT32 har det inte.

I Linux finns det tre rättigheter:

  • (r)ead, för att användare skall kunna läsa en fil
  • (w)rite, för att användare skall kunna skriva till en fil, eller radera den
  • e(x)ecute, för att användare skall kunna köra filen (starta programmet)

e(x)ecute har en speciell funktion för kataloger, om rättigheten är satt så får användaren visa filerna i katalogen.

An icon of a key

Kom ihåg: read, write och execute

Vi börjar med att skapa en tom fil (som vi döper till filer) och visar den med kommandot ls:

$ touch filer
$ ls -l filer
-rw-r--r--. 1 jonas jonas 0 Sep 13 10:17 filer

Den här delen skall vi titta på nu, för det är den som anger filrättigheterna: -rw-r--r--. Det första tecknet (-) anger att det är en vanlig fil vi ser. Så vi får kvar rw-r--r--. Det är tre grupper: ägaren (user), gruppen (group) och andra (other). Var och en har tre av tecknen var, så här: rw- | r-- | r--. Läser vi rättigheterna blir det ägaren av filen har läs- (r) och skriv- (w) rättighet, gruppen har läsrättighet (r) och andra har läsrättighet (r).

Vem är ägare av filen och vilken grupp tillhör filen? Vi tittar på kommandot ls -l igen:

$ ls -l filer
-rw-r--r--. 1 jonas jonas 0 Sep 13 10:17 filer

Det står jonas jonas där, den första jonas anger att jonas äger filen och den andra jonas anger att filen tillhör gruppen jonas.

Vi läser raden igen: användaren jonas har läs- och skrivrättighet, gruppen jonas har läsrättighet och de användare som inte är jonas och inte tillhör gruppen jonas har läsrättighet till filen.

Filrättigheter

För att ändra rättigheterna på en fil använder vi kommandot chmod. För att ge ägaren (u) rättighet att köra filen (eXecute) skriver vi:

$ chmod u+x filer
$ ls -l filer
-rwxr--r--. 1 jonas jonas 0 Sep 13 10:17 filer

För att ge gruppen (g) skrivrättighet (w) skriver vi:

$ chmod g+w filer
$ ls -l filer
-rwxrw-r--. 1 jonas jonas 0 Sep 13 10:17 filer

För att ta bort läsrättigheten (r) för andra (o) skriver vi:

$ chmod o-r filer
$ ls -l filer
-rwxrw----. 1 jonas jonas 0 Sep 13 10:17 filer

Vi kan kombinera dem i ett och samma kommando, så här skriver vi för att ta bort körrättigheten (x) från ägaren (u), ge gruppen (g) läs- och skrivrättigheter (rw) och ta bort läs- och körrättigheter (rx) för andra (o):

$ chmod u-x,g+rw,o-rx filer
$ ls -l filer
-rw-rw----. 1 jonas jonas 0 Sep 13 10:17 filer

När vi använder + och - sätter vi, eller tar bort rättigheter oavsett vad vi har för rättigheter på filen sedan tidigare. Om ägaren av filen har rw- och vi skriver chmod u-w kommer ägaren ha rättigheten r-- efteråt.

För att sätta exakta rättigheter, oavsett vad vi har sedan tidigare, använder vi =:

$ chmod u=rw,g=r,o=r filer
$ ls -l filer
-rw-r--r--. 1 jonas jonas 0 Sep 13 10:17 filer

Nu kommer ägaren ha läs- och skrivrättigheter, gruppen har läsrättighet och andra har läsrättighet. Oavsett vilka rättigheter filen hade innan. I det här fallet sätter vi också samma rättighet på gruppen och andra (r), vilket kan förkortas:

$ chmod u=rw,go=r filer
$ ls -l filer
-rw-r--r--. 1 jonas jonas 0 Sep 13 10:17 filer

Vi kan också använda oktala tal för att sätta rättigheterna enligt följande:

| 0 | Ingen rättighet (-) | | 1 | Kör (execute, x) | | 2 | Skriv (write, w) | | 4 | Läs (read, r) |

Dessa kan vi kombinera, så skriv (2) och läs (4) blir 6. Kör (1), skriv (2) och läs (4) blir 7. För att sätta rättigheterna med siffror måste vi ange alla tre grupperna (ägaren, gruppen och andra) i samma kommando:

$ chmod 755 filer
$ ls -l filer
-rwxr-xr-x. 1 jonas jonas 0 Sep 13 10:17 filer

755 är samma som om vi hade skrivit u=rwx,g=rx,o=rx (eller den kortare varianten: u=rwx,go=rx). Vi kan inte ta bort, eller lägga till, en specifik rättighet med siffror (som till exempel u+x). Vill vi sätta samma rättigheter på en hel katalog och alla filer under den använder vi -R (för recursive):

$ chmod -R 755 katalog/

För att en användare skall kunna visa filerna i en katalog måste användaren ha rättigheten x på katalogen. Antagligen genom att användaren äger katalogen, tillhör gruppen eller har x genom andra-rättigheten. Har användaren läsrättighet på en katalog kan användaren läsa filer i en katalog, om hon vet namnet på filen. Lämpligast är ju att användaren har både läs- (r) och kör- (x) rättigheten.

För att byta ägare av en fil, eller katalog, använder vi kommandot chown. Notera att det bara är root som kan byta ägare på en fil, så vi måste använda kommandot sudo. För att göra användaren kalle till ägare av filen filer skriver vi:

$ sudo chown kalle filer
$ ls -l filer
-rw-r--r--. 1 kalle jonas 0 Sep 13 10:17 filer

För att byta ägare på en katalog och alla filer i katalogen skriver vi:

$ sudo chown -R kalle katalog/

För att byta grupp på en fil, eller katalog, använder vi kommandot chgrp. För att sätta gruppen till elever på filen filer skriver vi:

$ chgrp elever filer
$ ls -l filer
-rw-r--r--. 1 kalle elever 0 Sep 13 10:17 filer

För att byta grupp på en katalog och alla filer i katalogen använder vi -R (för recursive) och skriver:

$ chgrp -R elever katalog/

Vi kan också använda kommandot chown för att byta ägare och grupp i samma kommando:

$ sudo chown kalle:elever filen

Eller för en katalog och alla filer i katalogen:

$ sudo chown -R kalle:elever katalog/

Processhantering

Processer och deras identitet

I Linux får varje process som startas ett eget process id, som kallas PID. Den första PID som skapas är PID 1 och är i princip alltid processen init.

För att se vilken den högsta PID vi kan skapa i systemet är, tittar vi i filen /proc/sys/kernel/pid_max :

$ cat /proc/sys/kernel/pid_max
4194304

Hantera processer

För att se vilka processer som är igång i systemet använder vi kommandot ps. Det kan till exempel se ut så här:

$ ps
PID   TTY       TIME CMD
1447  pts/0 00:00:00 bash
18725 pts/0 00:00:00 ps

I exemplet ovanför ser vi att vi har två processer igång: bash och ps. Bash är den kommandotolk vi kör i systemet, kommandoraden. Ps är kommandot vi använde för att se vilka processer som vi har igång, ps är alltså också igång när vi kör kommandot. PID anger vilket id processen har, TTY är vilken terminal processen körs på, TIME anger hur länge processen körts och CMD anger vilket kommando som körs.

TIME anger inte hur länge en process har körts i klocktid, utan i CPU-tid. Alltså hur länge processen har använt processorn.

Kommandot ps aux visar alla processer som körs i systemet, av alla användare inklusive systemets egna processer.

Kommandot pstree visar processlistan i en trädstruktur och vi kan se vilken process som startat en annan. Kommandot pstree utan argument visar de processer som körs i vår terminal, vill vi se alla processer i systemet skriver vi pstree –a.

Kommandot top visar processerna interaktivt. För att avsluta top trycker vi på tangenten q. Vill vi ha färger och lite fler funktioner kan vi installera programmet htop:

$ sudo apt install htop

Starta sedan programmet genom att ange kommandot htop. Avsluta genom att trycka på tangenten q.

Kommandot pidof visar vilken PID en process har. Till exempel pidof bash. Kommandot används mest i skript där vi behöver veta om en process är igång eller inte.

Trevliga processer

Om vi vill starta en process med en annan prioritet än standard (0) använder vi kommandot nice. Kommandot nice gör att en process kan få företräde till processortid och vi bestämmer hur mycket, eller lite, företräde processen skall få genom att sätta nice till olika nivåer. Noll (0) är standard och ges till alla processer som startas. Därifrån kan vi ge processen högre prioritet genom att sätta nice till en lägre siffra, ner till -20 som är den högsta prioriteten vi kan ge en process. Vi ger processen en lägre prioritet genom att sätta en högre nice, upp till 19. Så -20 är högsta prioritet och 19 är lägsta. Noll (0) är standard. En process som har prioriteten 19 kommer enbart köras när ingen annan process i systemet vill använda processorn.

Om vi till exempel vill starta programmet top med den lägsta prioriteten (19) skriver vi:

$ nice -n 19 top

När vi tittar i kolumnen NI i top ser vi att processen körs som nice 19. Väljer vi en högre siffra (20 och uppåt) kommer processen få 19, som är den lägsta prioriteten vi kan ge en process. Värden under noll (0) kan vi inte sätta om vi inte är användaren root. Det lägsta värdet vi kan sätta är -20, försöker vi sätta ett lägre värde än så får vi automatiskt värdet -20. Om vi inte anger något värde till nice kommer processen få värdet 10:

$ nice top

Med kommandot renice kan vi ändra prioritet på befintliga processer, sådana som redan är igång i systemet. För att ändra prioritet på processen med PID 2664 från 0 till 4 skriver vi:

$ sudo renice -n 4 2664
2664 (process ID) old priority 0, new priority 4

Vi kan sätta nice på alla processer för en specifik användare genom att ange argumentet –u:

$ sudo renice -n 4 -u jonas
1000 (user ID) old priority 0, new priority 4

Här ändrar vi alla processer som tillhör användaren jonas till prioriteten 4 från att ha varit 0. Användaren root kan ändra prioritet på processer hur hon vill. En användare i systemet kan bara sänka sin prioritet. Om användaren sänker sin prioritet på sin process från 0 till 10 kommer hon bara kunna sänka den ännu mer (11, 12, …) men inte höja den (9, 8, …) även om processens prioritet var högre innan.

Bakgrundsprocesser

Om vi skall köra ett kommando som kommer ta tid kan vi välja att köra det programmet i bakgrunden. När vi startar ett program på kommandoraden lägger vi till & i slutet av kommandot för att köra det i bakgrunden, ett exempel:

$ dd if=/dev/zero of=zerofile.img bs=1 count=100000 &
[1] 28237

Där [1] anger vilket bakgrundsjobb det är och 28237 anger vilket process-id (PID) processen fick. Vi kan lista bakgrundsjobb med kommandot jobs.

För att ta fram jobbet igen använder vi kommandot fg. Om ett jobb har jobb-id 1 skriver vi fg 1. Har vi flera jobb igång väljer vi helt enkelt vilket jobb vi vill ta fram med kommandot fg genom att ange jobbets id: fg 3. Om vi redan har startat en process på kommandoraden och vi vill lägga det som ett bakgrundsjobb trycker vi på tangenterna CTRL och z samtidigt.

Då pausar vi processen och kan välja att fortsätta köra det som ett bakgrundsjobb med kommandot bg. Pausar vi flera processer väljer vi vilken process vi vill köra som bakgrundsjobb med kommandot bg och jobbets id, till exempel: bg 2. Om vi lagt ett jobb i bakgrunden med kommandot bg och vill avsluta det använder vi kommandot kill. Vi kan ta reda på processens PID och använda kommandot kill med PID:en, eller så anger vi vilket jobbnummer vi vill avsluta. Jobbnumret anges med ett procenttecken (%) och jobbnumret:

$ kill %1

Vill vi använda till exempel SIGKILL (-9) på jobbet skriver vi:

$ kill -9 %1

Att använda screen

Ibland är det användbart att starta ett program på en linuxmaskin och låta det fortsätta köras även när vi loggat ut från maskinen. Det enklaste sättet att lösa detta är med kommandot screen. Kommandot screen är inte installerat som standard, utan vi får börja med att installera det i systemet med hjälp av kommandot apt:

$ sudo apt install screen

För att starta ett program med screen börjar vi kommandoraden med screen och sedan kommandot vi vill köra:

$ screen top
[screen is terminating]

Vi startar top med screen. För att avsluta top trycker vi på q på tangentbordet. När vi avslutar top kommer också screen avslutas, eftersom vi startade top med screen.

Vi kan också starta screen utan att köra ett kommando, då skriver vi helt enkelt screen på kommandoraden:

$ screen
Screen

När vi startat en session med screen kan vi går ur screen, men fortfarande köra kommandot i systemet, genom att hålla inne tangenten CTRL och trycka på tangenten a och sedan på tangenten d för att komma ut till vår terminal igen. Vi kommer få en rad liknande denna på kommandoraden:

[detached from 28310.pts-1.ubuntu]

Vilket betyder att screen och alla kommandon vi kör i screen fortfarande körs i systemet. För att återansluta till sessionen skriver vi screen -r. När vi kör kommandon i screen kommer de fortsätta köras i systemet även om vi loggar ut från det. Detta är användbart när vi vill köra en längre process, men inte vill vara inloggade i systemet under tiden det körs. Med argumentet -list kan vi se alla screen som körs i systemet just nu:

$ screen -list

Det här är bara en bråkdel av vad kommandot screen klarar av att göra, för mer information läser vi manualsidan för kommandot: man screen.

Att använda tmux

Tmux används på samma sätt som screen. Det är mycket en smaksak vilken av screen och tmux man gillar. Till stora delar gör de samma sak.

För att köra tmux behöver vi installera paketet tmux:

$ sudo apt install tmux

Sedan kan vi starta tmux genom att skriva:

$ tmux
tmux

Kommandon till tmux ges genom att vi trycker på CTRL och bokstaven b samtidigt, C-b. Därefter anger vi kommandot vi vill ge. För att detacha vår tmux-session, så att vi kommer ut ur den men den fortfarande kör i bakgrunden använder vi C-b d. För att återansluta till sessionen skriver vi kommandot tmux attach.

Vi kan också skapa flera fönster i tmux (de syns i raden längst ned) genom att trycka C-b c (för create). Hoppar mellan fönster gör vi genom att använda C-b n (för next).

c-b t visar en klocka med aktuell tid.

Hur Linux hanterar processer

Vi kan skicka signaler till processer i Linux, signalerna är styrkommandon som processen kan fånga upp (beroende på om utvecklaren har skrivit programkod som lyssnar på dem) och vi använder dem ofta för att stänga ner processer, eller helt enkelt tvångsavsluta dem.

Det finns en mängd olika signaler vi kan använda, några av de vanligaste är:

SIGTERM
Avslutar en process. Signalen ber processen stänga ner sig på ett korrekt sätt, precis som om vi hade valt att avsluta ett program.
SIGKILL
som försöker avsluta processen utan att ta hänsyn till något alls. Processen får ingen möjlighet att stänga filer som är öppna, eller frigöra minne som den använt.

För att se alla signaler vi kan använda i Linux kan vi skriva kommandot man signal och manualsidan för kommandot kill har lite information också: man kill . För att skicka signaler kan vi använda kommandot kill:

$ kill PID

Om vi vill avsluta flera processer som heter samma sak, till exempel top kan vi använda kommandot killall. Med killall anger vi inte processernas PID, utan deras namn.

För att avsluta alla processer som heter top skriver vi:

$ killall top

Vi kan naturligtvis skicka andra signaler till processerna än standardsignalen SIGTERM genom att ange signalen till kommandot:

$ killall -9 top

Användaren root kan avsluta alla användares processer. En vanlig användare kan enbart avsluta sina egna processer. Om vi vill avsluta alla processer som körs av användaren jonas skriver vi:

$ killall -u jonas

Arbeta med text

Att arbeta med Linux innebär att vi kommer jobba mycket med att redigera textfiler. Vi kommer ändra i konfigurationsfiler, vi kommer skriva skript och vi kommer dokumentera vårt system. Därför behöver vi kunna hantera en textredigerare. Det finns egentligen tre textredigerare som används idag och vilken som blir ens favorit är högst personligt:

vi eller vim
Vi(m) är en ganska krånglig texteditor innan man blir vän med den. Fördelen med att kunna vi(m) är att den finns installerad i så gott som varje UNIX-baserat system. Grundkunskaper i vi(m) är således värdefullt oavsett om vi sitter med Linux, macOS eller BSD för att nämna några. vi är originalet och vim (Vi IMproved) är en förbättrad version av vi. Det är ofta vim man menar när man pratar om vi.
nano
En enkel texteditor som är snabb att komma igång med. Funktionaliteten är ungefär som Notepad i Windows och många nybörjare gillar nano.
emacs
En avancerad textredigerare som kan göra mycket mer än att bara redigera text. En del säger att Emacs är ett helt eget operativsystem.

vi(m)

Vi har två olika lägen: command mode och insert mode. I command mode kan vi ge vim kommandon som att hoppa till början av raden, lägg till text på raden, ta bort raden och så vidare. I insert mode ändrar och skriver vi in texten vi vill ha in i filen. För att komma till command mode trycker vi på tangenten ESC på tangentbordet och för att komma till insert mode så vi kan skriva in text trycker vi på bokstaven i (för insert) när vi är i command mode. Vi kan också ge kommandon till vi på den nedersta raden på skärmen, dit kommer vi när vi är command mode och skriver kolon (:). Efter kolon-tecknet kan vi skriva det eller de kommandon vi vill utföra.

vim
An icon indicating this blurb contains information

Det kan vara svårt att förstå hur vi avslutar vi. Tryck på ESC, skriv sedan :q! (det skrivs längst ned på skärmen/terminalen) och tryck på RETUR. Det här gör att vi väljer att avsluta vi utan att spara ändringar.

Om vi inte har vim installerat använder vi kommandot apt för att installera:

$ sudo apt install vim

| i | Sätt in text framför markören | | a | Sätt in text efter markören | | / | Söka i filen, till exempel: /jonas söker efter jonas i filen. Tryck på n för att komma till nästa sökträff. | | x | Ta bort tecknet under markören | | 0 | Flytta markören till början av raden | | $ | Flytta markören till slutet av raden | | o | Skapa en ny rad under den du står på | | O | Skapa en ny rad över den du står på | | dd | Ta bort raden du står på | | d$ | Ta bort allt från markören till slutet av raden | | d0 | Ta bort allt från markören till början av raden | | u | Ångra (undo) | | :12 | Hoppa till rad 12. | | :w | Spara filen | | :q | Avsluta vi | | :q! | Avsluta vi utan att spara ändringar i filen | | :wq | Spara filen och avsluta vi | | ZZ | Spara filen och avsluta vi |

När vi är i insert mode står det -- INSERT -- längst ner på skärmen. För att komma ut ur insert mode (när vi kan skriva text i filen) trycker vi på tangenten ESC.

Det finns en manual för vim inbyggd, använd kommandot :help för att visa den. Kommandot :q avslutar manualen.

Det är en bra idé att få koll på textredigeraren vim då den finns installerad i princip i alla UNIX-baserade system. Det är inte alltid vi kommer kunna installera vår föredragna textredigerare på system vi kommer i kontakt med, så det är bra att kunna grunderna i vim.

Kommandot vimtutor startar en lär dig vim-utbildning. Gå igenom den så kommer du kunna det du behöver kunna.

Saknar du vimtutor behöver du installera paketet vim-runtime:

$ sudo apt install vim-runtime

nano

Editorn nano fungerar som de texteditorer vi är vana med som till exempel Notepad i Microsoft Windows. Kommandon till nano ges med CTRL-tangenten och en bokstav. Längst ner på skärmen ser vi bland annat ^X Exit, vilket betyder att vi skall hålla in CTRL-tangenten och trycka på bokstaven x samtidigt. En del kommandon i nano skrivs som M-d, där M är meta vilket är ALT-tangenten på en PC-dator.

nano

| ^G | Visar hjälp för nano, vi avslutar hjälpen genom att trycka på bokstaven q | | ^W | Söker i filen | | ^O | Sparar filen | | ^X | Avslutar nano |

Om vi saknar nano i vårt system kan vi installera det med kommandot apt:

$ sudo apt install nano

Emacs

Vi startar Emacs genom att skriva emacs på kommandoraden. För att avsluta Emacs håller vi in CTRL och trycker på bokstaven x, vi fortsätter hålla in CTRL och sedan trycker vi på c. I Emacs skrivs det här som C-x C-c där C betyder CTRL.

| C-x C-c | Avsluta Emacs | | C-s | Söka | | C-g | Avbryt | | M-x tetris | Spela Tetris | | C-h t | Starta Emacs tutorial |

För att installera emacs använder vi kommandot apt:

$ sudo apt install emacs-nox

Det finns också ett emacs-paket att installera, det är den grafiska versionen som kräver att vi har en grafiskt skrivbordsmiljö installerad. Om vi använder en textbaserad server är det bättre att installera den textbaserade Emacs (emacs-nox), eftersom den grafiska kommer installera paket för skrivbordsmiljön också.

emacs

Reguljära uttryck (regex)

Reguljära uttryck, regular expressions (eller regex, regexp) på engelska, är mönster som vi använder för att hitta en delmängd av en text. Säg att vi vill visa alla användarkonton som börjar med bokstaven a i vårt linuxsystem, det kan vi göra enkelt genom att skriva kommandot grep ^a /etc/passwd. ^ betyder börjar med och a är bokstaven a helt enkelt. I det här fallet blir det, visa mig alla rader som börjar med bokstaven a i filen /etc/passwd. Viktigt att känna till är att reguljära uttryck är skiftlägeskänsliga (skillnad på stora och små bokstäver).

Vi kan använda en del specialtecken för att gruppera och matcha mönster:

| [ ] | Matchar på det som är i paranteserna. Bokstäver och siffror, antagligen tillsammans eller enskilt. Notera att matchningen är skiftlägeskänslig! (Stora och små bokstäver) | | - | Skapar en serie av tecken, används ofta för 0-9, A-Z och a-z. | | ^ | Matchar på början av en rad, om vi vill matcha alla rader som börjar med bokstaven L skriver vi ^L. Om tecknet finns i [] som till exempel [^a] betyder det att vi inte skall matcha på det tecknet. | | $ | Matchar på rader som slutar med. Som till exempel Linux$ skulle matcha alla rader som slutar med Linux. | | . | Matchar vilket (okänt) tecken som helst. Till exempel L...x | | * | Matchar tecknet ingen eller fler gånger. Till exempel L.*x, där . anger ett okänt tecken och * att vi vill matcha inget eller flera okända tecken. | | ( ) | Kombinerar flera mönster att matcha på. | | | | Matchar på vänster eller höger, används ofta tillsammans med ( ) |

Vi börjar med att skapa filen exempel.txt som finns här nedanför. Skriv in texten i en fil och spara den i ditt linuxsystem. Vi kommer använda den som exempel för att titta på hur vi kan använda reguljära uttryck.

Figur 2. Filen exempel.txt
Det här är en exempelfil med text
Vi skall titta på hur reguljära uttryck
fungerar, kanske 2 gånger.

Med grep kan vi söka efter ord som "Linux"
Eller siffror som 20och4
Vi använder Ubuntu
men det finns också andra Linux
som till exempel Debian GNU/Linux
och Fedora så klart

Hur många Linux finns det?
20 + 4 = 24
Eller är det fler?

Med kommandot grep kan vi arbeta med reguljära uttryck, vi börjar med ett första exempel:

$ grep r[ae] exempel.txt
Vi skall titta på hur reguljära uttryck
fungerar, kanske 2 gånger.
Med grep kan vi söka efter ord som "Linux"
men det finns också andra Linux
och Fedora så klart

Här letar vi efter allt som har bokstaven r följt av ett a eller ett e. Har vi texten reguljär, raket, dragkrok, orkestrera kommer det matcha uttrycket r[ae].

Vi tittar på ett liknande exempel, där vi använder s[ik] som uttryck:

$ grep s[ik] exempel.txt 
Vi skall titta på hur reguljära uttryck
fungerar, kanske 2 gånger.
Eller siffror som 20och4

Vill vi hitta alla förekomster av bokstaven k i en fil skriver vi:

$ grep k exempel.txt 
Vi skall titta på hur reguljära uttryck
fungerar, kanske 2 gånger.
Med grep kan vi söka efter ord som "Linux"
men det finns också andra Linux
och Fedora så klart

Om vi vill hitta alla förekomster av bokstaven k, men inte dem där k följs direkt av ett a använder vi mönstret k[^a]:

$ grep k[^a] exempel.txt 
fungerar, kanske 2 gånger.
men det finns också andra Linux
och Fedora så klart

Titta på svaren vi får från de två ovanstående exemplen, k och k[^a].

Om vi vill hitta alla förekomster av siffrorna 0, 1, 2, 3, 4, 5, 6, 7, 8 och 9 kan vi skapa en range av dem genom att skriva [0-9] (alla siffror från noll (0) till och med nio (9)):

$ grep [0-9] exempel.txt 
fungerar, kanske 2 gånger.
Eller siffror som 20och4
20 + 4 = 24

Om vi vill visa alla rader i filen där vi inte har bokstäver (A-Z) använder vi grep -v [A-Za-z] exempel.txt. Notera hur vi anger A-Za-z eftersom vi behöver matcha på både stora och små bokstäver. Prova också att ta bort -v och se vad skillnaden blir.

$ grep -v [A-Za-z] exempel.txt 
20 + 4 = 24

Om vi vill hitta alla rader i filen som börjar med bokstaven M skriver vi:

$ grep ^M exempel.txt 
Med grep kan vi söka efter ord som "Linux"

Eftersom vi enbart matchar på stora m (M) och vill få fram de rader som börjar med små m också skriver vi ^[Mm]:

$ grep ^[Mm] exempel.txt <.>
Med grep kan vi söka efter ord som "Linux"
men det finns också andra Linux

Matcha rader som börjar med bokstaven M eller bokstaven m.

Ibland är vi mer intresserade av att matcha rader som slutar med någonting. För att matcha alla rader som slutar med ux i filen exempel.txt skriver vi mönstret ux$:

$ grep ux$ exempel.txt 
men det finns också andra Linux
som till exempel Debian GNU/Linux

Slutligen vill vi ibland matcha på rader som börjar med och slutar med någonting. För att matcha alla rader som börjar med bokstaven s och slutar med ux använder vi mönstret ^s.*ux$. . anger vilket tecken som helst (det vill säga - det kan matcha vilket tecken som helst) och * betyder inget eller flera av det som står till vänster. .* blir alltså inget eller flera av vilket tecken som helst:

$ grep ^s.*ux$ exempel.txt 
som till exempel Debian GNU/Linux

Omstyrning och rör

I vår kommandotolk (BASH) finns tre olika strömmar för in- och utmatning av information:

  • Standard in (0, stdin)
  • Standard out (1, stdout)
  • Standard error (2, stderr)

Bash använder som standard Standard Out (stdout) för att skicka utmatning från kommandon. Ibland vill vi ändra detta så utmatningen till exempel lagras i en fil istället. Säg att vi visar innehållet i en katalog och vi vill ha den listan i en textfil som vi kan arbeta vidare med istället, då använder vi > för att styra om standard out till filen:

$ ls -l > ls.txt

Här listar vi katalogens innehåll (filer och kataloger) med kommandot ls -l, men istället för att få listan på skärmen skickas utmatningen till filen ls.txt. > fångar upp det som skickas till standard out och styr det vidare till en fil. Ett annat sätt att fånga upp standard out är att skriva 1>, vilket betyder att det är stdout vi vill jobba med. stdin har 0 och stderr har 2.

Exemplet ovanför gick bra för att vi listade en katalog som finns (den katalog vi befinner oss i). Om vi provar att visa filerna i en katalog som inte finns kan det se ut så här:

$ ls -l /tmp/inget
ls: cannot access '/tmp/inget': No such file or directory

Hade vi skickat stdout till filen ls.txt hade den varit tom, men vi hade fått ett felmeddelande:

$ ls -l /tmp/inget > ls.txt
ls: cannot access '/tmp/inget': No such file or directory
$ cat ls.txt 
$ 

Att filen ls.txt blir tom beror på att vi inte kan lista några filer i en katalog som inte finns och att vi får ett felmeddelande på skärmen och inte i filen beror på att felmeddelandet skrivs till standard error (stderr). Vill vi fånga upp stderr använder vi 2> och det kan se ut så här:

$ ls -l /tmp/inget 1> ls.txt 2> ls.fel
$ cat ls.txt
$ cat ls.fel
ls: cannot access '/tmp/inget': No such file or directory
$

Här fångar vi upp både standard out (1>) och standard error (2>) och skriver dem till två olika filer. När vi tittar på innehållet i filen ls.txt är det tomt. Katalogen vi försökte lista finns inte, så inget kommer skrivas till filen ls.txt. När vi tittar på innehållet i filen ls.fel får vi fram felmeddelandet som tidigare skrevs ut på skärmen. Det beror på att vi nu fångat upp stderr med 2> och skickat utmatningen till filen ls.fel.

Nu skapar vi en fil som vi döper till namn.txt, i filen skriver vi in följande namn:

Figur 3. Filen namn.txt
anette
therese
madelene
ann-kristin
emma
jessica
samantha
magdalena
hanna
anna
susanne
maria
malin

Spara filen och avsluta din textredigerare. Nu skriver vi:

$ cat namn.txt 
anette
therese
madelene
ann-kristin
emma
jessica
samantha
magdalena
hanna
anna
susanne
maria
malin

Inget konstigt händer, filens innehåll skrivs ut som vi skrev den i textredigeraren. Låt oss nu sortera filen i alfabetisk ordning:

$ sort < namn.txt
anette
anna
ann-kristin
emma
hanna
jessica
madelene
magdalena
malin
maria
samantha
susanne
therese

Namnen i filen skrivs nu ut i alfabetisk ordning. Här använder vi omdirigeraren för stdin, <, för att läsa innehållet i filen namn.txt och skicka in den i kommandot sort. Eftersom kommandot sort vill skicka sin sorterade data till standard out hamnar listan på skärmen. Om vi vill spara den sorterade listan i en ny fil dirigerar vi om stdout till en fil så här:

$ sort < namn.txt > namn.sorterad.txt

Ingenting händer på skärmen, men tittar vi i katalogen så skapades en fil namn.sorterad.txt. Med kommandot cat kan vi visa innehållet i filen:

$ cat namn.sorterad.txt 
anette
anna
ann-kristin
emma
hanna
jessica
madelene
magdalena
malin
maria
samantha
susanne
therese

Den sorterade listan med namn skickades till filen namn.sorterad.txt istället för skärmen.

När vi använder > skriver vi över innehållet i filen varje gång. Ibland vill vi istället lägga till saker i slutet av en fil, då fungerar det inte att använda > som skriver över filen. Vi använder två stycken >-tecken istället: >>. >> gör att det vi dirigerar om till filen läggs till i slutet av filen:

$ sort < namn.txt > namn.sorterad.txt
$ cat namn.txt >>namn.sorterad.txt
$ cat namn.sorterad.txt
anette
anna
ann-kristin
emma
hanna
...

Vi kan också använda rör (pipes) för att skicka data vidare till andra kommandon. Det kan till exempel vara att vi har en katalog med många filer så att kommandot ls -l gör att massor av text skrollar förbi på skärmen. Då kommer kommandot less till hjälp. less gör att när skärmen är fylld med text pausar less utmatningen tills vi tryckt på mellanslag och då får vi en ny sida med text. Vi skriver ls -l /usr/bin/ | less för att se hur det fungerar. Pipe-tecknet (det lodräta strecket, |) fångar upp stdout från komamndot ls och skickar in det till stdin för kommandot less:

$ ls -l /usr/bin/ | less

Varför kan vi inte använda < istället? < fungerar bara på filer, inte på kommandons utmatningar (stdout). Så för att fånga upp stdout från ett kommando och skicka det till ett annat kommandos stdin använder vi pipetecknet (|), för att hämta information från en fil och skicka den till ett kommandos stdin använder vi <-tecknet. Vi skulle kunna skriva så här, om vi ville slippa pipe:

$ ls -l /usr/bin/ > filer && less filer

Här visar vi en detaljerad lista över alla filer och kataloger som finns i katalogen /usr/bin/ och sparar listan till filen filer. Sedan använder vi kommandot less för att öppna och visa filen filer som vi precis skapade. Att använda pipe för detta är mycket effektivare och bättre, om vi inte också vill spara utmatningen till en fil så klart.

Vi kan kombinera flera pipe om vi behöver det. Om vi till exempel vill visa alla filer i en katalog, sortera listan baklänges och visa listan med less skriver vi:

$ ls -1 /usr/bin/ | sort -r | less

Skulle vi istället vilja ha den baklängessorterade listan till en fil skulle vi skriva så här:

$ ls -1 /usr/bin/ | sort -r > sorterad.txt

Det går alltså att kombinera flera <. >, >> och | på en och samma kommandorad.

Jobba mer med text

Vi börjar med att skapa filen namn.txt med följande innehåll:

jonas
kalle
lasse
bosse
jonas
kalle
linda
kalle

Vi vill hitta unika namn i den filen och vänder oss därför till kommandot uniq:

$ uniq namn.txt 
jonas
kalle
lasse
bosse
jonas
kalle
linda
kalle

Här ser vi att listan med namn blir kortare, men vi har fortfarande dubletter av namn kvar. Det beror på att uniq behöver ha dubletterna efter varandra. Vi kan använda kommandot sort för att sortera listan alfabetiskt och sedan använda uniq för att visa unika namn:

$ sort namn.txt | uniq 
bosse
jonas
kalle
lasse
linda

Nu fungerade det bättre, men varför använda kommandot uniq när sort redan har möjlighet att visa unika:

$ sort -u namn.txt 
bosse
jonas
kalle
lasse
linda

Om vi vill visa listan med unika namn i filen baklänges (Z-A) lägger vi till -r:

$ sort -u -r namn.txt 
linda
lasse
kalle
jonas
bosse

Hur många namn vi har i listan kan vi visa med kommandot wc som bland annat kan räkna rader (lines) i en fil:

$ wc -l namn.txt 
8 namn.txt

Hur många unika namn har vi i listan i filen? Vi kombinerar kommandot sort och kommandot wc för att räkna ut det:

$ sort -u namn.txt | wc -l
5

Om vi vill sortera filer med siffror får kommandot sort lite problem, vi skapar filen nummer.txt med följande innehåll:

45
2
324
121
23
44
25
2156
11354

Om vi använder kommandot sort för att sortera numren i filen kommer vi få följande resultat:

$ sort nummer.txt 
11354
121
2
2156
23
25
324
44
45

Det blir rätt, men ändå fel. sort gör helt rätt - börjar med siffran 1 och går vidare med 2 och så vidare. Men vi vill ju ha numren i ordning efter värde. Om vi lägger till -n för numeric till kommandot sort får vi:

$ sort -n nummer.txt 
2
23
25
44
45
121
324
2156
11354

Nu ser det bättre ut. Vill vi ha numren i baklänges ordning (högst till lägst) använder vi -r och skriver sort -n -r nummer.txt.

Användarkonton i Ubuntu finns i filen /etc/passwd. Det hade varit trevligt om den listan kunde vara i bokstavsordning, så vi använder kommandot sort:

$ sort < /etc/passwd

Varför inte bara visa användarnamnen, istället för all information? Med kommandot cut kan vi splitta en rad som har avgränsare (till exempel :) och välja vilket fält vi vill ha:

$ cut -d ":" -f1 /etc/passwd | sort

Här berättar vi att vår avgränsare är :, vilket fungerar bra för filen /etc/passwd som ser ut så här:

jonas:x:1000:1000:Jonas Björk:/home/jonas:/bin/bash

cut -d ":" anger alltså att vår avgränsare (delimiter) är :, -f1 anger att vi vill ha det första fältet (i det här fallet blir det det användarnamnen) och /etc/passwd anger vilken fil vi vill jobba med.

Om vi vill veta vilka kommandotolkar som används av våra användare kan vi titta på fält sju (7) i filen /etc/passwd, där står det vilken kommandotolk som användaren skall logga in med. Användaren jonas nedanför använder kommandotolken /bin/bash:

jonas:x:1000:1000:Jonas Björk:/home/jonas:/bin/bash

För att hämta ut alla användares kommandotolkar skriver vi:

$ cut -d ":" -f7 /etc/passwd

Det blir en ganska lång lista och vi vill egentligen bara veta vilka unika kommandotolkar användarna använder, så låt oss använda sort -u också:

$ cut -d ":" -f7 /etc/passwd | sort -u
/bin/bash
/bin/sync
/sbin/halt
/sbin/nologin
/sbin/shutdown
/usr/sbin/nologin

Hur många användarkonton har vi i systemet? Kommandot wc kan räkna åt oss:

$ wc -l /etc/passwd
49 /etc/passwd

wc -l räknar rader (lines), wc -c räknar antal tecken i en fil och wc -w räknar antalet ord (words) i en fil.

Flera kommandon på samma rad

Ibland vill vi köra flera kommandon på en och samma rad. Varför sitta och vänta på att ett kommando skall köras klart innan vi kan skriva nästa? Det finns två sätt att göra detta, antagligen med ett semikolon (;) eller med två och-tecken (&&). Skillnaden på dem är att && inte kör nästa kommando om föregående inte lyckades. Detta visas bäst med ett exempel:

$ date ; date
fre 11 dec 2020 00:14:14 CET
fre 11 dec 2020 00:14:14 CET
$ dat ; date
-bash: dat: command not found
fre 11 dec 2020 00:14:14 CET
$ dat && date
-bash: dat: command not found

Första gången vi kör kommandot lyckas det båda gångerna. Andra gången stavar vi fel på date (vi skriver dat) och får ett felmeddelande och andra kommandot (date) körs. Sista gången gör vi samma misstag (stavar fel på date) och får ett felmeddelande.

Det andra date-kommandot körs inte, eftersom vi använder &&. Om vi istället vill köra ett annat kommando om det första inte lyckas (motsatsen till &&) kan vi använda dubbla pipe-tecken (||):

$ date || echo "ett datum visades inte"
fre 11 dec 2020 00:14:14 CET
$ dat || echo "ett datum visades inte"
-bash: dat: command not found
ett datum visades inte

Eftersom date fungerar första gången kommer vi inte skriva ut texten som kommer efter. I andra försöket skriver vi date utan e (dat) och det blir fel, därför skrivs texten ut.

I de exempel vi har haft ovanför använder vi två kommandon, vi kan naturligtvis använda flera kommandon. Som exempel kan vi konfigurera och bygga ett program från källkod direkt från en och samma kommandorad:

$ ./configure && make && make install

Här är det viktigt att vi använder &&, vi vill ju inte bygga programmet om det inte gick att konfigurera och än mindre vill vi installera ett sådant program i vårt system.

Ibland blir raderna med kommandon vi skriver långa, då kan vi använda backslash (\-tecknet) för att fortsätta mata in kommandon på nästa rad innan vi kör alltihopa:

$ echo "Nu skall vi skriva ut tid och datum."; \
> date; \
> echo "Det gick ju bra."
Nu skall vi skriva ut tid och datum.
fre 11 dec 2020 00:14:14 CET
Det gick ju bra.

Samma sak skulle kunna skrivas som:

$ echo "Nu skall vi skriva ut tid och datum."; date; echo "Det gick ju bra."

Kommandotolken Bash

En kommandotolk, skal (shell), används för att ge datorn instruktioner om vad den skall göra. Idag är det vanligaste sättet att arbeta med en dator med ett grafiskt gränssnitt, GUI (Graphical User Interface). I ett GUI klickar användaren med en muspekare på vad användaren vill att datorn skall göra. I ett skal skriver användaren sina kommandon i en textbaserad miljö, som kallas CLI (Command Line Interface). Ett skal är alltså ett kommandoradsbaserat gränssnitt mot användaren.

Den första kommandotolken för UNIX skrevs av Steven Bourne 1974. Kommandotolken hette Bourne Shell och är kanske mer känd som sh. Bourne Shell har satt standarden för hur kommandotolkar skall se ut och fungera, bland annat har vi fått kommandoprompten $ från Bourne Shell.

Bash är troligen den vanligaste kommadotolken som används i operativsystemet Linux. Bash, som är en förbättring av Bourne Shell, är en förkortning av Bourne Again Shell. Bash är resultatet av GNU-projektets arbete med att förbättra Bourne Shell. Bash är helt POSIX-kompatibelt. I exemplet nedanför ser vi hur Bash är startad och väntar på kommandon från användaren.

jonas@ubuntu:~$
jonas
är användarnamnet på den användare som är inloggad
ubuntu
är namnet på den dator användaren är inloggad på
tildetecknet (~)
anger vilken katalog användaren är i, tilde är en förkortning för användarens hemkatalog och dollartecknet är kommandoprompten, det är efter kommandoprompten användaren kan skriva sina kommandon.

Vi kan arbeta med Bash interaktivt, det vill säga vi skriver våra kommandon direkt på kommandoraden, eller genom att skriva skript. Ett skript är en samling av instruktioner som sparats i en vanlig textfil. När vi kör textfilen med Bash kommer den utföra de instruktioner textfilen innehåller. Vi kommer börja att jobba med Bash interaktivt för att lära oss hur Bash fungerar och vad vi kan göra med Bash. När vi kommit en bit på vägen börjar vi skapa våra första skript.

När Bash startas letar den efter ett par konfigurationsfiler som den läser in (om de finns) och kör. Ordningen på vilka filer den läser in är:

  • Om filen /etc/profile finns läses den in. Här finns systemglobal konfiguration. Filen läser in filen /etc/bash.bashrc. Filen läser också in alla filer som finns i katalogen /etc/profile.d/ och har filändelsen .sh.
  • Om filerna ~/.bash_profile och/eller ~/.bash_login finns kommer Bash läsa in den första filen den hittar av dessa. Om de inte finns kommer ~/.profile läsas in av Bash. I Ubuntu är det ~/.profile som skapas för varje användare och används. ~/.profile läser in filen ~/.bashrc. Filen ~/.bashrc i sin tur läser in filen ~/.bash_aliases om den finns.

Vill vi skapa egna systemglobala miljövariabler eller funktioner för Bash är rekommendationen att skapa filen /etc/profile.d/custom.sh och lägga allt där. Detta för att vi skall undvika att nästa uppdatering av paketet bash skulle kunna skriva över våra ändringar i filerna.

Om vi vill skapa egna variabler och funktioner för vår egen användare är ~/.bashrc en bra plats att göra det i. Lägg till variabler och funktioner i slutet av den filen. Är det alias vi vill lägga till är filen ~/.bash_aliases en bra plats att lägga in dem i.

.bash_profile (och .bash_login , .profile) läses bara in när du loggar in i systemet. Startar du en ny instans av Bash när du är inloggad redan, till exempel genom att skriva bash på kommandoraden, läser Bash enbart in filen .bashrc.

När du loggar ut läser Bash filen ~/.bash_logout om den finns. Där kan vi till exempel lägga in kommandot clear så att Bash raderar allt på skärmen innan vi loggar ut.

Kommandoprompten

Kommandoprompten styrs av två miljövariabler: PS1 och PS2. PS1 är den kommandoprompt du normalt ser (se exemplet ovanför), medan miljövariabeln PS2 används för den kommandoprompt som visas på andra raden. PS2 är normalt ett >-tecken.

Exempel:

jonas@ubuntu:~$ echo \
> arne
arne
jonas@ubuntu:~$

Vi kan ändra kommandopromptarna så de blir som vi vill.

An icon indicating this blurb contains information

I tabellen nedanför visar vi ett par av de styrkoder vi kan använda för att anpassa vår kommandoprompt.

Nu vill vi skapa en kommandoprompt som ser ut så här:

[jonas@ubuntu (08:06:10) tmp ] >>

Först har vi användarnamnet på inloggad användare (jonas), sedan kommer datorns namn (ubuntu). I parantesen visas hur mycket klockan är (08:06:10) och slutligen kommer vilken katalog vi befinner oss i (tmp). För att skapa en kommandoprompt som ser ut så här skriver vi:

$ PS1="[\u@\h (\t) \W ] >> "

| Styrkod | Förklaring | | ======== | ========== | |\a |ASCII bell (ett kort pip) | |\d |Datumet i veckodag månad datum-format, till exempel: mån dec 25 | |\e |ASCII escape tecknet | |\h |Datornamnet (hostname) fram till den första punkten | |\H |Datornamnet (hostname) | |\j |Antalet processer (jobb) som hanteras av kommandotolken | |\1 |Vilken terminal är vi inloggade på? | |\n |Newline, ny rad | |\r |Carriage return, radbrytning | |\s |Skalets namn | |\t |Klockan i 24-timmarsformat (TT::SS) | |\T |Klockan i 12-timmarsformat (TT::SS) | |@ |Klockan i 12-timmarsformat med am/pm | |\u |Användarnamnet på inloggad användare | |\v |Versionen på Bash (3.1) | |\V |Versionen på Bash med patchnivå (3.1.17) | |\w |Nuvarande katalog (/usr/share) | |\W |Basnamnet på nuvarande katalog (share) | |! |Historynumret på det kommando som kommer att utföras | |# |Nummer som visar hur många kommandon du utfört i det nuvarande skalet. | |$ |Om användaren är UID 0 (root) visas ett #-tecken, annars visas ett $-tecken. | |\nnn |Visar tecknet som motsvaras av det oktala numret nnn. | |\ |Ett backslash-tecken | |[ |Påbörja en sekvens med icke utskrivningsbara tecken. | |] |Avsluta en sekvens av icke utskrivningsbara tecken. |

Vi kan ange färger för vår kommandoprompt, vi måste dock tänka på att se till att escapa (vilket görs med backslash \, som också kallas escapetecknet) färgkoderna korrekt - annars fungerar det inte. Leta upp den färg du vill använda i tabellen för färgkoder här nedanför och skriv den som \[\033[0;32m\] (väljer grön färg), allt som kommer efter det kommer skrivas ut i grönt. För att byta färg igen skriver du den nya färgkoden och sedan vad du vill skriva ut.

Ett exempel:

$ PS1="\[\033[0;32m\]\u\[\033[1;31m\]@\[\033[1;32m\]\h\[\033[34;1m\]\w\[\033[0;37\
m\] >

Vill du få tillbaka din vanliga prompt igen kan du logga ut och in igen, eller skriva:

$ source ~/.bashrc
An icon indicating this blurb contains information

För att göra ändringarna permanenta (dvs de aktiveras varje gång du loggar in) lägger du till dem i filen .bashrc i din hemkatalog.

|Färg |Färgkod |Färg |Färgkod | | ====== | ======= | ========= | ======= | |Svart |0;30 |Mörkgrå |1;30 | |Röd |0;31 |Ljusröd |1;31 |
|Grön |0;32 |Ljusgrön |1;32 |
|Brun |0;33 |Gul |1;33 | |Blå |0;34 |Ljusblå |1;34 | |Lila |0;35 |Ljuslila |1;35 | |Turkos |0;36 |Ljusturkos |1;36 | |Ljusgrå |0;37 |Vitt |1;37 |

An icon indicating this blurb contains information

Förutom miljövariablerna PS1 och PS2 finns det en miljövariabel för PS3 och PS4 också. De används sällan, men finns där. Sök i manualsidan för Bash, man bash, för att se vad de används till.

Reserverade ord

Bash har, precis som andra kommandotolkar, reserverade ord. Ord som används för speciella ändamål av Bash. I Bash kan du använda orden som variabelnamn (det går inte i alla kommandotolkar), men du bör undvika det för att slippa bli förvirrad när fel uppstår.

| ! | case | coproc | do
| done | elif | else | easc
| fi | for | function | if
| in | select | then | until | while | { } | time | [[ ]]

Alias i Bash

Många kommandon använder sig av argument och ibland kommer vi på oss själva att alltid använda samma argument till kommandot. Vore det inte ganska smart att kunna skapa ett nytt kommando som gör att vi slipper skriva in kommandot och argumenten varje gång? Genom att skapa ett alias för ett kommando kan vi anropa kommandot med argument utan att själva behöva skriva argumenten. Ett sådant kommando kan vara ls som vi ofta använder med argumentet -l för att visa en detaljerad lista över filerna i en katalog.

Vi skapar ett alias för att visa en katalog med detaljer för filerna i den (kommandot ls -l). Aliaset döper vi till ll:

$ alias ll='ls -l'

Nu kan vi skriva ll istället för att skriva ls -l varje gång vi vill visa en detaljerad lista över filer i en katalog.

$ ll
totalt 12
-rw-r--r-- 1 jonas users  923 2011-12-27 11:46 pictures.php
-rw-r--r-- 1 jonas users 4454 2011-12-27 11:46 xorg.conf

För att ta bort ett alias använder vi kommandot unalias. För att ta bort aliaset ll som vi precis skapade skriver vi:

$ unalias ll

Om vi vill lista alla alias vi har tillgängliga skriver vi bara alias:

$ alias
alias cd..='cd ..'
alias dir='ls -l'
alias l='ls -alF'
alias la='ls -la'
alias ll='ls -l'

Ett alias kan heta samma sak som kommandot, vi skulle alltså kunna skapa aliaset ls som ger oss kommandot ls med argumentet -l (ls -l):

$ alias ls='ls -l'

Vi kan skapa våra alias direkt på kommandoraden eller spara dem i vår ~/.bash_aliases-fil som finns i vår hemkatalog. Om vi skapar våra alias i .bash_aliases kommer de alltid vara tillgängliga för oss när vi loggar in. För att skapa ett alias i filen .bash_aliases öppnar vi filen med en texteditor och skriver in aliaset i filen, precis som vi gjorde på kommmandoraden ovanför:

alias ls='ls -'

Om vi inte sparar våra alias i filen .bash_aliases kommer alla alias att försvinna när vi loggar ut.

Historik i Bash

Bash håller en historik på vilka kommandon vi använt. Hur många kommandon Bash skall komma ihåg bestäms av miljövariabeln HISTSIZE, vill vi se vad den är satt till kan vi skriva:

$ echo ${HISTSIZE}
1000

Alla kommandon i historiken sparas i filen .bash_history i användarens hemkatalog, om vi inte ändrat miljövariablen HISTFILE som används för att ange vilken fil historiken skall sparas i.

Vi kan söka i historiken med !-tecknet.

$ date
ons dec 27 13:43:04 CET 2011
$ !d
date
ons dec 27 13:43:07 CET 2011

Kommandot !d letar efter det senaste kommandot som använts som börjat med bokstaven d, vi skrev date precis innan så det är date som är senast använt. Om vi fortsätter och testar, kan vi skriva:

$ dir
totalt 12
-rw-r--r-- 1 jonas users  923 2011-12-27 11:46 pictures.php
-rw-r--r-- 1 jonas users 4454 2011-12-27 11:46 xorg.conf
$ !da
date
ons dec 27 13:45:36 CET 2011
$ !di
dir
totalt 12
-rw-r--r-- 1 jonas users  923 2011-12-27 11:46 pictures.php
-rw-r--r-- 1 jonas users 4454 2011-12-27 11:46 xorg.conf

Eftersom vi nu anger !da kommer inte kommandot dir köras igen, istället letar Bash i historiken efter det senaste kommandot som började på da och kör kommandot date. Efter det skriver vi !di för att köra kommandot dir igen.

Vill vi se vilket kommando history kommer köra innan den kör kommandot skriver vi:

$ history -p !d
history -p date
date

I fallet ovanför kommer ett !d att köra kommandot date. Dubbla utropstecken (!!) kör det senaste kommandot igen:

$ date
ons dec 27 13:50:38 CET 2011
$ !!
date
ons dec 27 13:50:39 CET 2011

Om vi vill köra ett kommando som vi körde för n gånger sedan skriver vi !-n:

$ !-10
ls
totalt 12
-rw-r--r-- 1 jonas users 923 2011-12-27 11:46 pictures.php
-rw-r--r-- 1 jonas users 4454 2011-12-27 11:46 xorg.conf

Vill vi se hela historiken skriver vi history. Vill vi se de senaste fem (5) kommandona i historiken skriver vi:

$ history 5
 1218  ls
 1219  dir
 1220  date
 1221  dir
 1222  history 5

Vill vi köra kommandot 1220 (date) igen skriver vi:

$ !1220
date
ons dec 27 13:55:58 CET 2011

Vill vi ta bort ett kommando från historiken använder vi kommandot history -d nummer:

 ...
 1223  date
 1224  history
$ history -d 1223
$ history 5
 1221  dir
 1222  history 5
 1223  history
 1224  history -d 1223
 1225  history 5

Vill vi tömma hela historiken använder vi kommandot history -c:

$ history -c
$ history
 227  history

Flera kommandon på samma rad

Ibland vill vi köra flera kommandon på en och samma rad. Varför sitta och vänta på att ett kommando skall köras klart innan vi kan skriva nästa? Det finns två sätt att göra detta, antagligen med ett semikolon (;) eller med två och-tecken (&&). Skillnaden på dem är att && inte kör nästa kommando om föregående inte lyckades.

Detta visas bäst med ett exempel:

$ date ; date <.>
ons dec 27 14:36:06 CET 2011
ons dec 27 14:36:06 CET 2011
$ dat ; date <.>
-bash: dat: command not found
ons dec 27 14:36:08 CET 2011
$ dat && date <.>
-bash: dat: command not found

Första gången vi kör kommandot lyckas det båda gångerna. Andra gången stavar vi fel på date (vi skriver dat) och får ett felmeddelande och det andra kommandot (date) körs. Den sista gången gör vi samma misstag (stavar fel på date) och får ett felmeddelande.

Det andra date-kommandot körs inte, eftersom vi använder &&. Om vi istället vill köra ett annat kommando om det första inte lyckas (motsatsen till &&) kan vi använda dubbla pipe-tecken (||):

$ date || echo "ett datum visades inte" <.>
ons dec 27 14:43:14 CET 2011
$ dat || echo "ett datum visades inte" <.>
-bash: dat: command not found
ett datum visades inte

Eftersom date fungerar första gången kommer vi inte skriva ut texten som kommer efter. I det andra försöket skriver vi date utan e (dat) och det blir fel, därför skrivs texten ut.

I de exempel vi har har här ovanför använder vi två kommandon, vi kan naturligtvis använda flera kommandon. Som exempel kan vi konfigurera och kompilera ett källkodspaket direkt från en kommandorad:

$ ./configure && make && make install

Här är det viktigt att vi använder &&, vi vill ju inte kompilera paketet om det inte gick att konfigurera och än mindre vill vi installera ett sådant paket i vårt system.

Ibland blir raderna med kommandon vi skriver långa, då kan vi använda backslash (\-tecknet) för att fortsätta mata in kommandon på nästa rad innan vi kör alltihopa:

$ echo "Nu skall vi skriva ut tid och datum."; \
> date; \
> echo "Det gick ju bra."
Nu skall vi skriva ut tid och datum.
ons dec 27 14:52:44 CET 2011
Det gick ju bra.

Samma sak skulle kunna skrivas som:

$ echo "Nu skall vi skriva ut tid och datum."; date; echo "Det gick ju bra."

Omdirigering och rör

Innan vi börjar med omdirigeringar måste vi ha klart för oss att Bash arbetar med tre olika standarder för in- och utmatning av information:

  • Standard in (stdin, 0)
  • Standard out (stdout, 1)
  • Standard error (stderr, 2)

Siffrorna 0, 1 och 2 kommer vi snart tillbaka till.

Bash använder som standard Standard Out (stdout) för att skicka utmatning från kommandon. Ibland vill vi ändra detta så utmatningen lagras i en fil. Säg att vi visar innehållet i en katalog och vi vill ha den listan till en textfil som vi kan arbeta med. Vi skriver:

$ ls -l 1> ls.txt

Här listar vi katalogen med kommandot ls -l, men utskriften kommer inte visas på skärmen utan skickas vidare till en fil som vi kallar ls.txt. Vi skulle kunna skriva ls -l > ls.txt också, eftersom Bash då tolkar > som 1>. Om vi nu istället vill lista en katalog som inte finns kommer vi få ett felmeddelande:

$ ls -l /tmp/a
ls: /tmp/a: Filen eller katalogen finns inte

Om vi nu skickar stdout till filen ls.txt kommer filen ls.txt vara tom, eftersom katalogen /tmp/a/ inte finns kan vi inte lista innehållet i katalogen. Vi kommer få ett felmeddelande som ser ut så här:

$ ls -l /tmp/a 1> ls.txt
ls: /tmp/a: Filen eller katalogen finns inte
$ cat ls.txt
$

Felmeddelanden från kommandon skickas till stderr och vi kan fånga upp stderr på samma sätt som vi gjorde med stdout. Istället för att använda 1 (som vi gjorde för stdout) använder vi 2 (för stderr). Vi tittar på ett exempel där vi skickar alla felmeddelanden till en fil som vi kallar ls.fel.txt:

$ ls -l /tmp/a 1> ls.txt 2> ls.fel.txt
$ cat ls.txt
$ cat ls.fel.txt
ls: /tmp/a: Filen eller katalogen finns inte
$

Nu fångar vi alltså upp stdout (1) till filen ls.txt och stderr (2) till filen ls.fel.txt.

Nu tittar vi på hur vi kan dirigera om stdin (för input). Vi börjar med att skapa en fil, som vi döper till namn.txt, med följande innehåll:

anette
therese
madelene
ann-kristin
emma
jessica
samantha
magdalena
hanna
anna
susanne
maria
malin

När vi matat in alla namn sparar vi filen och avslutar vår textredigerare. Nu använder vi kommandot cat för att visa innehållet av filen:

$ cat namn.txt

Inget konstigt händer, filens innehåll skrivs ut som vi skrev den i textredigeraren. Låt oss nu sortera filen i alfabetisk ordning:

$ sort < namn.txt

Namnen skrivs ut i alfabetisk ordning. Här använder vi omdirigeraren för stdin, <, för att läsa innehållet i filen namn.txt och skicka innehållet till kommandot sort. Eftersom kommandot sort vill skicka sin sorterade data till stdout hamnar listan på skärmen, om vi vill spara den sorterade listan i en ny fil dirigerar vi om stdout till en fil så här:

$ sort < namn.txt > namn.sorterad.txt

Ingenting händer på skärmen, men tittar vi i katalogen så skapades en fil: namn.sorterad.txt. Vi använder kommandot cat för att visa innehållet:

$ cat namn.sorterad.txt

Den sorterade listan skickades till filen namn.sorterad.txt istället för skärmen.

När vi använder > (ett tecken) skriver vi över innehållet i filen varje gång, ibland vill vi istället lägga till saker i slutet av en befintlig fil. Då kan vi inte använda >. För att lägga till innehåll i en befintlig fil, utan att skriva över filen, använder vi två stycken >> istället. >> gör att det vi dirigerar om till filen läggs till i slutet av filen:

$ sort < namn.txt > namn.sorterad.txt
$ cat namn.txt >> namn.sorterad.txt
$ cat namn.sorterad.txt

Vi kan använda rör (eng. pipe) för att skicka data vidare till andra kommandon. Du kan ha sett exempel på kommandon som ls -l | less någon gång. ls -l visar en detaljerad lista över vad en katalog innehåller och den listan kan bli ganska lång om katalogen innehåller många filer, så genom att skicka utskriften från kommandot ls till kommandot less, som visar en skärmsida i taget, får vi en bättre översikt av listan. Det är tecknet | (pipe) som genomför det magiska här. Pipe fångar upp stdout från kommandot ls och skickar texten vidare till stdin för kommanot less. Nu provar vi det här i verkligheten:

$ ls -l | less

Varför kan vi inte använda < istället? < fungerar bara för filer, inte på kommandons utmatningar. Så för att fånga upp stdout från ett kommando och skicka det till ett annat kommandos stdin använder vi |-tecknet, för att hämta innehållet från en fil och skicka det vidare till ett kommandos stdin använder vi <-tecknet. Vi skulle kunna skriva så här, om vi vill slippa använda pipe:

$ ls -l > filer.txt && less filer.txt

Visa en detaljerad lista över alla filer i katalogen och spara listan i filen filer.txt, visa sedan filen filer.txt med kommandot less. Att använda ett rör för ändamålet är betydligt effektivare än att gå via en fil.

Vi kan naturligtvis använda flera rör på en och samma rad om vi behöver det. Om vi vill visa alla filer i en katalog, sortera listan baklänges och visa den med kommandot less skriver vi:

$ ls -1 | sort -r | less

Skulle vi vilja ha den baklängessorterade listan till en fil istället för skickad till kommandot less skulle vi kunna skriva så här:

$ ls -1 | sort -r > sorterad.txt

Det går alltså att kombinera flera av < , > och | på en och samma kommandorad.

Jokertecken

När vi arbetar med filnamn och katalognamn, till exempel med kommandot ls, kan vi använda jokertecken. Det finns tre jokertecken: +*+, ? och [].

Det första jokertecknet är +*+ som betyder inget eller flera, vi använder +*+ när vi vet en del av, men inte hela, filnamnet. Vet vi början, något i mitten eller slutet av filnamnet? Vi tittar på ett exempel:

$ cd /usr/bin
$ ls xz*
xz xzcat xzcomp xzdiff xzegrep xzfgrep xzless xzmore
$ ls *xz
unxz xz
$ ls *xz*
unxz xz xzcat xzcomp xzdiff xzegrep xzfgrep xzgrep xzless xzmore

Först listade vi filnamn som börjar med bokstäverna xz (+xz*+), sedan listade vi filnamn som slutar med bokstäverna xz (+*xz+) och slutligen listade vi filnamn som innehåller bokstäverna xz (+*xz*+).

Det andra jokertecknet är ? som betyder exakt ett okänt tecken. Vill vi söka efter flera okända tecken får vi lägga till ett ?-tecken för varje okänt. är det tre okända tecken blir det till exempel ???.

$ cd /usr/bin
$ ls ???e
file free line nice time
$ ls ?i?e
file line nice time

I det första exemplet ovan visar vi alla filnamn som är fyra tecken långa, där de tre första är okända och det fjärde tecknet är ett e (???e). I det andra exemplet visar vi alla filnamn som är fyra tecken långa, där det andra tecknet är bokstaven i och det fjärde tecknet är bokstaven e (?i?e).

Det tredje jokertecknet är egentligen två tecken: +[+ och +]+. +[ ]+ används för att ange en uppsättning tecken. Om vi vill lista alla filer som börjar med a, b eller c i katalogen kan vi skiva +ls a* b* c*+. Det är inte speciellt snyggt, men det fungerar. Prova att skriva +ls [abc]*+ istället, det gör samma sak. Om uttrycket mellan +[+ och +]+ börjar med ett utropstecken (!) eller cirkumflex (^) betyder det allt som inte matchar uttrycket. Vi kan till exempel lista alla filer utom de som börjar med bokstaven a, b eller c. Några exempel på hur vi använder detta:

$ ls a* b* c*
$ ls [abc]*
$ ls [!abc]*

Vi kan ange områden i våra sökningar istället för att ange ett specikt tecken. Om vi till exempel vill lista alla filer som börjar med någon av bokstäverna a till c skriver vi +ls [a-c]*+. För att ange stora bokstäver skriver vi istället +ls [A-C]*+ och för siffror skriver vi +ls [0-9]*+. För att lista alla filer som börjar med en liten eller en stor bokstav skriver vi +ls [A-Za-z]*+.

$ ls [a-c]*
$ ls [A-C]*
$ ls [0-9]*
$ ls [A-Za-z]*

Hur gör vi för att visa de dolda filerna med hjälp av jokertecken? Vi måste ange punkten (.) först, sedan används jokertecken precis som med vanliga filer:

$ ls .g*
$ ls .[a-z]*

Skriva skript i Bash

Innan vi börjar skriva vårt första skript i Bash skall vi gå igenom ett par grundläggande saker. Skript kan skivas i många olika språk, nu använder vi Bash. Vi skulle lika gärna kunna skriva våra skript i csh, zsh, ksh eller någon annan kommandotolk. Vi kan skriva skript i Bash även om vi använder zsh som kommandotolk, och tvärtom. Skript är helt vanliga textfiler som innehåller information till datorn vad vi vill att den skall göra. Lite grann som ett recept: ta en deciliter mjöl, blanda ut det med en tesked salt, värm upp fyra deciliter mjölk och rör ner mjölet i det..

Trots att Linux inte använder filändelser för filer i filsystemet använder vi traditionellt filändelsen .sh för bashskript. Du har säkert redan listat ut att .sh är en förkortning för det engelska ordet shell. Den första raden i filen vi skapar för vårt skript måste ange vilken kommandotolk vi vill använda, detta eftersom användaren kanske kör skriptet under en helt annan kommandotolk som har ett annat programspråk.

För att ange att det är Bash vi vill använda skriver vi: +#!/usr/bin/env bash+ på den första raden i vår fil. Ibland stöter vi på +#!/bin/sh+ i skriptfiler. Tittar vi på filen /bin/sh med kommandot ls -l /bin/sh i filsystemet upptäcker vi att det är en länk till /bin/dash. Inte alls samma kommandotolk som vi vill använda. Ganska ofta kommer vi stöta på +#!/bin/bash+ på den första raden i bashskript också, det är inte helt fel men vi bör undvika det eftersom bash kan ligga någon annan stans i filsystemet hos den som kör vårt skript. Vi håller oss därför till +#!/usr/bin/env bash+ i våra skript.

Vi tittar på vårt skript så här långt:

#!/usr/bin/env bash

Staket-tecknet (#) används normalt för kommentarer, förutom just för denna rad där den anger vilken kommandotolk vi skall starta för att köra skriptet. Nu lägger vi till en rad i vårt skript så det faktiskt kan göra något:

#!/usr/bin/env bash
echo 'Hej Bash!'

Kommandot echo innebär att Bash skall skriva ut något, i det här fallet texten Hej Bash!. Vi avslutar vårt skript på rätt sätt, det vill säga anropar kommandot exit och anger ett exitvärde:

#!/usr/bin/env bash
echo 'Hej Bash!'
exit 0

Ett skript som lyckats köra utan problem skall returnera exitvärdet noll (0), alla andra värden än noll (0) är felmeddelanden. Vi kommer tillbaka till detta med returvärden och exitvärden lite senare.

Nu är vårt skript färdigt, vi sparar filen och skriver chmod +x hello.sh (om vi döpte skriptet till hello.sh). Detta gör vi för att skriptet skall gå att köra utan att först anropa bash.

$ chmod +x hello.sh
$ ./hello.sh
Hej Bash!

Om vi inte sätter executeflaggan (x) med chmod kan vi ändå köra vårt skript, om vi startar det med bash, så här:

$ bash hello.sh
Hej Bash!

Nu skall vi titta på lite miljövariabler i Bash. Kommandot printenv visar vilka variabler som är satta i systemet och vad de innehåller:

$ printenv
ACLOCAL_FLAGS=-I /opt/gnome/share/aclocal
COLORTER=1
CPU=i686
...
$

Det finns en miljövariabel som heter USER, den innehåller användarnamnet för den användare som är inloggad. Vi visar variabeln genom att skriva:

$ echo $USER
jonas

Innan vi skall använda variabeln i vårt skript skall vi ta och titta på skillnaden mellan tecknen " och ' i Bash:

$ echo "$USER"
jonas
$ echo '$USER'
$USER

Om vi använder citattecken (") kommer variabeln expanderas, det vill säga - innehållet i den kommer att visas. Använder vi däremot apostrof-tecken (') kommer variabeln inte expanderas. Det är skillnaden mellan de två.

Nu öppnar vi vårt skript, hello.sh, igen och ändrar lite i det:

#!/usr/bin/env bash
echo "Hej $USER"
exit 0

Nu sparar vi filen och kör skriptet. Nu kommer skriptet att skriva ut Hej Jonas istället (om vårt användarnamn är jonas).

En viktig del av de skript vi skriver är kommentering. Kommentarer i skripten kommer hjälpa oss när vi kommer tillbaka till skriptet tre, eller sex månader sedan och inte längre minns hur vi tänkte när vi skrev det. Att kommentera vårt korta skript Hej Bash verkar kanske lite onödigt, men vi tittar ändå på hur vi kan skapa kommentarer i våra skript:

#!/usr/bin/env bash
# Ett skript som skriver ut Hej och användarens namn
echo "Hej $USER" # Skriver ut Hej och användarens namn
exit 0 # Avslutar programmet

Nu sparar vi filen och kör skriptet. Det är ingen synlig skillnad från innan, det skriver fortfarande ut Hej jonas på skärmen (om vårt användarnamn är jonas). Med hjälp av +#+-tecknet kan vi alltså skriva kommentarer i våra skript. Allt som kommer efter +#+-tecknet, fram till radbrytningen, kommer att tolkas som en kommentar och ignoreras av Bash. Skriver vi ett +#+-tecken i början av raden ignoreras hela raden. Skriver vi ett +#+-tecken någon annanstans på raden ignoreras allt från tecknet fram till radbrytningen.

Vi bör alltid skriva bra kommentarer i våra skript. När vi tittar på skripten ett halvår senare kommer vi tacka oss själva för att vi kommenterade skripten. Hur väl insatta vi än känner oss i våra skript idag, kommer situtionen se annorlunda ut om ett par månader då vi sitter och funderar på varför vi har med det där konstiga i skriptet.

Variabler

För att kunna skapa användbara skript måste vi ha koll på variabler. En variabel är en plats i datorns minne som innehåller ett värde. Variabler deklareras innan de kan användas. I Bash behöver vi inte deklarera variabeln innan vi använder den, utan kan tilldela den ett värde direkt:

#!/usr/bin/env bash
NAMN="jonas"
echo ${NAMN}
exit 0

I exemplet ovanför deklarerar vi variabeln NAMN och tilldelar den värdet jonas. Variabelnamnen i Bash skrivs traditionellt med stora bokstäver för att skilja dem från kommandon i skriptet. Variabler deklareras med sitt namn (NAMN i exemplet ovanför) men refereras till som ${variabelnamn} (som ${NAMN} i exemplet ovanför).

Vi kan inte använda mellanslag mellan variabelnamnet, likamed-tecknet (=) och värdet vi vill tilldela, några exempel:

$ VAR = "jonas"
-bash: VAR: command not found
$ VAR= "jonas"
-bash: VAR: command not found
$ VAR="jonas"
$ echo ${VAR}
jonas
$

Det är bara det sista kommandot i exemplet ovanför, VAR="jonas", som fungerar - de andra skapar felmeddelanden. Skall vi skapa riktigt fina skript bör vi använda declare för att deklarera våra variabler. declare har dessutom några flaggor som vi har nytta av ibland:

  • -a för array (listor)
  • -i för integer (heltal)
  • -p för print (visa variabelns värde)
  • -r för read-only (variabeln går inte att ändra)
  • -x för export (variabeln exporteras)

Listor (array) och heltal (integer) kommer vi använda senare. Vi tittar på print, read-only och export så länge:

$ declare NAMN="jonas"
$ declare -p NAMN
jonas
$ declare -r NAMN="jonas"
$ declare -x NAMN="jonas"

Print (-p) visar variablens värde, det vi lagrat i variablen. Notera att vi inte använder ${NAMN} här. Nu tittar vi på read-only:

$ declare -r OS="Linux"
$ echo ${OS}
Linux
$ OS="Windows"
-bash: OS: readonly variable

I exemplet ovanför skapar vi en read-only variabel som vi döper till OS, värdet för OS sätter vi till Linux. Vi visar variabelns värde med hjälp av kommandot echo. Sedan försöker vi ändra värdet till Windows, vilket Bash sätter stopp för. Variabler som inte går att ändra (read-only) kallas för konstanta variabler eller bara kort: konstant.

Export (-x) använder vi för att göra våra variabler tillgängliga utanför det skript som vi skapar dem i. Nu skapar vi skriptet export.sh:

#!/usr/bin/env bash
declare DIST="Ubuntu"
echo "export.sh: ${DIST}"
bash import.sh
exit 0

Vi sparar skriptet och skapar ett nytt skript som vi döper till import.sh:

#!/usr/bin/env bash
echo "import.sh: Aha, du kommer med en dist som heter: ${DIST} ?"
exit 0

Lägg märke till att vi inte deklarerar variabeln DIST i filen import.sh. Det skall vi inte göra. Vi anropar också skriptet import.sh från export.sh genom att skriva bash import.sh. Nu kör vi skriptet export.sh:

$ chmod +x export.sh
$ ./export.sh
export.sh: Ubuntu
import.sh: Aha, du kommer med en dist som heter: ?
$

Det fungerade ju ganska bra. export.sh startas och anropar import.sh, det ser vi på utskriften. Däremot visas inte värdet i variabeln DIST i skriptet import.sh, så vi ändrar i export.sh:

#!/usr/bin/env bash
declare -x DIST="Ubuntu"
echo "export.sh: ${DIST}"
bash import.sh
exit 0

Det vi gör är att vi exporterar variabeln DIST med hjälp av flaggan -x till declare. Exporten sker till alla skal som startas från det vi kör just nu, när vi startar import.sh med bash-kommandot i export.sh kommer variabeln alltså att exporteras till Bash som startar import.sh. Nu fungerar vårt skript som vi tänkt.

$ ./export.sh
export.sh: Ubuntu
import.sh: Aha, du kommer med en dist som heter: Ubuntu ?
$

Istället för att använda declare -x kan vi istället skriva export. declare -x DIST="Ubuntu" skrivs så här med export: export DIST="Ubuntu".

Ibland kan det vara bra att ta bort sina variabler också, det gör vi med kommandot unset . unset är inget krångligt alls utan fungerar så att man skriver unset VARIABELNAMN. Vi skapar ett skript för att visa hur det fungerar:

#!/usr/bin/env bash
declare DIST="Ubuntu"
echo "Din dist är ${DIST}"
unset DIST
echo "Vart tog ${DIST} vägen?"
exit 0

Nu sparar vi filen och kör den för att se vad som händer när vi använder unset.

In- och utmatning

För att användaren skall kunna mata in svar och på så sätt påverka hur skriptet kommer köras behöver vi använda bashs inbyggda kommando read. Med read kan vi vänta på att användaren skall svara på en fråga innan vi fortsätter köra skriptet. Vi skriver ett enkelt skript (inmatning.sh):

#!/usr/bin/env bash
echo "Vad heter du?"
read USERNAME
echo "Hej ${USERNAME}"
exit 0

Vi sparar skriptet som inmatning.sh och sätter körflaggan (chmod +x inmatning.sh) på filen och testar skriptet. Det användaren matar in hamnar i variabeln USERNAME, vi använder den variabeln för att skriva ut Hej ... på raden efter inmatningen. Lade du märke till att du fick svara på raden under frågan? Det ser inte så bra ut, helst skulle vi vilja att användaren svarade direkt efter frågan. Det löser vi med en flagga till echo, nämligen -n:

#!/usr/bin/env bash
echo -n "Vad heter du? "
read USERNAME
echo "Hej ${USERNAME}"
exit 0

Flaggan -n till echo innebär att echo inte skapar en ny rad efter att den skrivit ut texten. Varför inte använda kommandot read för detta istället? Vi tittar på flaggan -p för kommandot read:

#!/usr/bin/env bash
read -p "Vad heter du? " USERNAME
echo "Hej ${USERNAME}"
exit 0

Se där, det fungerar ju. Flaggan -p (för prompt) till kommandot read innebär att read skall skriva ut något innan användaren matar in sitt svar. Variabeln som skall innehålla svaret skriver vi sist på raden med read -p.

Hur gör vi om användaren inte svarar inom en tidsperiod, säg tre sekunder? Skall skriptet stå och vänta i all evighet då, eller skall vi gå vidare ändå med standardvärden? Naturligtvis har read ett sådant val också, -t. Tiden read skall vänta mäts i sekunder. Vi tittar på ett exempel:

#!/usr/bin/env bash
echo -n "Vad heter du? "
read -t 3 USERNAME
echo "Hej ${USERNAME}"
exit 0

Nu kommer skriptet vänta i tre (3) sekunder innan det går vidare. Här är det lämpligt att lägga in ett standardvärde, om användaren inte matar in något:

#!/usr/bin/env bash
echo -n "Vad heter du? "
read -t 3 USERNAME
USERNAME=${USERNAME:="Hemlig"}
echo "Hej ${USERNAME}"
exit 0

Om användaren inte matar in sitt namn inom tre sekunder kommer variabeln USERNAME sättas till värdet Hemlig.

Slutligen skall vi titta på hur vi hanterar för långa inmatningar. Kommandot read kan begränsa hur många tecken vi skall läsa in från kommandoraden med hjälp av flaggan -n. Vi vill inte ha längre namn än tio (10) tecken så vi begränsar användaren till det:

#!/usr/bin/env bash
echo -n "Mata in max tio tecken: "
read -n 10 TECKEN
echo "Dina tecken är: ${TECKEN}"
exit 0

När användaren har matat in tio tecken, eller tryckt på RETUR, avbryts inmatningen och Dina tecken är … skrivs ut. Notera att det blir skönhetsfel om användaren matat in tio tecken, Dina tecken är … skrivs ut direkt efter det inmatade. En lösning på detta är att lägga till ett extra echo på raden mellan read och echo i skriptet.

Om vi inte anger ett variabelnamn till read kommer svaret användaren matar in att hamna i variabeln REPLY:

$ read
jonas
$ echo $REPLY
jonas
$

Utmatningen sker med antagligen kommandot echo, som vi använt hittils, eller med kommandot printf som har fler funktioner för formatterad utskrift.

Några av specialtecknen för utmatning i printf:

|Specialtecken | Förklaring | | ================ | ============ | | \b | Backspace | | \n | Newline, skapa en ny rad | | \t | Tabb | | \\ | Ett backslashtecken | | \0n | Visa ASCII-tecknet med koden n |

Vi testar printf på kommandoraden:

$ MENING=Jag tycker Bash är roligt
$ printf "%s\n" $MENING
Jag
tycker
Bash
är
roligt
$

Det där blev inte riktigt vad vi tänkte oss. Eftersom vi inte omsluter värdet vi tilldelar variabeln MENING med citattecken blir utskriften felaktig, vi provar igen:

$ MENING="Jag tycker Bash är roligt"
$ printf "%s\n" $MENING
Jag tycker Bash är roligt
$

Nu fungerade det mycket bättre. %s anger att vi vill skriva ut en sträng, strängen hittar printf i variabeln vi anger efter printf . Specialtecknet \n skapar en radbrytning.

Vi tittar på hur vi kan hantera tal med %d:

$ BRED=abc
$ printf "Priset på bredband sänktes med %d%%.\n" $BRED
-bash: printf: abc: invalid number
Priset på bredband sänktes med 0%.
$

Med %d kan vi inte skriva ut bokstäver, det är bara hela tal som fungerar. Vi skriver BRED=25 och försöker igen:

$ BRED=25
$ printf "Priset på bredband sänktes med %d%%.\n" $BRED
Priset på bredband sänktes med 25%.
$

De två %%-tecknen som kommer efter %d är till för att skriva ut ett procenttecken (%).

Räkna med bash - de fyra räknesätten

I det här avsnittet skall vi titta på hur vi kan räkna med de fyra räknesätten: addition, subtraktion, multiplikation och division i Bash.

Addition

Addition av två tal beräknas med plustecknet (+). Vi kan använda kommandot let för detta:

$ let SUMMA=10+10
$ echo $SUMMA
20

Vi kan också använda dubbla paranteser på det här viset:

$ SUMMA=$(( 10 + 10 ))
$ echo $SUMMA
20

Subtraktion

Subtraktion av två tal beräknas med minustecknet (-), vi använder kommandot let för detta:

$ let DIFF=100-10
$ echo $DIFF
90

Eller med dubbla paranteser:

$ DIFF=$(( 100 - 10 ))
$ echo $DIFF
90

Multiplikation

Multiplikation av två tal beräknas med *, vi använder kommandot let för detta:

$ let PRODUKT=100*10
$ echo $PRODUKT
1000

Eller med dubbla paranteser:

$ PRODUKT=$(( 100 * 10 ))
$ echo $PRODUKT
1000

Division och modulus

Division med heltal skapar en kvot och en rest. Eftersom vi bara kan räkna med heltal kan vi inte inte få fram decimalerna i våra uträkningar med division. Vi tar 5/2 som exempel, kvoten kommer bli 2.5, men eftersom vi enbart kan räkna med heltal kommer kvoten från Bash bli 2. 2 * 2 = 4 kvar har vi alltså 1. För att få fram resten (1) använder vi oss av modulus som är ett procenttecken (%): 5%2 ger oss 1. Resten av heltalsdivisionen 5/2 är alltså 1.

Vi använder kommandot let för att få fram kvoten av 100 dividerat med 10:

$ let KVOT=100/10
$ echo $KVOT
10

Vi kan naturligtvis göra samma sak med dubbla paranteser:

$ KVOT=$(( 100 / 10 ))
$ echo $KVOT
10

Hur gör vi om divisionen inte går jämnt ut? Vi måste få fram resten, vilken vi får med hjälp av modulus (%):

$ let KVOT=100/13
$ echo $KVOT
7
$ let REST=100%13
$ echo $REST
9

Vad hände här? 100 går att dela med 13 sju gånger ( 13 * 7 = 91 ). Kvar blir 9 (91 +9 = 100).

Det finns flera olika sätt att räkna med decimala tal (flyttal) i Bash, vi skall titta på ett sätt vi kan använda. Kommandot bc.

Vi börjar med att räkna ut 2.5*5 med decimaler:

$ bc <<< "2.5*5"
12.5

Detta fungerar med addition, subtraktion och multiplikation. Även division fungerar, men vi måste använda scale för att ange noggrannheten (precisionen) på antalet decimaler. Vi tittar på ett exempel:

$ bc <<< "5/2"
2

Fem delat med två blir 2.5 och inte 2 som i exemplet ovanför. För att visa decimalerna använder vi scale:

$ bc <<< "scale=2;5/2"
2.50

Genom att sätta scale=2 före uttrycket vi vill beräkna säger vi till bc att vi vill visa talet med två decimalers noggrannhet. scale=1 ger oss en decimal, scale=4 ger oss fyra decimaler och så vidare.

Ofta vill vi lagra resultatet av en beräkning i en variabel för att kunna använda den senare i våra skript. För att göra detta använder vi $( ) på detta viset:

$ Tal=$( bc <<< "2.5*5" )
$ echo ${Tal}
2.50

För division med scale ser det ut så här:

$ Tal=$( bc <<< "scale=2;5/2" )
$ echo ${Tal}
2.50

Genom att ange flaggan -l till kommandot bc får vi maximal precision på decimalerna, ofta är det dock bättre att specifiera hur stor noggrannhet vi vill ha med scale:

$ bc -l <<< "5/2"
2.50000000000000000000

Villkor

Villkor använder vi för att skapa alternativ för skriptet, vad händer till exempel om programmet vi vill använda i skriptet inte finns?

Ett enkelt villkor skrivs så här:

if villkor ; then
    vad skall vi göra om villkoret uppfylls?
fi

Villkor för filer

Vi använder ofta villkor för att testa filer i början av skript. Finns filen vi vill läsa? Om den inte finns kommer skriptet inte fungera och bör avbrytas snarast. Kan vi skriva till en fil? om inte verkar det dumt att ens försöka.

| Villkor | Förklaring | | ======== | ========= | | -b | Är filen en block device? | | -c | Är filen en character device? | | -d | Är filen en katalog (directory)? | | -e | Finns filen (exists)? | | -h och -L | Är filen en länk (link)? | | -r | Är filen läsbar för vårt skript? | | -s | Finns filen och har den något innehåll? | | -w | Är filen skrivbar för vårt skript? | | -x | Är filen körbar för vårt skript? | | fil1 -nt fil2 | Är fil1 nyare än fil2 (newer than)? | | fil1 -ot fil2 | Är fil1 äldre än fil2 (older than)? |

Vi skriver ett exempel som visar hur det fungerar:

if [ -r ./hemliginfo.dat ] ; then
    echo "Vi kan läsa filen."
fi

Ovanstående exempel testar om filen hemliginfo.dat i katalogen är läsbar för vårt skript. Om den är det kommer texten Vi kan läsa filen. att skrivas ut. Nu är det kanske inte alltid det här vi vill testa, utan snarare vill vi testa om en fil går att läsa, om inte skall skriptet avbrytas. Vi använder utropstecknet (!) för att göra om ett sant villkor till ett falskt. Ett villkor som uppfylls, till exempel att filen finns är sant. Ett villkor som inte uppfylls är falskt. Ett villkorsuttryck kan alltså bara resultera i en av två värden: sant eller falskt.

Ofta vill vi se om ett villkor inte är uppfyllt, till exempel om filen inte finns. I exemplet ovanför testade vi om filen hemliginfo.dat var läsbar för skriptet, om filen fanns skulle vi skriva ut Vi kan läsa filen. Istället för att enbart skriva ut texten med kommandot echo skulle vi kunna skriva hela vårt skipt mellan raden med if och raden med fi. Det här skulle bli ganska opraktiskt att underhålla, speciellt när vi egentligen bara ville se om filen var läsbar och om den inte var det skulle vi avbryta skriptet.

Vi ändrar skriptet så det ser ut så här:

if [ ! -r ./hemliginfo.dat ] ; then
    echo "Vi kan läsa filen."
fi

Om filen finns och är läsbar för vårt skript kommer if-satsen resultera i att villkoret är sant. Genom att sätta ett utropstecken innan kommer vi göra om sant till falskt. Eftersom villkoret nu blir falskt kommer inte echo köras. Om filen istället inte är läsbar för oss kommer villkoret bli falskt, och med ett utropstecken kommer falskt bli sant och echo kommer köras.

Vi tittar på en mer användbar kodsnutt:

if [ ! -e ./hemliginfo.dat ] ; then
    echo "FEL: Filen hemliginfo.dat finns inte. Avbryter."
    exit 1
fi

Här använder vi -e för att titta om filen hemliginfo.dat finns. Om den inte finns skall skriptet avslutas. Ett mycket bra sätt att felhantera skriptet om vårt skript är beroende av att en fil finns.

Villkor med strängar

I denna del skall vi titta på hur vi kan hantera villkor för strängar.

| Villkor | Förklaring | | ======= | ========== | | -z | Är strängen tom? | | -n | Är strängen inte tom? | | str1 = str2 | Är str1 lika som str2? |
| str1 != str2 | Är str1 inte lika som str2? | | str1 < str2 | Är str1 mindre än str2? | | str1 > str2 | Är str1 större än str1? |

Vi börjar med att titta på -z och -n:

#!/usr/bin/env bash
STR="Ubuntu"
if [ -z "${STR}" ] ; then
    echo "Strängen är tom."
fi
        
if [ -n "${STR}" ] ; then
    echo "Strängen är inte tom."
fi
    
exit 0

Skriv ovanstående exempel som ett skript och kör skriptet. ändra sedan så det står STR="" och kör skriptet igen. Att veta om en variabel är tom innan vi börjar använda den är mycket användbart.

Vi tittar nu på hur vi kan se om två variabler är lika:

#!/usr/bin/env bash
STR1="Ubuntu"
STR2="Linux"
if [ "${STR1}" = "${STR2}" ] ; then
    echo "${STR1} är likadan som ${STR2}"
fi
        
if [ "${STR1}" != "${STR2}" ] ; then
    echo "${STR1} är inte samma som ${STR2}"
fi
        
exit 0

Varje tecken vi kan skriva i en dator finns i en teckenkodningstabell som kallas ASCII (American Standard Code for Information Interchange). ASCII är en sju bitarstabell som rymmer de tecken som behövdes i USA, vilket gör att våra svenska tecken som Å, Ä och Ö inte finns med i tabellen. Därför utökades ASCII till olika ISO-standarder som hade åtta bitar för varje tecken. För att skriva västeuropeiska tecken (som å, ä och ö) användes ISO-8859-1 teckenkodning. När valutan euro introducerades i Europa utökades ISO-8859-1 med tecknet för euro och cent. Nu förtiden använder vi teckenkodningstabellen UTF-8 som sägs täcka alla behov av tecken i hela världen.

Tecknet 0 har det numeriska värdet 48 i ASCII-tabellen, tecknet 9 har det numeriska värdet 57. Stora A har det numeriska värdet 65. Lilla a har det numeriska värdet 97.

Att titta på om en sträng är större än eller mindre än fungerar inte rikigt som vi tänker oss att det borde fungera. Vad större än och mindre än gör är att jämföra tecknens ASCII-koder (se ovan angående ASCII). Om vi till exempel jämför orden sommar och vår skulle vi kunna tänka oss att sommar skulle vara större än vår, eftersom sommar är längre än vår. Så är inte fallet i Bash, jämförelsen säger att vår är större än sommar, eftersom ASCII-koden för bokstaven v är 118 och för bokstaven s är ASCII-koden 115. Villkoret skall alltså tolkas som 118 är större än 115:

#!/usr/bin/env bash

STR1="sommar"
STR2="vår"

if [[ "${STR1}" < "${STR2}" ]] ; then
    echo "${STR1} är mindre än ${STR2}"
fi
        
if [[ "${STR1}" > "${STR2}" ]] ; then
    echo "${STR1} är större än ${STR2}"
fi
        
exit 0

Vill vi se vilken sträng som är störst (längst) får vi räkna ut längden på en sträng med # som i ${#STR1}. Vi provar:

$ VAR="jonas"
$ echo ${#VAR}
5

I exemplet ovanför ser vi att variabeln VAR innehåller fem (5) tecken (jonas). Vi kan använda detta för att se vilken sträng som är längst.

#!/usr/bin/env bash
        
STR1="linux"
STR2="windows"
        
if [[ ${#STR1} -gt ${#STR2} ]] ; then
    echo "${STR1} är längre än ${STR2}"
fi
        
if [[ ${#STR1} -lt ${#STR2} ]] ; then
    echo "${STR1} är kortare än ${STR2}"
fi
        
exit 0

Eftersom linux är fem tecken och windows är sju tecken långt kommer vi i ovanstående skript få utmatningen linux är kortare än windows. Vi kan göra skriptet kortare med else:

#!/usr/bin/env bash

STR1="linux"
STR2="windows"
    
if [[ ${#STR1} -gt ${#STR2} ]] ; then
    echo "${STR1} är längre än ${STR2}"
else
    echo "${STR1} är kortare än ${STR2}"
fi
        
exit 0

Om STR1 inte är längre än STR2 kan vi utgå från att STR1 är kortare än STR2. Notera dock att vi inte tar hänsyn till om strängarna är lika långa i exemplen ovanför.

Matematiska villkor

Vi kan jämföra tal med Bash också.

| Villkor | Förklaring | | ======= | ========== | | nr1 -eq nr2 | Är nr1 och nr2 lika (equal)? | | nr1 -ne nr2 | Är nr1 och nr2 olika (not equal)? | | nr1 -lt nr2 | Är nr1 mindre än nr2 (less than)? | | nr1 -le nr2 | Är nr1 mindre än, eller lika med, nr2 (less than or equal)? | | nr1 -gt nr2 | Är nr1 större än nr2 (greater than)? | | nr1 -ge nr2 | Är nr1 större än, eller lika med, nr2 (greater than or equal)? |

För att jämföra två variabler med värden kan vi göra så här:

#!/usr/bin/env bash
        
TAL1=10
TAL2=20
if [ ${TAL1} -eq ${TAL2} ] ; then
    echo "${TAL1} är lika med ${TAL2}"
fi
if [ ${TAL1} -ne ${TAL2} ] ; then
    echo "${TAL1} är inte lika med ${TAL2}"
fi
        
exit 0

Vi skapar en fil där vi skriver in skriptet ovanför och kör det för att se vad som händer. Sedan ändrar vi TAL1 och TAL2 till andra värden för att kontrollera att det fungerar som det skall.

Flera villkor

Det händer ibland att vi vill kunnna jämföra flera villkor samtidigt. Att en fil finns betyder inte alltid att den går att skriva till. För att kunna använda flera villkor behöver vi använda -a (för and, och) och -o (för or, eller). Säg att vårt skript behöver veta om en fil finns och att skriptet kan skriva till filen, om dessa villkor inte uppfylls skall vi avsluta skriptet:

if [ ! -e ./datafil.dat -o ! -w ./datafil.dat ] ; then
    echo "Filen datafil.dat finns inte, eller går inte att skriva till."
    exit 1
fi

Vad vi säger i vårt villkor (i skriptet ovanför) är om filen datafil.dat inte finns (! -e ./datafil.dat) eller (-o) filen inte är skrivbar (! -w ./datafil.dat) skall vi skriva ett felmeddelande och avsluta skriptet.

Om inte ett villkor uppfylls, kanske ett annat gör det?

I våra if-satser (villkoren) har vi testat om ett, eller flera (med and och or), villkor är uppfyllda. Hur gör vi om vill testa om ett villkor uppfylls, och göra något annat om villkoret inte är uppfyllt?

if [ ${TAL} -ge 10 ] ; then
    echo "Talet är 10 eller högre."
else
    echo "Talet är mindre än 10."
fi

I exemplet ovanför testar vi om variabeln $TAL är större eller lika med 10, om $TAL är större eller lika med 10 kommer vi skriva ut texten Talet är 10 eller högre om det är mindre än 10 (villkoret uppfylls inte) kommer vi skriva ut texten Talet är mindre än 10 istället.

Här använder vi oss av else som vi kan läsa som: om villkoret är sant gör vi det här, om det inte är sant (else) gör vi det här.

Det händer att vi vill testa flera villkor på en och samma variabel. Vi tittar på ett exempel där vi skall skriva ett skript som räknar ut betyg för elever. Vi skriver följande:

if [ ${BETYG} -ge 80 ] ; then
    echo "VG"
elif [ ${BETYG} -ge 50 ] ; then
    echo "G"
else
    echo "IG"
fi

Variabeln $BETYG innehåller antalet poäng eleven har skrivit på provet. Gränsen för godkänd har vi satt till 50 poäng och gränsen för väl godkänd har vi satt till 80 poäng. Allt under 50 poäng kommer resultera i betyget icke godkänd.

Vi använder oss av elif i skriptet ovanför. elif är en förkortning av else if (annars om). elif använder vi när vi behöver testa flera olika villkor.

Om vi sätter variabeln $BETYG till 90 kommer det första villkoret (större än 80) att uppfyllas, dessutom kommer det andra villkoret (90 är större än 50) att uppfyllas. Varför kommer skriptet inte skriva ut både VG och G?

En if-elif sats avbryter hela if-satsen när det första villkoret är sant. Därför kommer vi avsluta hella villkorstestet när villkoret ${BETYG} -ge 80 uppfyllts.

Loopar

Med loopar kan vi upprepa samma sak om och om igen, i all oändlighet eller tills något uppfyller ett villkor. Vi skall titta på tre olika loopar i Bash: while, for och until

While

while-loopar används för att skapa loopar som håller på så länge som ett villkor är uppfyllt, eller avbryts med kommandot break. En while-loop skrivs så här:

while villkor ; do
    vad skall vi göra?
done

Vi testar det i ett skript:

#!/usr/bin/env bash

while read -p "Mata in ett namn: " NAMN ; do
    echo "Du matade in ${NAMN}"
done

Skriptet kommer köras i all oändlighet, eller tills vi avbryter det genom att trycka CTRL och D samtidigt. Om ett villkor inte uppfylls på första försöket kommer kommandona mellan while och done aldrig att köras.

För att skapa ett bättre skript bör vi lägga till något sätt att ta oss ur while-loopen, förutom att trycka CTRL+D. Vi skriver om vårt skript:

#!/usr/bin/env bash
    
while read -p "Mata in ett namn (exit avslutar): " NAMN ; do
    if [ "${NAME}" = "exit" ] ; then
        break
    fi
    echo "Du matade in ${NAMN}"
done

Ett villkor kan vara sant eller falskt. Vi kan använda orden true och false för att skapa våra villkor:

#!/usr/bin/env bash
        
while true ; do
    read -p "Mata in ett namn (exit avslutar): " NAMN
    if [ "${NAMN}" = "exit" ] ; then
        break
    fi
   echo "Du matade in ${NAMN}"
done

Vi kan naturligtvis också bestämma hur många gånger något skall upprepas genom att skapa en räknare:

#!/usr/bin/env bash

NUM=0
while [ ${NUM} -lt 3 ] ; do
    echo "Nummer: ${NUM}"
    NUM=$[ NUM + 1]
done
    
exit 0

for

En for-loop repeterar något så länge den har något att repetera.

Låt oss säga att vi har ett antal filer som vi vill komprimera med gzip, en for-loop skulle klara saken enkelt:

#!/usr/bin/env bash
    
for FILER in "fil1.sh fil2.sh fil3.sh" ; do
    gzip ${FILER}
done
exit 0

I skriptet ovanför kommer vi att komprimera filerna fil1.sh, fil2.sh och fil3.sh med gzip.

En for-loop kan användas med en räknare också, om vi vill göra något ett bestämt antal gånger:

#!/usr/bin/env bash
        
for (( RAKNARE=1; RAKNARE < 10; RAKNARE++ )) ; do
    echo "Räknaren är nu: ${RAKNARE}"
done
exit 0

until

until använder vi för att skapa loopar som skall upprepas tills ett villkor uppfylls. Ett exempel:

#!/usr/bin/env bash
        
NAMN=""
until [ "${NAMN}" = "jonas" ] ; do
    read -p "Skriv ett namn: " NAMN
done
exit 0

I exemplet ovanför kommer until köras tills användaren matar in namnet jonas.

Vill vi att until-loopen skall köras ett visst antal gånger kan vi skriva som i följande skript:

#!/usr/bin/env bash
        
NUM=1
until [ ${NUM} -gt 3 ] ; do
    echo "Nummer: ${NUM}"
    NUM=$[ NUM + 1 ]
done
exit 0

I exemplet ovanför kommer until-loopen skriva ut 1, 2 och 3.

Argument

När vi använder kommandon från kommandoraden kan vi ofta skicka med argument till kommandot. När vi vill kopiera en fil kan det se ut så här:

$ cp filen.txt kopian.txt

Kommandot cp (copy) tar emot två argument: filen.txt som är filen vi vill kopiera och kopian.txt som är namnet på den kopia vi vill skapa.

Ofta vill vi använda argument till våra egna skript också, så användaren av skriptet kan påverka vad som skall hända, eller göras, i skriptet. I den här delen tittar vi på hur det fungerar i Bash.

I Bash finns det ett par fördefinerade variabler som hanterar argument:

  • $# innehåller hur många argument vi har skickat till skriptet.
  • $0 innehåller skriptets namn (filnamnet).
  • $1 innehåller det första argumentet.
  • $2 innehåller det andra argumentet.
  • $3, $4, $5 och så vidare innehåller det tredje, fjärde och femte argumentet som skickats till skriptet.
  • $* innehåller alla argument.

Om vi nu går tillbaka och tittar på kopieringen av filen i exemplet ovanför:

$ cp filen.txt kopian.txt

Här skulle $# säga 2, eftersom vi har två argument. $0 skulle vara cp, eftersom det är skriptets namn. $1 skulle innehålla filen.txt och $2 skulle innehålla kopian.txt.

Nu skriver vi ett skript som använder sig av argument:

#!/usr/bin/env bash

echo "Ditt argument var: $1"
exit 0

Vi sparar skiptet och kör det:

$ bash argument.sh argumentet
Ditt argument var: argumentet

Det är lämpligt att deklarera en variabel som fångar upp argumentet i skriptet, så vi inte får problem med till exempel $1 senare i skriptet:

#!/usr/bin/env bash
        
ARG=$1
echo "Ditt argument var: $ARG"
exit 0

Om ett skript vi skapar behöver argument för att fungera bör vi också ha en kontroll om det kommer ett argument till skriptet. För att kontrollera om vi får in argument till ett skript kan vi skriva så här:

#!/usr/bin/env bash

if [ $# -ne 1 ] ; then
    echo "$0: Behöver ett argument"
    exit 1
fi

I skriptet ovanför kontrollerar vi om vi har ett ($# -ne 1) argument skickat till oss. Om vi inte har ett argument skall vi skriva ut ett felmeddelande, här använder vi $0 för att skriva ut skriptets namn innan meddelandet. Efter felmeddelandet avslutar vi skiptet med exitkoden 1.

Om vi istället vill ha minst ett, eller fler argument skriver vi [ $# -ge 1 ] i skripet. Då kommer vi kontrollera om vi har ett, eller flera, agument skickade till skriptet.

Med variabeln $* får vi fram alla argument som skickades till skriptet, till exempel så här:

#!/usr/bin/env bash
echo $*
exit 0

Vi sparar skiptet med filnamnet argument.sh och kör det:

$ bash argument.sh anna lena lotta jessica
anna lena lotta jessica

Vill vi använda argumenten till något användbart kan vi till exempel använda en for-loop:

#!/usr/bin/env bash

for NAMN in $* ; do
    echo "Ett flicknamn är $NAMN"
done
exit 0

När vi kör skriptet kommer det se ut så här:

$ bash argument.sh anna lena lotta jessica
Ett flicknamn är anna
Ett flicknamn är lena
Ett flicknamn är lotta
Ett flicknamn är jessica

Slumpade tal

Slumpade tal är användbara när vi vill att slumpen skall ha en avgörande roll i våra skript, till exempel slå en tärning. Bash har en variabel som ger oss slumpade tal mellan 0 och 32767: $RANDOM .

$ echo $RANDOM
4716
$ echo $RANDOM
10055

Om vi vill ha ett slumpat tal mellan 0 och 100 låter vi $RANDOM slumpa ett tal och sedan använder vi modulus för att få resten vid en division:

$ echo $((RANDOM%101))
85

För att få ett tal mellan 1 och 100 skriver vi:

$ echo $((RANDOM%100+1))
38

Först tar vi ett slumpat tal med $RANDOM, sedan kör vi det genom modulus 100 vilket ger oss ett tal mellan 0 och 99. Eftersom vi ville ha ett tal mellan 1 och 100 adderar vi 1 till talet vi slumpat fram.

En tärning där vi vill ha ett värde mellan 1 och 6 slumpar vi fram enligt:

$ echo $((RANDOM%6+1))
4

Om vi vill ha ett slumpat tal mellan 20 och 40 börjar vi med att använda modulus 21 (vilket ger oss tal mellan 0 och 20, 21 olika värden) och sedan adderar vi 20:

$ echo $((RANDOM%21+20))
35

Vi måste alltså hålla koll på vad vår offset är (20) och hur många värden från det vi vill nå (21). Om vi vill ha ett slumpat tal mellan 50 och 80 är det 31 som är hur långt vi vill (80 - 50) nå och vår offset är 50 (där vi skall börja):

$ echo $((RANDOM%31+50))
75

Köra program och fånga upp dem från skriptet

Vi kan köra program och jämföra deras utmatning mot något villkor i en if-sats eller tilldela en variabel värdet av vad kommandot matar ut. Detta gör vi genom att skriva kommandot inom $( ), till exempel $(ls) eller $(ls -l).

För att kontrollera om användaren är inloggad som root kan vi skriva så här i vårt skript:

if [ $(id -u) -ne 0 ] ; then
    echo "Du måste köra det här skriptet som root."
    exit 1
fi

Ett bättre sätt att kontrollera om skriptet körs som användaren root är [ $EUID -ne 0 ]. Där $EUID innehåller användar-id för den användare som kör skriptet. Användaren root har användar-id noll (0).

Vi kan tilldela en variabel värdet av vad ett program returnerar:

DATUM=$(date +%Y%m%d)
echo ${DATUM}
touch ${DATUM}.txt

Alla program som körs i systemet returnerar en exit code, precis som vi har gjort med våra skript: exit 0. Noll (0) är standard för att visa att allt gick bra, programmet lyckades utan fel. Exit värden mellan 1 och 255 är felkoder som har olika betydelser. Vanligast är att 1 betyder fel, men många program har olika värden beroende på vilket fel som uppstod. På så sätt har vi lättare att fånga upp fel i våra skript. Vi tittar på det från kommandoraden:

$ touch /tmp/hej
$ echo $?
0
$ touch /proc/hej
touch: kan inte beröra "/proc/hej": Filen eller katalogen finns inte
$ echo $?
1

Eftersom vi har rättigheter att skapa filer i katalogen /tmp kommer det första kommandot lyckas. I katalogen /proc däremot har vi inga skrivrättigheter och kommandot touch misslyckas, vi får tillbaka en etta (1) som indikerar ett fel.

Vi får också ett felmeddelande som inte är så fint att ha med i ett skript, vi skulle kunna dirigera om STDERR till /dev/null för att dölja felmeddelandet:

$ touch /proc/hej 2> /dev/null
$ echo $?
1

/dev/null är en speciell fil som ofta används för att kasta bort utskrifter från skript. Allt vi dirigerar om till /dev/null försvinner, som i ett svart hål. Om vi läser /dev/null får vi enbart tillbaka specialtecknet end of file (EOF).

Om vi vill använda exitvärdena i våra skript kan vi till exempel skriva:

touch /proc/hej 2> /dev/null
if [ $? -ne 0 ] ; then
    echo "$0: fel vid skapandet av filen"
    exit 1
fi

Funktioner

Funktioner använder vi för att återanvända kod. Tänk dig att vi vill göra samma sak flera gånger i ett skript, men på olika ställen i skriptet. Varför skriva om samma kod om och om igen då?

Funktioner skrivs så här:

function funktionens_namn {
    vad funktionen skall göra...
}

En enkel funktion i ett skript kan skrivas så här:

#!/usr/bin/env bash

function funk {
    echo "Hej från funktionen funk"
}
        
funk
exit 0

När vi kör ovanstående skript kommer vi anropa funktionen funk som kommer skriva ut Hej från funktionen funk på skärmen.

Variabler som skapas i skriptet är som standard globala, det vill säga: de är tillgängliga överallt i skriptet. Vi kan alltså använda variabler i våra funktioner, som i det här skriptet:

#!/usr/bin/env bash
    
function ditt_namn {
    echo "Ditt namn är $NAMN"
}
read -p "Mata in ditt namn: " NAMN
ditt_namn
exit 0

I skriptet ovanför kommer vi be användaren att mata in sitt namn, som vi lagrar i variabeln $NAMN. Sedan anropar vi funktionen ditt_namn som kommer använda variabeln $NAMN för att skriva ut det inmatade namnet.

Variabler vi skapar i funktionen kommer också att vara globala, vårt skript kommer alltså åt dem och kan använda dem. Detta hindrar vi med kommandot local som skapar en lokal variabel i funktionen. Den lokala variabeln är inte tillgänglig utanför funktionen. Vi tittar på ett exempel:

#!/usr/bin/env bash

function lokalvar {
    local MAGISK=42
    echo "Inne i funktionen har vi talet $MAGISK"
}
lokalvar
echo "Det magiska talet är $MAGISK"
exit 0

I exemplet ovanför kommer vi att anropa funktionen lokalvar som deklarerar en lokal variabel, $MAGISK, som vi sedan skriver ut på raden under med echo. Efter all kod i funktionen körts hoppar skriptet tillbaka till raden under funktionsanropet (lokalvar i skriptet) och skriver ut $MAGISK igen med echo. Den här gången är $MAGISK tom, eftersom vi inte har deklarerat variabeln och $MAGISK i funktionen lokalvar är en lokal variabel för just den funktionen.

Vi har tidigare tittat på hur vi kan skicka in argument till skript, detta fungerar för funktioner också. $# innehåller hur många argument som skickas, $1 innehåller det första argumentet och så vidare.

Vi börjar med att skapa ett skript med funktionen summa som adderar två tal och skriver ut summan:

#!/usr/bin/env bash
        
function summa {
    SUMMA=$(( $1 + $2 ))
    echo "Summan är $SUMMA"
}
summa 10 20
read -p "Mata in tal1: " TAL1
read -p "Mata in tal2: " TAL2
summa $TAL1 $TAL2
exit 0

Först anropar vi funktionen summa med två tal: summa 10 20 och funktionen skriver ut summan av de två talen. Därefter ber skriptet användaren om två tal. När användaren har matat in dem anropas funktionen summa med de två inmatade talen.

Returvärden

Funktioner kan returnera värden, precis som våra skript kan returnera värden med exit. Funktionerna använder sig av return istället och kan returnera ett värde mellan 0 och 255. Returvärdet fångar vi upp med variabeln $?.

#!/usr/bin/env bash
        
function summa {
    SUMMA=$(( $1 + $2 ))
    return 0
}
summa 10 30
echo "Returvärdet är: $?"
echo "Summan är: $SUMMA"
exit 0

I skriptet ovanför skickar vi in talen 10 och 30 till funktionen summa som räknar ut summan av talen och lagrar svaret i variabeln $SUMMA. Sedan returnerar vi noll med return 0.

Vi fångar upp returvärdet med variabeln $? på raden under anropet till funktionen och variablen $SUMMA på nästa rad.

Tänk på att returvärden bara kan returnera heltal mellan 0 och 255. För att fånga upp resultat av beräkningar och liknande i funktioner får vi jobba med variabler (som $SUMMA) i exemplet ovanför.

Source

Ibland vill vi inkludera ett annat skript i det skript vi kör just nu. Det kan till exempel vara ett skript med funktioner och konfigurering som vi vill använda i flera andra skript. I Bash använder vi source för att inkludera andra skript i det skript vi kör.

När vi använder source läser vårt skript in det andra skriptet och kör det direkt. Vi tittar på ett exempel.

Vi börjar med att skapa skriptet source2.sh:

#!/usr/bin/env bash
echo "Hej från source2.sh"
exit 0

Vi fortsätter med att skapa skriptet source1.sh:

#!/usr/bin/env bash
echo "Hej från source1.sh"
source source2.sh
exit 0

Nu kör vi skriptet source1.sh och ser vad som händer:

$ bash source1.sh
Hej från source1.sh
Hej från source2.sh
$

Om vi vill använda oss av variabler mellan skripten ser det ut så här. Vi ändrar source2.sh till:

#!/usr/bin/env bash
echo "Hej $NAMN från source2.sh"
exit 0

Sedan ändrar vi source1.sh till:

#!/usr/bin/env bash
read -p "Mata in ditt namn: " NAMN
echo "Hej från source1.sh"
source source2.sh
exit 0

Nu kör vi source1.sh och ser vad som händer:

$ bash source1.sh
Mata in ditt namn: jonas
Hej från source1.sh
Hej jonas från source2.sh
$

Vi skapar alltså en variabel ($NAMN) i skriptet source1.sh som sedan används i skriptet source2.sh.

source kan också förkortas som en punkt (.) som i . skript2.sh. Notera mellanslaget mellan punkten och skriptets namn.

Logghantering och felsökning

Loggfiler är till stor hjälp när vi felsöker ett linuxsystem. Linuxkärnan, tjänster som körs i systemet och många applikationer skriver loggfiler. Det finns flera loggfiler i systemet, syslog (systemloggen) är bland de viktigaste att känna till. Loggfilerna lagras i katalogen /var/log/ och där hittar vi ett par loggfiler.

/var/log/auth.log
innehåller information om anslutningar och inloggningar (och inloggningsförsök).
/var/log/dmesg
innehåller meddelanden från linuxkärnan, vi kan visa loggfilen med kommandot dmesg
/var/log/lastlog
innehåller information om vilka användare vi har på systemet och när de senast loggade in (och varifrån). Loggfilen är inte i textformat utan en databas, vi måste använda kommandot lastlog för att visa loggen.
/var/log/syslog
är sysloggen (på en del linuxsystem heter loggfilen /var/log/messages). Numera hamnar det mesta i journald-loggen.

Vi hittar också ett par kataloger i /var/log/. Katalogerna är för specifika tjänster i systemet. apt innehåller loggar från apt-kommandot. Installerar vi webbservern Apache på systemet kommer den logga till katalogen /var/log/apache2/.

Det är en tjänst som heter rsyslogd som hanterar loggfiler i Linux. rsyslogd har en konfigurationsfil som heter /etc/rsyslog.conf, den har också en katalog som heter /etc/rsyslog.d/ där vi kan lägga egen konfiguration till rsyslogd.

rsyslogd kan logga lokalt till systemets disk, eller till en rsyslogd-server över nätverk. Nätverkskommunikationen sker över port 514 med UDP- eller TCP-protokollet. Vi kan också logga både till en nätverksserver och den lokala disken samtidigt.

An icon of a key

Scenario: vi får intrång i vår server och någon illasinnad hacker installerar en Bitcoin-miner. För att radera alla spår från intrånget raderar hackern allt i loggfilerna på disken som kan avslöja henne. Om vi hade loggat allt till en nätverksserver hade vi haft spåren kvar i loggfilerna på den servern.

rsyslogd har också ett system för att vi skall kunna fånga upp loggmeddelanden och skicka dem till rätt ställe: facilities och levels:

facilities
auth, authpriv, cron, daemon, ftp, kern, lpr, mail, news, syslog, user, uucp, local0, local1, local2, local3, local4, local5, local6, local7
levels
emerg, alert, crit, err, warning, notice, info, debug

Hur rsyslogd loggar de olika meddelandena specifierar vi i konfigurationen för rsyslogd: /etc/rsyslog.d/50-default.conf:

auth,authpriv.*                 /var/log/auth.log
*.*;auth,authpriv.none          -/var/log/syslog
#cron.*                         /var/log/cron.log
#daemon.*                       -/var/log/daemon.log
kern.*                          -/var/log/kern.log
#lpr.*                          -/var/log/lpr.log
mail.*                          -/var/log/mail.log
#user.*                         -/var/log/user.log

Raderna ovanför visar några exempel på vilka regler vi har i /etc/rsyslog.d/50-default.conf.

Om vi börjar med raden som ser ut så här: *.*;auth,authpriv.none -/var/log/syslog. Här är den första matchningen *.*, vilket betyder att alla facilities med alla levels skall loggas till /var/log/syslog. Vi provar att skapa ett meddelande med kommandot logger:

$ logger -p local4.info "hello local4 info"

Nu kan vi titta i /var/log/syslog om det kom in i loggfilen, vi kan till exempel använda kommandot grep för att söka efter strängar (tecken) i filen:

$ grep "local4" /var/log/syslog
Sep 25 22:41:06 ubuntu jonas: hello local4 info

Vi har en regel som säger cron.* /var/log/cron, allt som går till facility cron oavsett level skall hamna i filen /var/log/cron. För att prova denna måste vi avkommentera raden (ta bort #-tecknet) i filen /etc/rsyslog.d/50-default.conf:

$ sudo vi /etc/rsyslog.d/50-default.conf 
auth,authpriv.*                 /var/log/auth.log
*.*;auth,authpriv.none          -/var/log/syslog
#cron.*                          /var/log/cron.log
#daemon.*                       -/var/log/daemon.log
kern.*                          -/var/log/kern.log
#lpr.*                          -/var/log/lpr.log
mail.*                          -/var/log/mail.log
#user.*                         -/var/log/user.log

#-tecknet betyder att raden är en kommentar och skall ignoreras, det vill vi inte - så vi tar bort tecknet.

Nu startar vi om tjänsten rsyslog för att läsa in konfigurationen:

$ sudo systemctl restart rsyslog

För att skicka ett meddelande till facility cron kan vi använda kommandot logger igen:

$ logger -p cron.info "Hello cron"

Nu skapades filen /var/log/cron.log (om den inte redan fanns) och där kom meddelandet vi skickade med kommandot logger.

$ tail /var/log/cron.log
Sep 25 22:41:06 ubuntu jonas: Hello cron

Så med kommandot logger kan vi ange facility.level med -p. Om vi inte anger något kommer user.notice att användas som standard. Vi kan också tagga våra meddelanden, vilket kan vara bra om vi enkelt vill kunna söka efter dem. Då använder vi flaggan -t så här:

$ logger -p user.info -t JONAS "Hej Jonas"

Nu är det enkelt att hitta alla meddelanden som är taggade med JONAS med hjälp av kommandot grep:

$ grep JONAS /var/log/syslog
Sep 25 22:51:30 ubuntu JONAS: Hej Jonas
An icon indicating this blurb contains information

Taggning av meddelanden är användbart när vi skriver egna skript och enkelt vill kunna hitta meddelanden från skripten. Ett skript är en serie kommandon som skrivs in i en fil, för att sedan köras tillsammans.

Loggrotatation

Eftersom det skrivs i loggfilerna hela tiden kommer de bli stora och ta mycket plats på disken. Därför använder vi något som kallas logrotate. Logrotate har sin konfigurationsfil i /etc/logrotate.conf och katalogen /etc/logrotate.d/. I konfigurationen ställer vi in hur olika loggfiler skall hanteras av logrotate. Logrotate tar loggfilen och roterar den vilket innebär att den nuvarande loggfilen kan komprimeras och sparas undan i ett komprimerat format. Ofta döps roterade loggfiler till namnet och sedan filändelsen .1, .2 och så vidare. Komprimerar vi loggfilen kommer filnamnet sluta med .gz.

Logrotate körs varje dag med hjälp av schemaläggaren cron, jobbet för logrotate ligger i filen /etc/cron.daily/logrotate . För loggfilen /var/log/syslog ser logrotatejobbet ut så här:

/var/log/syslog
{
    rotate 7 <.>
    daily <.>
    missingok <.>
    notifempty <.> 
    delaycompress <.>
    compress <.> 
    postrotate <.>
        /usr/lib/rsyslog/rsyslog-rotate
    endscript 
}
  • rotate anger hur många gånger en fil skall roteras innan den skall raderas. Här sparar vi de senaste 7 filerna.
  • daily anger att vi skall rotera filerna varje dag
  • missingok om loggfilen saknas ignorerar logrotate den, vi får alltså inget felmeddelande. Motsatsen är nomissingok.
  • notifempty anger att vi inte skall rotera filen om den är tom.
  • delaycompress anger att vi skall komprimera den roterade loggfilen vid nästa tillfälle logrotate körs.
  • compress anger att vi skall komprimera (med gzip) loggfilerna som roteras.
  • postrotate börjar ett block med kommandon som slutar med endscript. De kommandon som står mellan postrotate och endscript kommer köras med kommandotolken /bin/sh när logrotate körs.

lastlog

Med kommandot lastlog kan vi se när användare senast loggade in på systemet. När vi kör lastlog visas alla användarkonton vi har i systemet. Vill vi bara se när en specifik användare loggade in senast använder vi -u användarnamn till kommandot. Så här för användaren root:

$ lastlog -u root

journald

Ubuntu använder uppstarts- och tjänstehanteraren systemd som kommer med logghanteraren journald. Med kommandot journalctl kan vi hämta information från alla loggar i systemet på ett enklare sätt, men detta fungerar bara med de linuxdistributioner som använder systemd. De flesta nya linuxdistributioner använder systemd, eftersom man vill standardisera på systemd. Saknar vi kommandot journalctl på datorn betyder det att vi får läsa loggfilerna manuellt, som vi gick igenom innan.

Det allra enklaste sättet att läsa loggarna med journalctl är att bara skriva kommandot och köra programmet:

$ journalctl

Loggarna visas och vi hoppar upp och ned med hjälp av Page Up/Page Down. Vi avslutar listningen genom att trycka på tangenten q.

Vill vi visa boot-loggen, det vill säga allt som loggats medan systemet startade upp skriver vi:

$ journalctl -b

För att enbart visa meddelanden från linuxkärnan skriver vi:

$ journalctl -k

Vi kan visa meddelanden som är taggade med något specifikt genom att skriva:

$ journalctl -t jonas

Här kommer vi få alla meddelanden som är taggade med jonas (se exemplet med kommandot logger tidigare i det här dokumentet).

I systemd finns något som kallas units, vi kommer till det senare i kursen när vi skall prata om systemd och tjänster. Journalctl kan visa meddelanden från units genom att skriva till exempel (för cron):

$ journalctl -u cron

Med -p kan vi filtrera på vilken prioritet ett meddelande har, för att till exempel se alla felmeddelanden (err) i loggarna skriver vi:

$ journalctl -p err
An icon indicating this blurb contains information

Det finns åtta olika nivåer av prioritet: emerg, alert, crit, err, warning, notice, info och debug.

För att följa en logg, det vill säga visa alla nya meddelanden som kommer in i loggen använder vi -f:

$ journalctl -f

Vi avbryter genom att hålla nere tangenten CTRL och trycka på c samtidigt.

Vi kan välja från vilket datum och tid och till vilket datum och tid vi vill visa loggmeddelanden. Tiden anger vi i formatet "2016-09-01 07:00:00", om vi inte anger tiden utan bara datum "2016-09-01" kommer tiden 00:00:00 användas. Vi kan också ange tiden som yesterday, today, tomorrow och now. Vi använder flaggorna --since= och --until= för att filtrera på tid, till exempel:

$ journalctl --since="2016-08-01" --until "2016-08-02 16:04:08"
$ journalctl --since="2016-08-01"

Det första kommandot kommer visa alla loggmeddelanden från 2016-08-01 00:00:00 till 2016-08-02 00:00:00. Det andra kommandot kommer visa alla loggmeddelanden från 2016-08-01 00:00:00 till nu.

Vi kan hämta loggar från specifika program och tjänster som körs i systemet. Vi har till exempel en SSH-server igång som standard i Ubuntu. Den tjänsten körs av programmet /usr/sbin/sshd och för att visa loggfilerna från tjänsten skriver vi:

$ journalctl /usr/sbin/sshd

Vi kan också kombinera många av valen till journalctl, till exempel om vi bara vill visa felmeddelanden från tjänsten cron skriver vi:

$ journalctl -p err -u cron

Systemd

I den här delen kommer vi:

  • Gå igenom vad systemd är
  • Lära oss att hantera tjänster i Linux

Systemd är en system- och tjänstehanterare för Linux. Det har funnits några sådana genom tiderna, varav SysV init var den allra vanligaste uppstartshanteraren, sedan kom Upstart och nu är det Systemd som gäller för de flesta större linuxdistributioner. Med systemd hanterar vi hur systemet skall starta upp (boota) och vi kan hantera de tjänster som vi har installerade: vi kan starta dem, stänga ner dem, starta om dem och så vidare.

Med systemd kan vi starta tjänster parallelt, vilket innebär att uppstarten av en linuxmaskin blir mycket snabbare. Systemd tar reda på vilka tjänster en specifik tjänst är beroende av och startar dem sedan i turordning. Systemd är också bakåtkompatibel, vilket innebär att vi kan använda skript för SysV init och Upstart tjänsterna.

Systemd använder sig av något som kallas units, en unit är en konfiguration av en specifik tjänst. Tjänste-units i systemd kallas namn.service och kan användas för att till exempel starta, stänga ner, starta om, aktivera och inaktivera tjänsten. Vi använder kommandot systemctl för att hantera tjänsterna:

systemctl start name.service
Startar tjänsten name
systemctl stop name.service
Stänger ner (avslutar) tjänsten name
systemctl restart name.service
Startar om tjänsten name
systemctl try-restart name.service
Startar bara om tjänsten name om den redan körs.
systemctl reload name.service
Läser in konfigurationen för tjänsten name igen. Bra när vi konfigurerat om en tjänst och vill läsa in den nya konfigurationen.
systemctl status name.service
Visar status för tjänsten name
systemctl is-active name.service
Visar om tjänsten name körs. Ungefär som status men med enklare utmatning.
systemctl list-units --type service --all
Visar status för alla tjänster i systemet.

Systemd hanterar uppstarten av linuxsystemet och bestämmer vilka tjänster som skall starta upp vid uppstart (boot) och vilka som inte skall starta upp. Vi hanterar detta med enable och disable i systemd.

systemctl enable name.service
Aktiverar tjänsten name
systemctl disable name.service
Inaktiverar tjänsten name
systemctl is-enabled name.service
Visar om tjänsten name är aktiverad eller inte.

Om vi inte skriver .service efter en tjänsts namn kommer systemd förutsätta att vi menar just .service. Om vi inte angett något annat så klart. Följande kommandon gör samma sak:

$ systemctl is-enabled httpd.service
$ systemctl is-enabled httpd

Vi visar alla installerade tjänster som körs genom att skriva:

$ systemctl list-units --type service
UNIT                        LOAD   ACTIVE SUB     DESCRIPTION
alsa-state.service          loaded active running Manage Sound Card State (res
auditd.service              loaded active running Security Auditing Service
chronyd.service             loaded active running NTP client/server
crond.service               loaded active running Command Scheduler
dbus.service                loaded active running D-Bus System Message Bus
...

Listan pausas skärm för skärm och vi använder PAGE-UP och PAGE-DOWN för att förflytta oss. Vi avslutar listningen genom att trycka på tangenten q.

För att visa alla tjänster och deras status (även de som inte körs) skriver vi:

$ systemctl list-units --type service --all

Vi kan också visa status på alla tjänster med list-unit-files:

$ systemctl list-unit-files --type service
UNIT FILE                                   STATE
alsa-restore.service                        static
alsa-state.service                          static
alsa-store.service                          static
auditd.service                              enabled
autovt@.service                             disabled
blk-availability.service                    disabled
...

Om vi vill visa status för en specifik tjänst använder vi status:

$ systemctl status sshd
* sshd.service - OpenSSH server daemon
  Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled;
    vendor preset: enabled)
  Active: active (running) since Sun 2016-10-02 14:23:25 CEST;
    1h 45min ago
    Docs: man:sshd(8)
             man:sshd_config(5)
 Main PID: 1122 (sshd)
   CGroup: /system.slice/sshd.service
             |--1122 /usr/sbin/sshd -D
Loaded
informerar om tjänsten är laddad, vilken sökväg den finns på och om den är aktiverad (enabled).
Active
visar om tjänsten körs just nu, och i så fall när den startades.
Main PID
visar vilket process id (PID) som startade tjänsten.
CGroup
visar information om de CGroups (control groups) som är relaterade till tjänsten.

Om vi bara vill se om tjänsten är startad eller inte använder vi is-active:

$ systemctl is-active sshd
active

Vill vi veta om en tjänst aktiveras vid uppstart använder vi enabled:

$ systemctl is-enabled sshd
enabled

För att starta en tjänst använder vi start:

$ systemctl start sshd

För att stänga ner en tjänst använder vi stop:

An icon indicating this blurb contains a warning

I exemplet kommer vi stänga ner SSH-tjänsten på vår maskin. Gör inte detta om du använder SSH för att ansluta till din linuxmaskin!

$ systemctl stop sshd

Ibland behöver vi starta om en tjänst, kanske för att vi konfigurerat om den eller att den slutat svara. Då använder vi restart:

$ systemctl restart sshd

Vi kan också använda try-restart som bara startar om tjänsten om den redan körs. Detta gör att vi inte startar upp tjänster som inte redan var igång.

En del tjänster klarar av att läsa om sin konfiguration utan att starta om hela tjänsten med restart. Då använder vi reload istället:

$ systemctl reload sshd

Om vi vill att en tjänst skall starta upp när systemet startar använder vi enable:

$ systemctl enable sshd
Created symlink from /etc/systemd/system/multi-user.target.wants/
    sshd.service to /usr/lib/systemd/system/sshd.service.

För att se till att tjänsten inte startar upp med systemet använder vi disable:

$ systemctl disable sshd
Removed symlink /etc/systemd/system/multi-user.target.wants/
    sshd.service.

Targets

Om du har använt Linux tidigare kanske du känner till runlevels. I SysV init var de numrerade 0 till 6. Där man valde vilken runlevel man ville att systemet skulle köras i. Vanligtvis var det runlevel 2 eller 3 för en server utan grafisk miljö och 5 för en server med grafisk miljö. I systemd heter de olika runlevels targets och vi har fortfarande sju stycken (0-6):

runlevel0.target, poweroff.target
stänger ner systemet och datorn (stänger av strömmen)
runlevel1.target, rescue.target
startar ett räddningsläge där vi kan laga ett trasigt linuxsystem
runlevel2.target, multi-user.target
startar en fleranvändarmiljö utan grafik
runlevel3.target, multi-user.target
startar en fleranvändarmiljö utan grafik
runlevel4.target, multi-user.target
startar en fleranvändarmiljö utan grafik
runlevel5.target, graphical.target
startar en fleranvändarmiljö med grafik
runlevel6.target, reboot.target
stänger ner systemet och startar om datorn

För att se vilka targets vi har laddade nu skriver vi:

$ systemctl list-units --type target
UNIT                  LOAD   ACTIVE SUB    DESCRIPTION
basic.target          loaded active active Basic System
bluetooth.target      loaded active active Bluetooth
cryptsetup.target     loaded active active Encrypted Volumes
...

För att se alla tillgängliga targets lägger vi till --all:

$ systemctl list-units --type target --all

För att se vilken target som är standard använder vi get-default:

$ systemctl get-default
multi-user.target

För att ändra target använder vi isolate, för att starta om datorn skriver vi:

$ systemctl isolate reboot.target

För att ändra vilken target systemet skall starta upp i använder vi set-default:

$ systemctl set-default graphical.target

För att återställa till den target vi använder när vi kör textbaserat läge skriver vi:

$ systemctl set-default multi-user.target

Stänga ner och starta om Linux

systemctl halt
stänger ner systemet, men behåller strömmen
systemctl poweroff
stänger ner systemet och stänger av strömmen
systemctl reboot
startar om systemet

Nätverk

En av de viktigaste funktionerna för vår server är nätverket. Det dominerande protokollet för nätverk idag är TCP/IP. TCP/IP är egentligen två protokoll, ett för transporten av datapaket (TCP) och ett för adresseringen av nätverk och noder i nätverk (IP). Det är den här protokollstacken vi använder hemma, i skolan och på företaget. När vi surfar på webben använder vi protokollet Hyper Text Transfer Protocol (HTTP) och ibland är det HTTPS. HTTPS är HTTP som skickas över en krypterad förbindelse, tidigare SSL - numera TLS. När vi skickar epost använder vi oss av ett protokoll som heter Simple Mail Transfer Protocol (SMTP) och när vi hämtar epost använder vi oss av POP3- eller IMAP-protokollen. Även om tjänster som GMail och Outlook.com har tagit över mycket av epostandet idag är det fortfarande SMTP och POP3/IMAP som ligger där och arbetar bakom webbsidorna.

Under alla dessa protokoll (det finns många fler) skickar vi våra datapaket över kablar, radiovågor och ljus (fiber). Den standard som är vanligast förekommande för överföring av datapaket heter Ethernet. Ethernet delas upp i olika standarder (eller versioner om man så vill), där IEEE 802.3 är Ethernet över partvinnad kopparkabel (TP-kabel) och IEEE 802.11 som är Ethernet över radiovågor (WiFi). Dessa standarder delas också in i undergrupper, där vi kan hitta 10 megabit Ethernet, 100 megabit Ethernet, 1000 megabit Ethernet och 10 000 megabit Ethernet under 802.3. Hastigheten är inte allt, undergrupperna definerar också om det är koppartråd, koaxialkabel eller fiber vi använder och hur lång kabeln får vara. Under den trådlösa standarden 802.11 hittar vi undergrupper som 802.11b, 802.11g, 802.11n och 802.11a.

Det finns andra typer av nätverk också, som Token Ring, FDDI och ATM. Här kommer vi bara gå igenom Ethernet, eftersom det är det vanligast förekommande.

TCP/IP

För att en enhet (dator) skall kunna kommunicera i ett TCP/IP-nätverk behöver den ha minst två saker konfigurerade: en unik IP-adress och en nätmask. Nätmasken används av enheten för att identifiera vilket nätverk enheten finns på. IP-adressen och nätmasken tillsammans berättar vilken enhet (host) och vilket nätverk (net) enheten befinner sig på.

Vi brukar konfigurera två saker till när vi konfigurerar TCP/IP för en enhet: en default gateway och DNS-server. Default gateway använder vi för att berätta för enheten hur den skall komma ut ur sitt egna nätverk, till exempel när vi vill titta på kattfilmer på Youtube, eller titta på Facebook, så vi vet vad vår kompis åt till lunch idag. DNS-servrar använder vi för att göra om domännamn som youtube.com och facebook.com till IP-adresser som 142.250.74.110 och 31.13.72.36 .

De fyra saker vi behöver för att ha en fungerande enhet på ett TCP/IP-baserat nätverk är:

  • IP-adress
  • Nätmask
  • Default Gateway
  • DNS-server (eller flera)

För att se vilken IP-adress och nätmask vi har konfigurerat på vår dator använder vi kommandot ip address (eller ip addr eller ip a som är förkortningar av kommandot).

$ ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default\
 qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group\
 default qlen 1000
    link/ether 04:92:26:d4:b1:63 brd ff:ff:ff:ff:ff:ff
    altname enp0s31f6
    inet 10.0.1.196/24 brd 10.0.1.255 scope global dynamic noprefixroute eno1
       valid_lft 29366sec preferred_lft 29366sec
    inet6 fd0c:1af4:8a21:0:6313:c131:1508:827a/64 scope global noprefixroute
       valid_lft forever preferred_lft forever
    inet6 fe80::24d8:b160:e32b:dbf6/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

Här ovanför ser vi att vi har två nätverksgränssnitt (network interface): lo och eth0 . Gränssnittet lo är speciellt och heter local loopback. Det har alltid IP-adressen 127.0.0.1 och IPv6-adressen ::1. Det är ett gränssnitt som bara går att komma åt från vår egen dator, alltså inte utifrån.

Det andra gränssnittet, eth0, är vårt nätverkskort som sitter i datorn. Vi kan ha flera sådana och då visas de också med kommandot ip address. I det här exemplet har vårt nätverkskort IP-adressen 10.0.1.196 och nätmasken /24. Adressen skrivs enligt en standard som kallas Classless Inter-Domain Routing (CIDR) (uttalas cider) och anger hur många bitar av IP-adressen som är nätverksidentiteten. I det här exemplet är det 24 bitar som används för att identifiera nätverket enheten befinner sig i.

Vi har också en link/ether adress, i exemplet: 04:92:26:d4:b1:63 . Det är en adress som kallas MAC-adress. Varje nätverkskort som tillverkas får en egen, 48 bitar lång, adress som används för att kommunicera med nätverkskortet.

För att se vilken default gateway vi använder skriver vi kommandot ip route (eller bara ip r):

$ ip route
default via 10.0.1.1 dev eno1 proto dhcp src 10.0.1.196 metric 100
10.0.1.0/24 dev eno1 proto kernel scope link src 10.0.1.196 metric 100

Här ser vi att default går via IP-adressen 10.0.1.1 . Det betyder att all nätverkstrafik som skall ut ur vårt egna IP-nätverk till, till exempel internet, skall skickas till IP-adressen 10.0.1.1 . Där finns förhoppningsvis en nätverksenhet som kallas router. Routern skickar trafiken vidare till nästa nätverk, som skickar till nästa nätverk och så vidare tills vi har nått vårat mål (till exempel Youtube.com). Målet skickar tillbaka ett svar som skickas till en router, som skickar till nästa router, och så vidare tills vi når målet (vår dator).

Vår default gateway (router) måste vara i samma nätverk som vår enhet är. Det vill säga, inom samma IP-adress- område. Syftet med en default gateway är att skicka ut (och in) trafik till andra nätverk. Vi måste alltså ha den i samma nätverk.

Datorer kommunicerar med andra enheter med hjälp av IP-adresserna, men vi använder domännamn som facebook.com och youtube.com. För att datorerna skall kunna göra om facebook.com till en IP-adress frågar de en DNS-server. Det fungerar lite som nummerupplysningen på internet. Vi kan fråga vilket telefonnummer Jonas har, och så får vi veta det. Känner vi till ett telefonnummer kan vi också använda tjänster på internet för att ta reda på vem det numret går till. DNS fungerar likadant, fast för IP-adresser.

Vi kan använda kommandot dig för att ta reda på vilken IP-adress till exempel facebook.com har:

$ dig facebook.com

; <<>> DiG 9.16.1-Ubuntu <<>> facebook.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48685
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;facebook.com.                  IN      A

;; ANSWER SECTION:
facebook.com.           1       IN      A       157.240.9.35

;; Query time: 275 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)

Här ovanför ser vi att facebook.com går till IP-adressen 157.240.9.35 . Låt oss titta på aftonbladet.se också, vi gör samma sak med kommandot dig:

$ dig aftonbladet.se

; <<>> DiG 9.16.1-Ubuntu <<>> aftonbladet.se
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2412
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;aftonbladet.se.            IN      A


;; ANSWER SECTION:
aftonbladet.se.     84      IN      A       35.71.185.87
aftonbladet.se.     84      IN      A       52.223.48.227

;; Query time: 55 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)

I exemplet ovanför ser vi att vi får två IP-adresser som svar. Det innebär att vår webbläsare kommer använda en av dem för att ansluta till aftonbladet.se om vi vill titta på deras nyheter. Att det är två (eller flera) IP-adresser konfigurerade för en domän heter DNS Round Robin och är en enklare form av lastbalansering.

Vi kan också använda kommandot host för att fråga våra DNS-servrar:

$ host facebook.com
facebook.com has address 31.13.72.36
facebook.com has IPv6 address 2a03:2880:f10a:83:face:b00c:0:25de
facebook.com mail is handled by 10 smtpin.vvv.facebook.com.

Här får vi också veta IPv6-adressen till facebook.com och vilken server som hanterar deras inkommande epost. Denna information finns i en posttyp (record) som heter MX (Mail eXchange) och används varje gång vi vill skicka epost till någon.

Nu gör vi samma sak för aftonbladet.se :

$ host aftonbladet.se
aftonbladet.se has address 35.71.185.87
aftonbladet.se has address 52.223.48.227
aftonbladet.se mail is handled by 1 aspmx.l.google.com.
aftonbladet.se mail is handled by 5 alt1.aspmx.l.google.com.
aftonbladet.se mail is handled by 5 alt2.aspmx.l.google.com.
aftonbladet.se mail is handled by 10 alt3.aspmx.l.google.com.
aftonbladet.se mail is handled by 10 alt4.aspmx.l.google.com.

Här ser vi att Aftonbladet använder Google för sin epost (GMail).

Varje gång vi använder kommandot dig, host eller något annat kommando som pratar DNS frågar vår dator den DNS-server som vi konfigurerat. I Linux är det filen /etc/resolv.conf som innehåller informationen om vilka DNS-servrar vi vill använda:

$ cat /etc/resolv.conf
# This file is managed by man:systemd-resolved(8). Do not edit.
#
# This is a dynamic resolv.conf file for connecting local clients to the
# internal DNS stub resolver of systemd-resolved. This file lists all
# configured search domains.
#
# Run "resolvectl status" to see details about the uplink DNS servers
# currently in use.
#
# Third party programs must not access this file directly, but only through the
# symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a different way,
# replace this symlink by a static file or a different symlink.
#
# See man:systemd-resolved.service(8) for details about the supported modes of
# operation for /etc/resolv.conf.
nameserver 127.0.0.53
options edns0 trust-ad

Valet nameserver anger vilken DNS-server vi vill fråga. Här skall vi alltid ange en IP-adress! I Ubuntu används systemd-resolved som DNS-server. Det är en tjänst som hanterar namnuppslag (frågor till DNS-servrar) och vi använder kommandot resolvectl för att hantera tjänsten.

$ resolvectl status
Global
       LLMNR setting: no
MulticastDNS setting: no
  DNSOverTLS setting: no
      DNSSEC setting: yes
    DNSSEC supported: yes
  Current DNS Server: 208.67.220.220
         DNS Servers: 4.2.2.1
                      4.2.2.2
                      208.67.220.220
          DNSSEC NTA: 10.in-addr.arpa
                      16.172.in-addr.arpa
                      168.192.in-addr.arpa
                      17.172.in-addr.arpa
                      18.172.in-addr.arpa
                      19.172.in-addr.arpa
                      20.172.in-addr.arpa
                      21.172.in-addr.arpa
                      22.172.in-addr.arpa
                      23.172.in-addr.arpa
                      24.172.in-addr.arpa
                      25.172.in-addr.arpa
                      26.172.in-addr.arpa
                      27.172.in-addr.arpa
                      28.172.in-addr.arpa
                      29.172.in-addr.arpa
                      30.172.in-addr.arpa
                      31.172.in-addr.arpa
                      corp
                      d.f.ip6.arpa
                      home
                      internal
                      intranet
                      lan
                      local
                      private
                      test
Link 2 (eth0)
      Current Scopes: DNS
DefaultRoute setting: yes
       LLMNR setting: yes
MulticastDNS setting: no
  DNSOverTLS setting: no
      DNSSEC setting: yes
    DNSSEC supported: yes
  Current DNS Server: 208.67.220.220
         DNS Servers: 4.2.2.1
                      4.2.2.2
                      208.67.220.220
                      192.168.122.1

Sist i utmatningen kan vi se de fyra DNS-servrar systemet är konfigurerat att använda:

  • 4.2.2.1
  • 4.2.2.2
  • 208.67.220.220
  • 192.168.122.1

Vill vi bara veta vilka DNS-servrar som används skriver vi:

$ resolvectl dns
Global: 4.2.2.1 4.2.2.2 208.67.220.220
Link 2 (eth0): 4.2.2.1 4.2.2.2 208.67.220.220 192.168.122.1

Vi kan använda kommandot resolvectl för att fråga DNS-servrarna:

$ resolvectl query aftonbladet.se
aftonbladet.se: 35.71.185.87                   -- link: eth0
                52.223.48.227                  -- link: eth0
-- Information acquired via protocol DNS in 29.2ms.
-- Data is authenticated: no

För att ställa in så att servern frågar Googles DNS-server skriver vi följande kommando:

$ sudo resolvectl dns eth0 8.8.8.8 8.8.4.4

För att göra ändringarna permanenta (så det är inställt vid varje omstart) behöver vi redigera filen /etc/systemd/resolved.conf :

$ sudo vi /etc/systemd/resolved.conf

I filen skriver vi:

[Resolve]
DNS=8.8.8.8 8.8.4.4
FallbackDNS=
Domains=
#LLMNR=no
#MulticastDNS=no
DNSSEC=yes
#DNSOverTLS=no
Cache=yes
DNSStubListener=yes
#ReadEtcHosts=yes

Efter att vi ändrat i filen och sparat den laddar vi om systemd-resolved :

$ sudo systemctl start systemd-resolved.service

IP-adress, nätmask och default gateway brukar vi konfigurera tillsammans. I Ubuntu görs det med hjälp av netplan . Till att börja med så finns det två olika sätt att konfigurera IP-adressen med: dynamisk- (DHCP) och statisk konfiguration. Hemma brukar vi använda dynamisk konfiguration (DHCP), då det är smidigast. Vi ansluter vår laptop eller mobiltelefon, ansluter den till wifi med ett lösenord och sedan kommer vi ut på internet. Enheten fick IP-adress, nätmask, default gateway och DNS-servrar från nätverket helt automatiskt.

När vi konfigurerar servrar vill vi oftast att de skall ha samma IP-adress hela tiden och för att säkerställa det använder vi statisk konfiguration.

An icon indicating this blurb contains information

Tänk på att vara försiktig med att ändra IP-adress, nätmask och default gateway på en server som du ansluter till över internet. Konfigurerar du den fel kommer du inte komma åt den.

Netplan konfigureras i katalogen /etc/netplan/ där vi hittar filen 01-netcfg.yaml . För att ställa in vårt nätverkskort att använda dynamisk konfiguration redigerar vi filen med texteditorn vi:

$ sudo vi /etc/netplan/01-netcfg.yaml

Filen är skriven i ett format som kallas YAML. Det är ett vanligt format för konfigurationsfiler för moderna applikationer. Viktigt att tänka på när det gäller YAML är att man använder indrag (indentering) för att skapa avsnitt i konfigurationen. De här indragen skall skapas med mellanslag (två mellanslag) och inte tabbtangenten! Det är också viktigt att filen följer formatteringen, annars kommer det bli fel när vi skall aktivera ändringarna. Var nogrann!

network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: true
      nameservers:
        addresses: [8.8.8.8, 8.8.4.4]

eth0 är namnet på nätverkskortet vi vill konfigurera, vi kan använda kommandot ip address för att ta reda på namnet på nätverkskortet. dhcp4: true anger att vi vill använda DHCP (dynamisk konfiguration) och nameservers är en lista med IP-adresser till DNS-servrar vi vill använda. Det här behöver vi inte ange, eftersom vårt nätverk kommer tillhandahålla information om vilka DNS-servrar vi skall använda. I exemplet ovanför visar vi hur vi sätter Googles DNS-servrar för nätverkskortet.

När vi är klara med ändringarna i filen 01-netcfg.yaml skall vi aktivera den nya konfigurationen, det gör vi med kommandot netplan apply :

$ sudo netplan apply

För att ställa in en statisk IP-adress behöver vi ändra i filen 01-netcfg.yaml igen, men först behöver vi veta vilken IP-adress, nätmask, default gateway och DNS-server vi vill använda. I exemplet nedanför kommer vi använda:

  • IP-adress: 192.168.1.100
  • Nätmask: /24
  • Default gateway: 192.168.1.1
  • DNS-server: 8.8.8.8 och 8.8.4.4 (Googles DNS-servrar)

Då öppnar vi filen och redigerar den enligt nedanstående:

$ sudo vi /etc/netplan/01-netcfg.yaml
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: no
      addresses:
        - 192.168.1.100/24
      gateway4: 192.168.1.1
      nameservers:
        addresses: [8.8.8.8, 8.8.4.4]

När vi är klara med ändringarna aktiverar vi dem med kommandot netplan apply :

$ sudo netplan apply

Nu kan vi konfigurera TCP/IP på vår server. Vi kan ändra IP-adress, nätmask, default gateway och DNS-servrar.

Brandväggen

I linuxkärnan finns en inbyggd brandvägg som heter Netfilter. Vi kan hantera Netfilter med ett kommando som heter iptables . Netfilter jobbar med tre huvudflöden: INPUT för paket som kommer in till vår dator, OUTPUT för paket som lämnar vår dator och skall ut på nätverket och FORWARD som är paket som kommer in till vår dator och sedan skickas vidare ut (routing).

För att se vilka regler vi har aktiverade i brandväggen kan vi skriva iptables -L :

$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target      prot opt source                 destination

Chain FORWARD (policy ACCEPT)
target      prot opt source                 destination

Chain OUTPUT (policy ACCEPT)
target      prot opt source                 destination

Vill vi se reglerna för en specifik kedja anger vi helt enkelt dess namn till kommandot också, till exempel för INPUT:

$ sudo iptables -L INPUT
Chain INPUT (policy ACCEPT)
target      prot opt source                 destination

Vi kan också välja FORWARD eller OUTPUT.

När vi tittar på utmatningen från kommandot iptables -L står det (policy ACCEPT) , det betyder att policyn är satt till ACCEPT vilket innebär att om ingen regel matchar paketet som kommer så gäller policyn. ACCEPT innebär att paketet kommer accepteras. Policyn kan sättas till antagligen ACCEPT eller DROP , vilket innebär att paketet blockeras.

Om vi sätter regler som blockerar inkommande nätverkstrafik på en server som vi bara når över internet måste vi tänka till. Om vi stänger av inkommande trafik till ssh (port 22) kommer vi inte kunna logga in på servern längre.

För att sätta policy på en kedja använder vi kommandot iptables -P :

$ sudo iptables -P INPUT ACCEPT

När vi jobbar med brandväggsregler i Netfilter måste vi ha kunskap om nätverksprotokoll och portar. Med Netfilter kan vi bygga mycket avancerade nätverksregler där vi i den enklaste formen kan blockera, eller acceptera, nätverkstrafik och i mer avancerade konfigurationer hålla koll på hur många anslutningar en klient gör mot vår server, logga misstänkta anslutningar med mera.

En bra första konfiguration att aktivera på en server som står mot internet med en publik IP-adress, vilket innebär att den är nåbar för alla, är att begränsa åtkomsten till SSH. Så det bara är vi själva som kommer åt den. Ett stort problem med detta blir att vi behöver ha en IP-adress som inte ändrar sig på vår klient, eftersom vi kommer tillåta en specifik IP-adress att ansluta till SSH-tjänsten.

$ sudo iptables -I INPUT -s 212.213.214.215/32 -p tcp --dport 22 -j ACCEPT
$ sudo iptables -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     tcp  --  212.213.214.215/32   anywhere             tcp dpt:ssh

-s 212.213.214.215/32 anger vilken source regeln gäller för. All trafik som kommer från den här IP-adressen kommer träffa regeln om -p tcp protokollet är TCP och --dport 22 destinationsporten är 22. Det är tjänsten SSH som lyssnar på port 22/tcp och trafik från IP-adressen 212.213.214.215 som skall till SSH på vår server matchar regeln. När vi får en match kommer vi antagligen acceptera inkommande trafik (ACCEPT) eller neka den (DROP). I det här fallet, -j ACCEPT, accepterar vi trafiken. Vi tillåter IP-adressen att ansluta till SSH-tjänsten.

Nästa regel vi behöver sätta är policyn för inkommande trafik. Som det är nu tillåter vi all trafik (policy ACCEPT):

$ sudo iptables -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     tcp  --  212.213.214.215/32   anywhere             tcp dpt:ssh

För att sätta policyn till DROP (neka all trafik) skriver vi:

$ sudo iptables -P INPUT DROP

Det här innebär att våra regler ser ut så här:

$ sudo iptables -L INPUT
Chain INPUT (policy DROP)
target     prot opt source               destination
ACCEPT     tcp  --  212.213.214.215/32   anywhere             tcp dpt:ssh

Policyn är satt till DROP, vilket innebär att om vi inte hittar en matchande regel kommer vi neka trafik in till servern. Vi har en regel, den för IP-adressen 212.213.214.215 som tillåter trafik till SSH. Det här betyder att om vi kommer från den IP-adressen och försöker ansluta till SSH-tjänsten så kommer vi tillåtas ansluta, medan all annan inkommande trafik kommer nekas.

Om vi gör en anslutning från servern ut mot internet, eller en annan maskin i vårt nätverk, kommer svaret att komma in genom brandväggen. Vi behöver alltså inte sätta regler på INPUT för sådan trafik som initeras från oss själva.

Vi har inte sparat reglerna permanent, så om vi skulle låsa oss ute från servern kan vi starta om servern med hjälp av en kontrollpanel om vi har tillgång till en sådan. För att spara reglerna permanent använder vi kommandot iptables-save och skickar utmatningen till en fil :

$ sudo iptables-save
:INPUT ACCEPT [340:75177]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [1073:121710]
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
COMMIT
$ sudo iptables-save > iptables.rules

För att läsa in reglerna igen använder vi kommandot iptables-restore tillsammans med filnamnet på den fil (som vi skapat med iptables-save) vi vill läsa in :

$ sudo iptables-restore iptables.rules

Att administrera Netfilter med hjälp av iptables är ganska krångligt och i Ubuntu finns det ett kommando som heter ufw (Uncomplicated FireWall) som gör administrationen av brandväggen enklare.

Kommandot ufw status visar status för brandväggen. När den är avstängd ser det ut så här:

$ sudo ufw status
Status: inactive

Många av servertjänsterna vi installerar i Ubuntu kommer också lägga till profiler som innehåller information om hur brandväggen skall konfigureras. Eftersom vi har installerat SSH finns det en sådan profil att se:

$ sudo ufw app list
Available applications:
  OpenSSH

För att se information om profilen använder vi kommandot ufw app info OpenSSH, där OpenSSH är namnet på profilen som vi får fram med ufw app list:

$ sudo ufw app info OpenSSH
Profile: OpenSSH
Title: Secure shell server, an rshd replacement
Description: OpenSSH is a free implementation of the Secure Shell protocol.

Port:
  22/tcp

För att aktivera (tillåta) nätverkstrafik enligt profilen OpenSSH använder vi ufw allow:

$ sudo ufw allow ssh
Rules updated
Rules updated (v6)

Här använder vi ssh, men vi skulle kunna använda OpenSSH (som tjänsten heter i listan) också.

Nu sätter vi policyn för inkommande trafik till deny:

$ sudo ufw default deny incoming
Default incoming policy changed to 'deny'
(be sure to update your rules accordingly)

För att aktivera brandväggen använder vi kommandot ufw enable:

$ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup

Nu kan vi se status för brandväggen med kommandot ufw status:

$ sudo ufw status
Status: active

To                      Action      From
--                      ------      ----
22/tcp                  ALLOW       Anywhere
OpenSSH                 ALLOW       Anywhere
22/tcp (v6)             ALLOW       Anywhere (v6)
OpenSSH (v6)            ALLOW       Anywhere (v6)

Brandväggen är aktiverad och tillåter inkommande trafik till tjänsten ssh.

Vartefter vi installerar nätverkstjänster i Ubuntu (som till exempel webbservern Apache httpd) kommer vi få in fler policies i ufw app list som vi kan aktivera.

Om vi vill öppna upp för trafik på en specifik port på vår server lägger vi till den med kommandot ufw, låt oss lägga till port 80 med tcp-protokollet och port 53 med udp-protokollet:

$ sudo ufw allow 80/tcp
Rule added
Rule added (v6)
$ sudo ufw allow 53/udp
Rule added
Rule added (v6)

Med kommandot ufw status verbose kan vi se att port 80/tcp och port 53/udp är öppna (allow in).

$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

To                          Action      From
--                          ------      ----
22/tcp                      ALLOW IN    Anywhere
22/tcp (OpenSSH)            ALLOW IN    Anywhere
80/tcp                      ALLOW IN    Anywhere
53/udp                      ALLOW IN    Anywhere
22/tcp (v6)                 ALLOW IN    Anywhere (v6)
22/tcp (OpenSSH (v6))       ALLOW IN    Anywhere (v6)
80/tcp (v6)                 ALLOW IN    Anywhere (v6)
53/udp (v6)                 ALLOW IN    Anywhere (v6)

I filen /etc/service kan vi se alla kända portar och vilka protokoll de kör och vilka applikationer de tillhör.

Ibland vill vi öppna upp för en serie med nätverksportar. Låt oss säga port 20000 - 21000, då skulle vi kunna skriva så här:

$ sudo ufw allow 10000:20000/tcp

I exemplet använder vi protokollet tcp, vill vi istället använda udp skriver vi 10000:20000/udp. Vill vi lägga till både tcp och udp får vi göra det med två kommandon: ett för tcp och ett för udp.

Säg att vi har en specifik IP-adress som vi vill tillåta komma åt alla på portar på vår server, till exempel vår fasta IP- adress på kontoret. Då kör vi kommandot ufw allow from och den IP-adress som vi vill tillåta:

$ sudo ufw allow from 201.202.203.204

Om det är en specik port, tjänst, som vi vill tillåta för IP-adressen lägger vi till to any port , till exempel för ssh- tjänsten:

$ sudo ufw allow from 201.202.203.204 to any port 22

Om vi vill tillåta ett helt subnät kan vi ange det också. Säg att vi har ett kontorsnätverk - 192.168.1.0/24 - som vi vill tillåta ansluta till ssh-tjänsten. Då skriver vi följande kommando:

$ sudo ufw allow from 192.168.1.0/24 to any port 22

Om vi vill neka trafik till en specifik port (eller tjänst), eller en specik IP-adress använder vi deny istället för allow. Det kan se ut så här:

$ sudo ufw deny ssh
$ sudo ufw deny from 201.202.203.204
$ sudo ufw deny from 201.202.203.204 to any port 22

Vill vi ta bort regler vi skapat kan vi använda ufw delete, tillsammans med tjänstens namn (ssh) eller port (22) :

$ sudo ufw delete allow ssh
$ sudo ufw delete allow 22

Med kommandot ufw status numbered får vi fram en lista över alla regler som är satta, dessa regler blir också numrerade:

$ sudo ufw status numbered
Status: active

     To                         Action          From
     --                         ------          ----
[ 1] 22/tcp                     ALLOW IN        Anywhere
[ 2] OpenSSH                    ALLOW IN        Anywhere
[ 3] 80/tcp                     ALLOW IN        Anywhere
[ 4] 53/udp                     ALLOW IN        Anywhere
[ 5] 10000:20000/tcp            ALLOW IN        Anywhere
[ 6] 80                         DENY IN         1.2.3.4
[ 7] 22/tcp (v6)                ALLOW IN        Anywhere (v6)
[ 8] OpenSSH (v6)               ALLOW IN        Anywhere (v6)
[ 9] 80/tcp (v6)                ALLOW IN        Anywhere (v6)
[10] 53/udp (v6)                ALLOW IN        Anywhere (v6)
[11] 10000:20000/tcp (v6)       ALLOW IN        Anywhere (v6)

Säg att vi vill ta bort blockeringen av port 80 för IP-adressen 1.2.3.4 i exemplet ovanför. Vi ser att det står [ 6] i början av den raden. Det är regel nummer 6 och vi tar bort den genom att skriva:

$ sudo ufw delete 6
Deleting:
 deny from 1.2.3.4 to any port 80
Proceed with operation (y|n)? y
Rule deleted

Tittar vi på listan med regler igen, ser det ut så här:

$ sudo ufw status numbered
Status: active

     To                         Action          From
     --                         ------          ----
[ 1] 22/tcp                     ALLOW IN        Anywhere
[ 2] OpenSSH                    ALLOW IN        Anywhere
[ 3] 80/tcp                     ALLOW IN        Anywhere
[ 4] 53/udp                     ALLOW IN        Anywhere
[ 5] 10000:20000/tcp            ALLOW IN        Anywhere
[ 6] 22/tcp (v6)                ALLOW IN        Anywhere (v6)
[ 7] OpenSSH (v6)               ALLOW IN        Anywhere (v6)
[ 8] 80/tcp (v6)                ALLOW IN        Anywhere (v6)
[ 9] 53/udp (v6)                ALLOW IN        Anywhere (v6)
[10] 10000:20000/tcp (v6)       ALLOW IN        Anywhere (v6)

Regeln för att blockera IP-adressen 1.2.3.4 från att komma åt port 80 är borttagen.

Om vi skulle vilja radera alla våra regler vi skapat i brandväggen kan vi köra kommandot ufw reset, som tömmer regellistan:

$ sudo ufw reset
Resetting all rules to installed defaults. This may disrupt existing ssh
connections. Proceed with operation (y|n)? y

När vi kör reset kommer brandväggen också inaktiveras. För att starta den igen använder vi kommandot ufw enable. Tänk på att vi inte har regeln för att komma åt ssh (port 22) som standard, så om vi aktiverar brandväggen på en server i en cloudtjänst kommer vi inte längre kunna ansluta till den servern med ssh.

Om vi vill behålla våra regler, men inaktivera brandväggen, kan vi använda kommandot ufw disable. Det stänger ned brandväggen och tillåter all trafik in till servern, men behåller reglerna vi skapat - så nästa gång vi aktiverar brandväggen (ufw enable) kommer reglerna att vara aktiverade.

När vi jobbar med brandväggar är en bra policy att vi låser ned allt (sätter policyn till deny eller drop) och öppnar upp så lite vi kan. SSH till exempel, är till för att vi skall kunna ansluta till servern och köra kommandon. Det behöver inte hela internet ha tillgång till. En webbserver däremot, där vi kör vår webbplats, behöver hela internet ha tillgång till. Så port 80 (http) och 443 (https) kanske behöver vara öppna på servern.

Kanske använder vi en jumpstation, en maskin som är uppsatt enbart för att vi skall kunna ansluta till den och sedan hoppa vidare, för att ansluta till ett privat nätverk i vår molntjänst eller i vårt datacenter. Därifrån kan vi sedan komma åt ssh på webbservern.

Även om vi använder en extern brandvägg, så som en hårdvaruappliance i datacentret eller en mjukvarubaserad i en molntjänst, är det bra att använda den lokala brandväggen på linuxservern också. Om någon kommer in i ditt privata nätverk (som körs i datacentret eller i molnet) behöver vi ju inte bjuda på full åtkomst överallt.

Ett exempel på konfiguration för en webbserver, där vi också vill kunna ansluta med ssh för att till exempel uppgradera Linux. I exemplet tänker vi oss att vi har IP-adressen 201.202.203.204 på vår egen maskin, så det är bara den som skall kunna ansluta till ssh.

$ sudo ufw disable
$ sudo ufw default deny incoming
$ sudo ufw default allow outgoing
$ sudo ufw allow from 201.202.203.204 to any port 22
$ sudo ufw allow http
$ sudo ufw allow https
$ sudo ufw enable

Notera hur vi inaktiverar (disable) brandväggen först. Sedan ser vi till att vi öppnar upp för trafik mot port 22 (ssh) och webbservern (http och https) för att slutligen aktivera brandväggen (enable). Det här är en bra grundläggande konfiguration för en webbserver.

fail2ban

När vi har nätverksportar öppna ut mot internet kommer andra hitta oss. Det finns tusentals datorer ute på internet som letar efter öppna maskiner som de kan attackera och ta över. För att göra det lite svårare för dem kan vi använda ett program som heter fail2ban, som är mycket effektivt på att stoppa intrångsförsök. fail2ban kan till exempel titta på misslyckade inloggningsförsök mot ssh-tjänsten och blockera IP-adressen som försöker logga in.

För att installera fail2ban använder vi kommandot apt:

$ sudo apt install fail2ban

Konfigurationen av fail2ban görs i katalogen /etc/fail2ban/, där vi hittar en fil som heter jail.conf. Eftersom den filen kan skrivas över av uppdateringar av fail2ban skapar vi en egen fil, jail.local i samma katalog och där gör vi vår konfiguration. Allt som står i jail.local-filen kommer gå över det som står i jail.conf-filen.

Vi kan titta i filen jail.conf för att se vad vi kan konfigurera, allt som står i filen kan vi skriva i vår jail.local-fil. Filen har en global inställning som gäller för alla tjänster vi konfigurerar i fail2ban, den globala inställningen hittar vi under [default]. Sedan kommer olika avsnitt med konfiguration för en mängd olika tjänster som vi skulle kunna köra på vår server, nu är vi mest intresserade av [sshd].

Här kommer vi titta på hur vi kan skydda ssh-tjänsten från inloggningsförsök. Vårt mål blir att låsa ute alla som misslyckas logga in på ssh tre gånger under en timme och vi vill att de skall låsas ute 24 timmar.

Till att börja med kanske vi vill hindra oss själva från att bli utlåsta och det vill vi nog för alla tjänster vi kan skydda med fail2ban, så vi skapar en ny fil jail.local och skriver in följande i den:

[default]
ignoreip = 201.202.203.204/32

Vi behöver också berätta för fail2ban att vi vill använda ufw för att hantera de blockerade IP-adresserna, det gör vi genom att ställa in actionban och actionunban:

[default]
ignoreip = 201.202.203.204/32
actionban = ufw.conf
actionunban = ufw.conf

Filen ufw.conf kan vi titta i om vi vill se vad den gör, öppna i så fall filen /etc/fail2ban/action.d/ufw.conf.

Sedan är det dags att skapa konfigurationen för ssh. Här ser vi till att kontrollen av ssh är aktiverad (enabled) och vi anger vilken port vi skall kontrollera (port). Vi anger också ett filter som innehåller de mönster fail2ban skall leta efter för att identifera misslyckade försök att komma åt tjänsten ssh. Vårt mål var låsa ute alla som misslyckats logga in på ssh tre gånger under en timme och vi skall låsa ut dem i 24 timmar. När vi anger tid till fail2ban är det i enheten sekunder. Så en timme blir 3600 sekunder och vi konfigurerar det med findtime. Antalet försök som är tillåtna konfigurerar vi med maxretry. Slutligen konfigurerar vi hur länge de skall vara utlåsta med bantime, i vårt fall sätts det till 86400 sekunder som är 24 timmar.

[sshd]
enabled = true
port = ssh
filter = sshd
findtime = 3600
maxretry = 3
bantime = 86400

När vi är klara skall filen jail.local se ut så här:

[default]
ignoreip = 201.202.203.204/32

actionban = ufw.conf
actionunban = ufw.conf

[sshd]
enabled = true
port = ssh
filter = sshd
findtime = 3600
maxretry = 3
bantime = 86400

För att läsa in konfigurationen startar vi om tjänsten fail2ban med kommandot systemctl:

$ sudo systemctl restart fail2ban

För att se att tjänsten är igång kan vi använda följande kommando:

$ sudo systemctl status fail2ban

Vi kan också titta i systemloggen efter meddelanden från fail2ban:

$ sudo journalctl -u fail2ban

fail2ban loggar också till filen /var/log/fail2ban.log där vi kan se lite mer vad som händer, till exempel med kommandot tail:

$ tail /var/log/fail2ban.log

För att se status på fail2ban använder vi kommandot fail2ban-client:

$ sudo fail2ban-client status
Status
|- Number of jail:      1
 - Jail list:   sshd

Här kan vi se att vi har ett jail (varje applikation vi övervakar kallas jail) aktivt och att det är sshd. Vill vi se status för ett specifikt jail, till exempel sshd ovanför, skriver vi så här:

$ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     0
|   - File list:        /var/log/auth.log
 - Actions
   |- Currently banned: 0
   |- Total banned:       0
    - Banned IP list:

Under Actions ser vi hur många IP-adresser vi har som är blockerade (banned), i det här fallet har vi inga alls. De kommer dyka upp om de gör tre misslyckade inloggningsförsök till tjänsten ssh under en och samma timme. Precis som vi konfigurerade fail2ban.

Diskar och filsystem

En hårddisk

En hårddisk är uppbyggd av flera elektromagnetiska skivor som sitter på en axel. En läs- och skrivarm rör sig hela tiden över de roterande skivorna.

$ sudo vol_id --uuid /dev/sda1
754fc3df-ad71-4ccc-ae86-80e011056241

Hantera partitioner

Det finns flera olika sätt att partionera lagringsmedia i Linux. Vi kommer titta på två av dem: fdisk och cfdisk.

fdisk

För att se alla partitioner vi har på hårddiskar som är anslutna till datorn kan vi använda kommandot fdisk -l :

$ sudo fdisk -l

Ett annat sätt att visa diskar och partitioner är att använda kommandot lsblk :

$ sudo lsblk

För att hantera en disk med kommandot fdisk startar vi fdisk med diskens namn:

$ sudo fdisk /dev/sda
  • p visar partitioner på disken
  • m visar hjälp
  • n skapar en ny partition
  • q avslutar fdisk
An icon indicating this blurb contains a warning

Var säker på vad du gör med fdisk, för gör du fel kan du förlora data på din disk!

cfdisk

Kommandot fdisk är lite omständigt att jobba med. Ett alternativ är att använda kommandot cfdisk som är enklare. Vi startar det på samma sätt som vi startade kommandot fdisk :

$ sudo cfdisk /dev/sda
  • ? ger oss hjälp
  • q avslutar

Filen /etc/fstab

Filen /etc/fstab innehåller information om hur och var diskar och nätverksenheter (NFS, CIFS, ..) skall monteras i filsystemsträdet. Filen används av systemet för att hitta vilka diskar som skall monteras vid uppstart av systemet och vi kan också skapa egna monteringspunkter till enheter som vi vill montera enklare.

Filen består av flera rader med sex fält som är separerade med mellanslag eller tabb:

$ cat /etc/fstab
/dev/sda1    /            ext4  defaults    0 0
LABEL=UEFI   /boot/efi    vfat  defaults    0 0

Fälten:

  • /dev/sda1 är enheten. Det kan vara /dev/sda1 som här, eller en UUID eller en LABEL.
  • / är monteringspunkten, det är här filsystemet kommer monteras i filsystemsträdet.
  • ext4 anger filsystemet
  • defaults anger alternativ till filsystemet. Här kan vi ibland hitta ro (read-only), rw (read-write) och andra alternativ.
  • 0 hör ihop med ett kommando som heter dump, en nolla här betyder att dump inte skall göra backup på filsystemet.
  • 0 anger vilken ordning filsystemet skall kontrolleras vid uppstart. / bör ha en 1:a här, andra monteringspunkter bör ha en 2:a om de skall kontrolleras, eller en 0:a om de install kontrolleras.

För att montera alla filsystem som definerats i filen /etc/fstab skriver vi:

$ sudo mount -a

Vill bara montera /home som vi definerat i filen /etc/fstab skriver vi:

$ sudo mount /home

RAID

Redundant Array of Independent Disks (när RAID kom hette det Inexpensive och inte Independent, vilket man kan läsa och höra fortfrande).

RAID är indelade i olika nivåer och de vanligaste nivåerna vi använder är:

  • RAID 0 - Stripe set
  • RAID 1 - Mirroring
  • RAID 5 - Stripe set med paritet
  • RAID 6 - Stripe set med dubbel paritet
  • RAID 10 - Mirroring och stripe set

RAID 0 - Stripe Set

Stripe set är den enklaste formen av RAID vi kan använda. För att bygga en stripe set behöver vi minst två diskar och tekniken fungerar så att när vi skriver data till stripe setet skrivs det första blocket på disk 1, det andra blocket på disk 2, det tredje blocket på disk 1, och så vidare. Fördelen med stripe set är att vi ökar hastigheten på diskläsningar och -skrivningar eftersom vi sprider ut datat på flera diskar. Vi får också en större volym att lagra data på.

Om vi har två stycken två (2) terabyte stora diskar och sätter upp dem i en stripe set kommer vi få en volym om fyra (4) terabyte där vi kan lagra data.

Nackdelen med stripe set är att om en av diskarna som ingår i volymen går sönder förlorar vi all data på hela volymen. Därför kom RAID 5 och RAID 6 som erbjuder stripe set med paritet.

Ibland kan vi se begreppet JBOD, Just a Bunch Of Disks, i samband med RAID-lösningar. JBOD fungerar i princip som RAID 0, med skillnaden att data skrivs först på disk 1 tills den blir full, då fortsätter vi skriva på disk 2. Tills den blir full. Fördelen med JBOD är att vi kan få större volymer att lagra data på eftersom vi slår ihop flera diskar till en och samma volym.

RAID 1 - Mirroring

Med mirroring speglar vi data på två diskar. Mirroring kräver minst två (2) diskar och all data som skrivs på disk 1 skrivs också på disk 2. Det här gör att om den ena disken går sönder så finns vår data kvar på den andra disken.

RAID 5 - Stripe set med paritet

I RAID 5 använder vi minst tre (3) diskar och bygger en stripe set på dem (som i RAID 0). Vi får partitet på stripe setet vilket är kontrolldata som sprids på diskarna. Om en disk går sönder kan vi återskapa data som fanns på den disken genom att använda data och partitet som finns på de andra diskarna.

RAID 5 är vanlig i servrar eftersom det erbjuder både bra prestanda på läsningar och en viss dataintegritet i och med pariteten. Pariteten använder lika mycket utrymme som en disk, men är utspridd över alla diskarna, vilket innebär att vi tappar en disks lagringskapacitet.

Om vi har tre (3) stycken två (2) terabytes diskar och använder RAID 5 på dem kommer vi alltså att ha fyra (4) terabyte utrymme att lagra data på, trots att de tre diskarna tillsammans blir sex (6) terabyte.

RAID 6 - Stripe set med dubbel paritet

RAID 6 fungerar i princip som RAID 5, men i RAID 6 utökas pariteten till den dubbla. Pariteten skrivs på två ställen i volymen och vi förlorar således utrymme motsvarande två (2) diskar. Det här betyder att två diskar kan gå sönder utan att vi har dataförlust. För att kunna använda RAID 6 behöver vi ha minst fyra (4) diskar.

Om vi har fyra (4) stycken två (2) terabytes diskar och använder RAID 6 på dem kommer vi ha fyra (4) terabyte att lagra data på, de andra två (2) terabyten används för pariteten.

RAID 10 - Mirroring och stripe set

I RAID 10 (ibland kallat RAID 1+0) kombinerar vi speglingen från RAID 1 med stripe set från RAID 0. Det betyder att vi speglar en stripe set. För att kunna göra detta behöver vi minst fyra diskar som vi delar upp i par om två och två där vi kör stripe set på det ena paret och speglar det till det andra paret.

Fördelen med den här lösningen är att det krävs mycket innan vi skall ha dataförlust på vår volym. Nackdelen är naturligtvis att lösningen är dyr, vi behöver dubbelt så mycket disk mot vad vi kan lagra på dem.

Om vi har fyra (4) stycken två (2) terabyte stora diskar och använder RAID 10 på dem kommer vi kunna lagra fyra terabyte data på dem. De fyra terabyte som vi inte kan använda för datalagring är speglingen.

Logical Volume Manager (LVM)

Logical Volume Manager (LVM) är ett verktyg för att hantera virtuella lagringsenheter (hårddiskar). LVM består av tre delar: Physical Volume (PV), Volume Group (VG) och Logical Volume (LV). Physical Volume motsvarar de fysiska enheterna, som till exempel en hårddisk. En Volume Group innehåller en eller flera Physical Volumes för att bilda en lagringspool, från vilken vi kan skapa Logical Volumes.

Skapa Physical Volumes

# fdisk -l
Disk /dev/vdb: 1 GiB, 1073741824 bytes, 2097152 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

Disk /dev/vdc: 1 GiB, 1073741824 bytes, 2097152 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

Disk /dev/vdd: 1 GiB, 1073741824 bytes, 2097152 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
# pvcreate /dev/vdb
  Physical volume "/dev/vdb" successfully created.
# pvcreate /dev/vdc
  Physical volume "/dev/vdc" successfully created.
# pvcreate /dev/vdd
  Physical volume "/dev/vdd" successfully created.

Vi kan också ange alla enheterna på en och samma rad:

# pvcreate /dev/vdb /dev/vdc /dev/vdd
  Physical volume "/dev/vdb" successfully created.
  Physical volume "/dev/vdc" successfully created.
  Physical volume "/dev/vdd" successfully created.

För att se att vår Physical Volume skapades kan vi använda kommandot pvdisplay, här visar vi bara enheten /dev/vdb:

# pvdisplay /dev/vdb 
  "/dev/vdb" is a new physical volume of "1.00 GiB"
  --- NEW Physical volume ---
  PV Name               /dev/vdb
  VG Name               
  PV Size               1.00 GiB
  Allocatable           NO
  PE Size               0   
  Total PE              0
  Free PE               0
  Allocated PE          0
  PV UUID               Oz14rm-VVjd-XHmn-PxE0-vaej-FS1b-NgUc2D
   

Vill vi visa alla Physical Volumes vi har skriver vi bara pvdisplay:

# pvdisplay

Nu behöver vi lägga till våra Physical Volumes i en Volume Group.

Skapa Volume Group

The valid characters for VG and LV names are: a-z A-Z 0-9 + _ . - får inte börja med -

# vgcreate data /dev/vdb /dev/vdc 
  Volume group "data" successfully created
# vgdisplay data
  --- Volume group ---
  VG Name               data
  System ID             
  Format                lvm2
  Metadata Areas        2
  Metadata Sequence No  1
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                0
  Open LV               0
  Max PV                0
  Cur PV                2
  Act PV                2
  VG Size               1.99 GiB
  PE Size               4.00 MiB
  Total PE              510
  Alloc PE / Size       0 / 0   
  Free  PE / Size       510 / 1.99 GiB
  VG UUID               SQU8hS-Zi76-gzGs-Bn1F-Mp0K-OJPd-t0QGAw

Skapa Logical Volume

Vi skapar en logical volume med kommandot lvcreate :

$ sudo lvcreate -L 500M -n mina_filer data
  Logical volume "mina_filer" created.

Skapa ett filsystem

När vi partionerat vår disk måste vi skapa ett filsystem på partitionen innan vi kan börja använda disken. Vi använder kommandot mkfs för att skapa filsystem i Linux.

Linux har stöd för många olika typer av filsystem, en del kan vara inkompilerade i kärnan och andra kan finnas tillgängliga som moduler. Modulerna, som kan ses som drivrutiner, finns listade i katalogen /lib/modules/DIN_VERSION/kernel/fs[footnote:Du får fram din kärnas versionsnamn med kommandot `uname -r]. Vi kommer titta på ett par stycken av dessa filsystem: ext2, ext3 och ext4, iso9660, UDF, FAT och NTFS.

Ett journalförande filsystem innebär att filsystemet håller en journal över förändringarna som sker i filsystemet. När vi sparar en fil till filsystemet håller filsystemet reda på hur långt det kommit med skrivningen och skulle systemet krascha eller plötsligt dö kommer journalen användas för att kontrollera filsystemet och återställa det till ett korrekt tillstånd. Vi behöver alltså inte köra en komplett filsystemskontroll på ett journalförande filsystem, vilket innebär att vi minskar tiden som krävs för att komma igång efter en krasch igen.

Linux har stöd för flera olika typer av filsystem som används över nätverk. Bland annat Network File System (NFS) och Common Internet File System (CIFS) som används av Windows. Ibland kan man höra talas om Samba som är den äldre varianten av CIFS.

ext2, ext3 och ext4

  • Ext2, second extended filesystem, var det filsystem som gällde i Linux under många år. Ext2 är ett ett ganska enkelt filsystem som har stöd för rättigheter på filnivå. Idag används ext2 enbart i specialfall, eftersom ext4 ersatt det.

  • Ext3, third extended filesystem, är ett journalförande filsystem. Filsystemet lagrar information om filer och kataloger i inoder. I en inode finns information om hur stor filen är, vilken enhet filen finns på, vem som äger filen, vilken grupp filen tillhör, vilka rättigheter som är satta på filen, vilken tid filen skapades (ctime), när den senast förändrades (mtime) och när den senast användes (atime), vilka länkar som pekar på filen och slutligen var på enheten filen ligger lagrad.

  • Filnamnen kan bestå av 255 bytes.
  • Filnamnen är skiftlägeskänsliga.
  • Filstorleken kan vara upp till 2 TiB.
  • Den största volymstorleken som kan skapas är 16 TiB.
  • Ext3 är ett journalförande filsystem.
  • Ext4, fourth extended filesystem, är en utökning av filsystemet ext3 vilket innebär att det också är ett journalförande filsystem. Den stora skillnaden mellan ext3 och ext4 är prestandaökningar och bättre hantering av fragmentering (filerna hålls ihop på disken). Ext4 ger också stöd för större filsystem, upp till 1 EiB (exabyte).
  • Filnamnen kan bestå av 255 bytes.
  • Filnamnen är skiftlägeskänsliga.
  • Filstorleken kan vara upp till 16 TiB.
  • Den största volymstorleken som kan skapas är 1 EiB (exabyte).
  • Ext4 är ett journalförande filsystem.
  • Bättre prestanda än ext3.

iso9660

ISO9660 är det filsystem som används på CD-ROM skivor. ISO9660 är indelad i tre nivåer där Level 1 är den ursprungliga standarden som stödjer 8.3 tecken, dvs åtta tecken i filnamnet och tre tecken i en filändelse. Level 2 och Level 3 utökar detta till 255 tecken.

  • Filnamnen kan vara 8.3 tecken långa (Level 1) och upp till 180 tecken (Level 2 och Level 3)
  • Filstorleken på lagrade filer kan vara 4 GiB (Level 1 och Level 2) och 8 TiB (Level 3)
  • Den största partitionen vi kan skapa med ISO9660 är 8 TiB

UDF

Universal Disk Format Filesystem (UDF) är ett filsystem som ofta används på CD-RW (läs/skrivbara skivor) och DVD.

  • Filnamn kan vara 255 tecken långa.
  • Filstorleken kan vara 16 EiB.
  • Partitionerna kan vara 2 TiB på hårddiskar och 8 TiB på optisk media.

FAT

FAT, eller File Allocation Table, finns i tre olika varianter: FAT12, FAT16 och FAT32. På en FAT-volym kan man som mest ha 512 filer i rotkatalogen, i underkataloger kan vi ha upp till 65 535 filer per katalog. En FAT-volym kan vara 8 TiB stor och lagra filer som är mest 4 GiB stora.

Filallokeringstabellen (FAT) innehåller information om filnamnet, filattributen, filstorlek, när filen senast förändrades och vilket kluster filen börjar på. FAT har saknar säkerhet och vi kan inte sätta rättigheter på filer och kataloger i FAT. FAT har stöd för filattribut som read-only, hidden, system och archive. Read-only innebär att filen är skrivskyddad och enbart går att läsa, hidden innebär att filen är dold, system att det är en systemfil och archive är en flagga som sätts när filen är arkiverad. Archive används av backup-program.

FAT har stöd för 8.3 tecken i filnamnen. Det innebär att filnamnet kan vara åtta (8) tecken och ha en filändelse på tre (3) tecken. Med stödet för långa filnamn (VFAT) kan vi ha upp till 255 tecken totalt i filnamnet. Den enda fördelen FAT har jämfört med NTFS är på små diskar (under 512 MiB), har vi en disk som är större än 512 MiB bör vi köra NTFS om vi kör operativsystemet Microsoft Windows.

I Linux heter modulen för att få stöd för FAT just fat. För att få stöd för långa filnamn laddar vi istället modulen vfat. Linux klarar av att både läsa och skriva till FAT.

  • Filnamnen kan vara 8.3 tecken eller 255 tecken om man har stöd för utökade filnamn.
  • Filnamnen är inte skiftlägeskänsliga.
  • Filstorleken kan vara: FAT12–32 MiB, FAT16–2 GiB och FAT32–4 GiB.
  • Den största volymstorleken som kan skapas är: FAT12–32 MiB, FAT16–2 GiB och FAT32–8 TiB.

NTFS

NTFS, New Technology File System, kom första gången i Windows NT4. NTFS är ett journalförande filsystem och informationen om filerna som filnamnet, storleken och rättigheter lagras i en Master File Table (MFT).

NTFS har stöd för filkompression, vilket innebär att en fil som sparas på enheten komprimeras automatiskt (om du aktiverat det). NTFS känner också själv av trasiga sektorer i filsystemet och flyttar filer därifrån och markerar sektorerna som skadade.

NTFS 3.0, den version som kom med Windows 2000, erbjuder en del utökningar till NTFS som Encrypted File System (EFS), Quota och stöd för att montera enheter i kataloger (Volume Mount Point).

I de versioner av Windows som stödjer NTFS kan vi konvertera ett FAT-filsystem till NTFS med hjälp av kommandot convert.

Linux har ett bra stöd för att läsa NTFS-filsystem, men har lite svårare med att skriva till dem. Det finns idag en del möjligheter att skriva till ett NTFS-filsystem i Linux - men det finns inget riktigt stabilt sätt. Modulen för att läsa NTFS heter ntfs.

  • Filnamnen kan vara 254 tecken långa, filändelsen skiljs åt med en punkt.
  • Filnamnen är inte skiftlägeskänsliga.
  • Filstorleken kan vara 16 EiB.
  • Den största volymstorleken som kan skapas är 16 EiB.
  • NTFS är ett journalförande filsystem.

Mjuka- och hårda länkar

I Linux kan vi skapa mjuka- och hårda länkar i filsystemet. Vi kan se länkar som de genvägar som vi kan skapa i Windows. Istället för att behöva komma ihåg hela sökvägen till en katalog eller en fil skapar vi en genväg till den.

Hård länk

En mjuk länk (symbolic link) är en länk som kan peka på en fil eller en katalog vart som helst i filsystemet. En hård länk (hard link) kan enbart pekas på filer inom samma partition i filsystemet.

En mjuk länk

Övning - skapa en logisk volym

För den här övningen behöver vi skapa tre filer som är 1 GiB stora, vi använder ett kommando som heter fallocate :

$ fallocate -l 1G disk1.img
$ fallocate -l 1G disk2.img
$ fallocate -l 1G disk3.img

Om vi saknar kommandot fallocate behöver vi installera paketet util-linux :

$ sudo apt install util-linux

Nu har vi tre filer som vi skall använda som diskar. Vi skapar loopback-devices av dem med kommandot losetup :

$ sudo losetup /dev/loop41 disk1.img
$ sudo losetup /dev/loop42 disk2.img
$ sudo losetup /dev/loop43 disk3.img

För att kunna skapa en logisk volym behöver vi först skapa physical volumes (pv), så vi använder kommandot pvcreate för detta:

$ sudo pvcreate /dev/loop41
  Physical volume "/dev/loop41" successfully created.
$ sudo pvcreate /dev/loop42
  Physical volume "/dev/loop42" successfully created.
$ sudo pvcreate /dev/loop43
  Physical volume "/dev/loop43" successfully created.

När vi skapat våra tre physical volumes skall vi skapa en volume group av dem, här skapar vi en volume group med namnet VG_TEST som innehåller de tre physical volumes vi skapade precis:

$ sudo vgcreate VG_TEST /dev/loop41 /dev/loop42 /dev/loop43
  Volume group "VG_TEST" successfully created

För att visa vår volume group använder vi kommandot vgdisplay :

$ sudo vgdisplay
  --- Volume group ---
  VG Name               VG_TEST
  System ID
  Format                lvm2
  Metadata Areas        3
  Metadata Sequence No  1
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                0
  Open LV               0
  Max PV                0
  Cur PV                3
  Act PV                3
  VG Size               <2.99 GiB
  PE Size               4.00 MiB
  Total PE              765
  Alloc PE / Size       0 / 0
  Free  PE / Size       765 / <2.99 GiB
  VG UUID               97WzyF-C5sN-xEx9-xvRw-n1Ns-7jHL-cbe8Wk

När vår volume group är skapad är det dags att skapa en logical volume från den, det gör vi med kommandot lvcreate och här skapar vi en 500 MiB stor logical volume som vi döper till DATA:

$ sudo lvcreate -L 500M -n DATA VG_TEST
  Logical volume "DATA" created.
ubuntu@ubuntu:~$ sudo lvdisplay
  --- Logical volume ---
  LV Path                /dev/VG_TEST/DATA
  LV Name                DATA
  VG Name                VG_TEST
  LV UUID                I4hKHh-oMT4-d0Ym-GyB2-15DM-23ZI-Vkgd7I
  LV Write Access        read/write
  LV Creation host, time ubuntu, 2023-10-22 19:15:35 +0200
  LV Status              available
  # open                 0
  LV Size                500.00 MiB
  Current LE             125
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:0

När den logiska volymen är skapad behöver vi formattera den med ett filsystem, vi använder kommandot mkfs för detta:

$ sudo mkfs.ext4 /dev/VG_TEST/DATA
mke2fs 1.45.5 (07-Jan-2020)
Discarding device blocks: done
Creating filesystem with 128000 4k blocks and 128000 inodes
Filesystem UUID: f9d462c2-0220-437e-9c0f-13035306581f
Superblock backups stored on blocks:
    32768, 98304

Allocating group tables: done
Writing inode tables: done
Creating journal (4096 blocks): done
Writing superblocks and filesystem accounting information: done

Nu när vi har ett filsystem på den logiska volymen kan vi montera, och använda, det. Vi skapar en katalog som vi skall använda för att montera filsystemet i:

$ sudo mkdir /mnt/data

Sedan monterar vi filsytemet:

$ sudo mount /dev/VG_TEST/DATA /mnt/data/

Kommandot df kan visa oss hur stor volymen är:

$ df -h | grep data
/dev/mapper/VG_TEST-DATA  468M   24K  433M   1% /mnt/data

Och kommandot mount visar vilket filsystem volymen är formatterad med:

$ mount | grep data
/dev/mapper/VG_TEST-DATA on /mnt/data type ext4 (rw,relatime)

Det var det, nu är det dags att städa upp, vi börjar med att avmontera katalogen /mnt/data. Om detta inte fungerar beror det ofta på att vi är i katalogen, skriv bara cd först för att komma till din hemkatalog så löser det sig ofta:

$ sudo umount /mnt/data

Sedan raderar vi den logiska volymen med kommandot lvremove :

$ sudo lvremove /dev/VG_TEST/DATA
Do you really want to remove and DISCARD active logical volume VG_TEST/DATA? [y/n\
]: y
  Logical volume "DATA" successfully removed

Vi har en volume group att radera också, det gör vi med kommandot vgremove:

$ sudo vgremove VG_TEST
  Volume group "VG_TEST" successfully removed

Volymgruppen byggde vi med tre stycken physical volumes, som vi raderar med kommandot pvremove:

$ sudo pvremove /dev/loop41
  Labels on physical volume "/dev/loop41" successfully wiped.
$ sudo pvremove /dev/loop42
  Labels on physical volume "/dev/loop42" successfully wiped.
$ sudo pvremove /dev/loop43
  Labels on physical volume "/dev/loop43" successfully wiped.

Och slutligen skall vi radera de loopback-devices som vi skapade:

$ sudo losetup -d /dev/loop41
$ sudo losetup -d /dev/loop42
$ sudo losetup -d /dev/loop43