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

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
Kommando Beskrivning
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 vim
:q! Avsluta vi utan att spara ändringar i filen
:wq Spara filen och avsluta vim
ZZ Spara filen och avsluta vim

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
Kommando Beskrivning
^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.

Kommando Beskrivning
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:

Uttryck Beskrivning
[ ] 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å.
` `

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."