Copyright © 2026, William Shotts
Detta verk är licensierat under Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License. För att läsa en kopia av licensen, följ länken ovan eller skriv till Creative Commons, PO Box 1866, Mountain View, CA 94042.
En version av den här boken finns också i tryckt form, utgiven av No Starch Press. Exemplar kan köpas där bra böcker säljs. No Starch Press erbjuder också elektroniska format för vanliga läsplattor. Du når dem här: https://www.nostarch.com.
Linux® är Linus Torvalds registrerade varumärke. Alla andra varumärken tillhör sina respektive ägare.
Den här boken är en del av LinuxCommand.org-projektet, en webbplats för Linuxutbildning och opinionsbildning med målet att hjälpa användare av äldre operativsystem att ta steget in i framtiden. Du kan kontakta LinuxCommand.org på https://linuxcommand.org.
Utgåvehistorik
| Version | Datum | Beskrivning |
|---|---|---|
25.12 |
5 januari 2026 |
Sjunde internetutgåvan |
24.11 |
1 november 2024 |
Sjätte internetutgåvan |
19.01A |
28 januari 2019 |
Femte internetutgåvan (korrigerad innehållsförteckning) |
19.01 |
17 januari 2019 |
Femte internetutgåvan |
17.10 |
19 oktober 2017 |
Fjärde internetutgåvan |
16.07 |
28 juli 2016 |
Tredje internetutgåvan |
13.07 |
6 juli 2013 |
Andra internetutgåvan |
09.12 |
14 december 2009 |
Första internetutgåvan |
Till Karen
Inledning
Jag vill berätta en historia.
Nej, inte historien om hur Linus Torvalds skrev den första versionen av Linuxkärnan 1991. Den berättelsen kan du läsa i massor av Linuxböcker. Och jag tänker inte heller berätta historien om hur Richard Stallman några år tidigare startade GNU-projektet för att skapa ett fritt Unix-liknande operativsystem. Det är också en viktig berättelse, men den finns också med i de flesta andra Linuxböcker.
Nej, jag vill berätta historien om hur du tar tillbaka kontrollen över din dator.
När jag började arbeta med datorer som universitetsstudent i slutet av 1970-talet pågick en revolution. Uppfinningen av mikroprocessorn gjorde det möjligt för vanliga människor som du och jag att faktiskt äga en dator. För många i dag är det svårt att föreställa sig hur världen såg ut när bara storföretag och statliga myndigheter hade hand om alla datorer. Vi kan säga så här: det blev inte mycket gjort.
I dag ser världen helt annorlunda ut. Datorer finns överallt, från små armbandsur till enorma datacenter och allt däremellan. Utöver dessa allestädes närvarande datorer har vi också ett allestädes närvarande nätverk som binder ihop dem. Det har skapat en fantastisk ny tidsålder av personlig handlingskraft och skapande frihet, men under de senaste årtiondena har något annat också hänt. Några få jättelika företag har lagt beslag på kontrollen över merparten av världens datorer och bestämmer vad du får och inte får göra med dem. Lyckligtvis gör människor över hela världen något åt saken. De kämpar för att behålla kontrollen över sina datorer genom att skriva sin egen programvara. De bygger Linux.
Många talar om "frihet" i samband med Linux, men jag tror inte att de flesta riktigt vet vad den friheten betyder. Frihet är makten att bestämma vad din dator gör, och det enda sättet att ha den friheten är att veta vad din dator gör. Frihet är en dator utan hemligheter, en dator där allt går att förstå om du bryr dig tillräckligt mycket för att ta reda på det.
1. Varför använda kommandoraden?
Har du märkt hur det är i filmer när "superhackern" - du vet, personen som tar sig in i en ultrasäker militär dator på mindre än trettio sekunder - sätter sig vid datorn? Hen rör aldrig musen. Det beror på att filmskapare inser att vi människor instinktivt vet att det enda sättet att verkligen få något gjort på en dator är att skriva på ett tangentbord.
De flesta datoranvändare i dag känner bara till grafiska användargränssnitt (GUI) och har fått lära sig av leverantörer och experter att kommandoradsgränssnittet (CLI) är ett skrämmande arv från det förflutna. Det är olyckligt, för ett bra kommandoradsgränssnitt är ett fantastiskt uttrycksfullt sätt att kommunicera med en dator, ungefär som skriftspråket är det mellan människor. Det har sagts att "grafiska användargränssnitt gör enkla uppgifter enkla, medan kommandoradsgränssnitt gör svåra uppgifter möjliga", och det är fortfarande sant.
Eftersom Linux är modellerat efter Unix-familjen av operativsystem delar det också Unix rika arv av kommandoradsverktyg. Unix fick sitt genombrott i början av 1980-talet (även om det utvecklades redan ett decennium tidigare), alltså före den breda spridningen av grafiska användargränssnitt, och utvecklade därför ett omfattande kommandoradsgränssnitt i stället. Faktum är att en av de starkaste anledningarna till att tidiga användare valde Linux framför till exempel Windows NT var just det kraftfulla kommandoradsgränssnittet som gjorde de "svåra uppgifterna möjliga".
2. Vad den här boken handlar om
Den här boken ger en bred överblick över hur det är att "leva" på Linux kommandorad. Till skillnad från vissa böcker som koncentrerar sig på ett enda program, till exempel skalet bash, försöker den här boken förmedla hur du fungerar väl med kommandoradsgränssnittet i ett större sammanhang. Hur fungerar allting? Vad kan det göra? Hur använder man det bäst?
Det här är ingen bok om Linuxsystemadministration. Även om varje seriös diskussion om kommandoraden ofrånkomligen leder in på systemadministration, berör den här boken bara några få administrativa frågor. Däremot förbereder den läsaren för vidare studier genom att ge en stabil grund i användningen av kommandoraden - ett nödvändigt verktyg för varje seriös uppgift inom systemadministration.
Den här boken är tydligt Linuxcentrerad. Många andra böcker försöker bredda sin målgrupp genom att ta med andra plattformar, till exempel generisk Unix och macOS. Då "späder" de ut innehållet till att främst handla om allmänna ämnen. Den här boken behandlar i stället enbart moderna Linuxdistributioner. Nittiofem procent av innehållet är användbart också för användare av andra Unix-liknande system, men boken är tydligt riktad till den moderna Linuxanvändaren på kommandoraden.
3. Vem bör läsa den här boken
Den här boken är till för nya Linuxanvändare som har bytt från andra plattformar. Troligen är du en van användare av någon version av Microsoft Windows. Kanske har din chef bett dig administrera en Linuxserver, eller så är du på väg in i den spännande världen av enkortsdatorer (SBC), som Raspberry Pi. Kanske är du bara en skrivbordsanvändare som är trött på alla säkerhetsproblem och vill ge Linux en chans. Det är helt i sin ordning. Alla är välkomna.
Med det sagt finns det ingen genväg till upplysning i Linuxvärlden. Att lära sig kommandoraden är krävande och kräver verklig ansträngning. Det är inte så mycket att den är svår - det är att den är så omfattande. Ett genomsnittligt Linuxsystem har bokstavligen tusentals program som du kan använda på kommandoraden. Se dig varnad: att lära sig kommandoraden är inget man gör i förbifarten.
Samtidigt är det oerhört givande att lära sig Linux kommandorad. Om du tycker att du är en "van användare" nu, vänta bara. Du vet ännu inte vad verklig kraft är. Och till skillnad från många andra datorkunskaper håller kunskapen om kommandoraden länge. De färdigheter du lär dig i dag kommer fortfarande att vara användbara om tio år. Kommandoraden har stått tidens prov.
Det förutsätts också att du inte har någon programmeringserfarenhet, men oroa dig inte - vi börjar den resan också.
4. Vad boken innehåller
Materialet presenteras i en noga vald ordning, ungefär som om en handledare satt bredvid dig och ledde dig framåt. Många författare behandlar det här stoffet på ett "systematiskt" sätt och går uttömmande igenom varje ämne i ordning. Det är begripligt ur författarens perspektiv, men kan vara mycket förvirrande för nya användare.
Ett annat mål är att göra dig bekant med Unix sätt att tänka, som skiljer sig från Windows sätt att tänka. På vägen gör vi några utvikningar för att hjälpa dig förstå varför vissa saker fungerar som de gör och hur de blev så. Linux är inte bara ett stycke programvara; det är också en liten del av den större Unixkulturen, med ett eget språk och en egen historia. Jag kanske också bjuder på en och annan harang.
Boken är uppdelad i fyra delar, som var och en tar upp en del av upplevelsen på kommandoraden:
-
Del 1 - Lära sig skalet inleder vår utforskning av kommandoradens grundläggande språk, inklusive sådant som kommandostruktur, navigering i filsystemet, redigering på kommandoraden samt hur man hittar hjälp och dokumentation för kommandon.
-
Del 2 - Konfiguration och miljö behandlar redigering av konfigurationsfiler som styr datorns beteende från kommandoraden.
-
Del 3 - Vanliga uppgifter och viktiga verktyg utforskar många av de vardagliga uppgifter som ofta utförs från kommandoraden. Unix-liknande operativsystem, som Linux, innehåller många "klassiska" kommandoradsprogram som används för kraftfull databehandling.
-
Del 4 - Skriva skalskript introducerar skalprogrammering - en visserligen enkel, men lättlärd, teknik för att automatisera många vanliga datoruppgifter. När du lär dig skalprogrammering blir du bekant med begrepp som går att använda i många andra programmeringsspråk.
5. Så läser du den här boken
Börja i början av boken och följ den till slutet. Den är inte skriven som ett uppslagsverk; den är snarare som en berättelse med början, mitt och slut.
6. Förkunskaper
För att använda den här boken behöver du bara en fungerande Linuxinstallation. Du kan få det på två sätt:
-
Installera Linux på en (inte alltför ny) dator. Det spelar ingen roll vilken distribution du väljer, även om de flesta i dag börjar med Ubuntu, Fedora eller OpenSUSE. Är du osäker, prova Ubuntu först. Att installera en modern Linuxdistribution kan vara löjligt enkelt eller löjligt svårt beroende på din hårdvara. Jag föreslår en stationär dator som är ett par år gammal och har minst 2 GB RAM och 6 GB ledigt diskutrymme. Undvik bärbara datorer och trådlösa nätverk om du kan, eftersom de ofta är svårare att få att fungera.
-
Använd en "live-CD" eller ett USB-minne. En av de häftiga sakerna du kan göra med många Linuxdistributioner är att köra dem direkt från en cd-skiva eller ett USB-minne utan att installera dem alls. Gå bara in i BIOS-inställningarna, ställ in datorn för att starta från cd-enhet eller USB-enhet och starta om. Den här metoden är ett utmärkt sätt att testa om en dator är kompatibel med Linux innan installation. Nackdelen är att det kan gå betydligt långsammare än att ha Linux installerat på hårddisken. Både Ubuntu och Fedora (bland andra) har liveversioner.
Oavsett hur du installerar Linux behöver du ibland superanvändarrättigheter (alltså administrativa rättigheter) för att kunna genomföra övningarna i den här boken.
När du har en fungerande installation, börja läsa och följ med på din egen dator. Merparten av materialet i den här boken är praktiskt, så sätt dig och börja skriva.
I vissa kretsar anses det politiskt korrekt att kalla Linuxoperativsystemet för "GNU/Linux-operativsystemet". Problemet med "Linux" är att det inte finns något helt korrekt sätt att namnge det, eftersom det har skrivits av många olika människor i ett enormt, distribuerat utvecklingsarbete. Tekniskt sett är Linux namnet på operativsystemets kärna, och inget mer. Kärnan är förstås mycket viktig, eftersom den får operativsystemet att fungera, men den räcker inte för att bilda ett komplett operativsystem.
Här kommer Richard Stallman in i bilden - den genialiske filosofen som grundade fri programvarurörelsen, startade Free Software Foundation, skapade GNU-projektet, skrev den första versionen av GNU C Compiler (gcc), skapade GNU General Public License (GPL), och så vidare. Han insisterar på att man ska kalla det "GNU/Linux" för att rättvist spegla GNU-projektets bidrag. GNU-projektet kom visserligen före Linuxkärnan, och projektets bidrag förtjänar i högsta grad erkännande, men att ge dem plats i namnet är orättvist mot alla andra som också gjort betydande insatser. Dessutom tycker jag att "Linux/GNU" vore tekniskt mer korrekt, eftersom kärnan startar först och allt annat kör ovanpå den.
I allmänt språkbruk syftar "Linux" på kärnan och all annan fri programvara med öppen källkod som finns i en typisk Linuxdistribution - alltså hela Linuxekosystemet, inte bara GNU-delarna. Marknaden för operativsystem verkar föredra namn med ett ord, som DOS, Windows, macOS, Solaris, Irix och AIX. Jag har valt att använda det etablerade formatet. Om du ändå föredrar "GNU/Linux", gör gärna ett mentalt sök-och-ersätt medan du läser den här boken. Jag tar inte illa upp.
7. Vad som är nytt i sjunde internetutgåvan
Själva skalet får bara större versionssläpp ungefär vart tionde år, men hårdvara och verktyg förändras ständigt. Den här utgåvan av Linux på kommandoraden har moderniserats igen för att spegla förändringarna i kommandoradsmiljön. Den innehåller många mindre justeringar och rättelser. Utgåvan ligger också i linje med den tryckta versionen, The Linux Command Line: A Complete Introduction, Third Edition, utgiven av No Starch Press.
För en detaljerad lista över ändringar, se versionsnoteringarna på LinuxCommand.org. Nytt i den här utgåvan är också en samling exempelskript från boken. Den går också att hämta på LinuxCommand.org.
8. Tack
Jag vill tacka följande personer, som gjorde den här boken möjlig:
Jenny Watson, förvärvsredaktör på Wiley Publishing, som ursprungligen föreslog att jag skulle skriva en bok om skalskript.
John C. Dvorak, välkänd krönikör och kommentator. I ett avsnitt av hans videopodd "Cranky Geeks" beskrev han skrivprocessen så här: "Ett helvete. Skriv 200 ord om dagen, så har du en roman om ett år." Det rådet fick mig att skriva en sida om dagen tills jag hade en bok.
Dmitri Popov skrev en artikel i Free Software Magazine med titeln "Creating a Book Template with Writer", som inspirerade mig att använda OpenOffice.org Writer (och senare LibreOffice Writer) för att skriva texten. Det visade sig fungera utmärkt.
Mark Polesky gjorde en exceptionell granskning och testning av texten i första utgåvan.
Jesse Becker, Tomasz Chrzczonowicz, Michael Levin och Spence Miner testade också och granskade delar av första utgåvan.
Karen M. Shotts lade många timmar på att putsa min så kallade engelska genom att redigera originalmanuset.
9. Sjunde internetutgåvan
Ett särskilt tack till följande personer som gav värdefull återkoppling som införlivades i den sjunde internetutgåvan: Vitor Centeio, Elmar Deininger, Francesco Di Viesto, Jaroslaw Kolosowski, Kayck Matias och Wang Zheng.
10. Tidigare utgåvor
Ett särskilt tack till följande personer som gav värdefull återkoppling som införlivades i tidigare utgåvor: Ala’a Ali, Adrian Arpidez, Mikey Barboza, Jesse Becker, Emanuele Bernardi, Andreas Bjørnestad, Hu Bo, Steve Bragg, John Burns, Heriberto Cantú, Enzo Cardinal, Paolo Casati, Tomasz Chrzczonowicz, Richard Cooke, Ethan Dowlatshah, Lixin Duan, Joshua Escamilla, Marc Evans, Ryan Flynn, Bruce Fowler, Devin Harper, Jørgen Heitmann, Janrodion, Jonathan Jones, Sunil Joshi, Ma Jun, Eric Kammerer, Robert Kennington, Seth King, Chris Knight, Jaroslaw Kolosowski, Klaus M. Körmendi, Jim Kovacs, Michael Levin, Bartłomiej Majka, Bashar Maree, Frank McTipps, Vladimir Milovanović, Sea Monkey, Tim Nelson, Mike O’Donnell, Oktay-Akin Okutan, Nick Owens, Justin Page, Michael Parrish, Esra Purba, Parviz Rasoulipour, Amir Razqandi, Patrick, Waldo Ribeiro, Pat Roche, Nick Rose, Satej Kumar Sahu, Avid Seeker, Mikhail Sizov, Ben Slater, Pickles Spill, Gabriel Stutzman, Pooya Taherkhani, Francesco Turco, Wolfram Volpi, Boyang Wang, Carl Westman, John Wiersba, Valter Wierzba och Christian Wuethrich.
Och till sist, stort tack till läsarna på LinuxCommand.org som har skickat så många vänliga mejl. Deras uppmuntran gav mig känslan av att jag faktiskt var något viktigt på spåren.
11. Din återkoppling behövs!
Den här boken är ett pågående projekt, precis som många projekt med öppen källkod. Om du hittar ett tekniskt fel, skicka gärna ett meddelande till bshotts@users.sourceforge.net.
Var noga med att ange exakt vilken utgåva av boken du läser. Dina ändringar och förslag kan komma med i framtida utgåvor.
12. Vidare läsning
-
Här är några Wikipediaartiklar om de kända personer som nämns ovan:
-
Free Software Foundation och GNU-projektet:
-
Richard Stallman har skrivit utförligt om namnfrågan "GNU/Linux":
Del 1 - Lära sig skalet
13. 1 - Vad är skalet?
När vi talar om kommandoraden syftar vi egentligen på skalet. Skalet är ett program som tar kommandon från tangentbordet och skickar dem vidare till operativsystemet för att utföras. Nästan alla Linuxdistributioner levererar ett skal från GNU-projektet som heter bash. Namnet "bash" är en akronym för "Bourne Again SHell", en hänvisning till att bash är en förbättrad ersättning för sh, det ursprungliga Unixskalet som skrevs av Steve Bourne.
13.1. Terminalemulatorer
När vi använder ett grafiskt användargränssnitt (GUI) behöver vi ett annat program, en så kallad terminalemulator, för att arbeta med skalet. Om vi tittar i skrivbordets menyer hittar vi sannolikt en sådan. KDE använder konsole och GNOME använder gnome-terminal, även om den i menyn ofta bara heter "terminal". Det finns flera andra terminalemulatorer för Linux, men i grunden gör de samma sak: ger oss tillgång till skalet. Du kommer troligen att få en favorit beroende på hur många finesser den har.
13.2. De första tangenttryckningarna
Då sätter vi i gång. Starta terminalemulatorn. När den öppnas bör vi se något i stil med:
[me@linuxbox ~]$
Det här kallas skalprompt och visas när skalet är redo att ta emot inmatning. Utseendet kan variera lite mellan distributioner, men innehåller oftast användarnamn@datornamn, följt av aktuell arbetskatalog (mer om det strax) och ett dollartecken.
Obs: Om sista tecknet i prompten är ett nummertecken ("#") i stället för ett dollartecken har terminalsessionen superanvändarrättigheter. Det betyder antingen att vi är inloggade som root eller att vi har valt en terminalemulator som ger administrativa rättigheter.
Om allt ser bra ut så här långt provar vi att skriva lite. Mata in något nonsens i prompten, till exempel:
[me@linuxbox ~]$ kaekfjaeifj
Eftersom kommandot saknar mening talar skalet om det och ger oss ett nytt försök.
bash: kaekfjaeifj: command not found
[me@linuxbox ~]$
13.2.1. Kommandohistorik
Om vi trycker på uppil ser vi att det föregående kommandot, kaekfjaeifj, dyker upp igen efter prompten. Det här kallas kommandohistorik. De flesta Linuxdistributioner minns som standard de senaste 1 000 kommandona. Trycker vi på nedil försvinner det tidigare kommandot.
13.2.2. Markörförflyttning
Hämta fram föregående kommando igen med uppil. Provar vi vänster- och högerpil ser vi att vi kan placera markören var som helst på kommandoraden. Det gör det enkelt att redigera kommandon.
13.2.3. Ett par ord om mus och fokus
Även om skalet kretsar kring tangentbordet kan du också använda mus i terminalemulatorn. En mekanism i fönstersystemet (motorn som får GUI:t att fungera) stödjer en snabb teknik för kopiera och klistra in. Om du markerar text genom att hålla nere vänster musknapp och dra över texten (eller dubbelklicka på ett ord) kopieras den till en buffert som fönstersystemet håller. Ett tryck på mittenknappen klistrar in texten vid markören. Prova. Det fungerar i de flesta skrivbordsmiljöer.
Obs: Låt dig inte frestas att använda Ctrl-c och Ctrl-v för kopiera och klistra in i terminalfönstret. Det fungerar inte. De styrkoderna har andra betydelser i skalet och tilldelades många år innan Microsoft Windows släpptes. Använd Shift-Ctrl-c och Shift-Ctrl-v i terminalfönstret i stället.
Din grafiska skrivbordsmiljö (troligen KDE eller GNOME) har förmodligen sin fokuseringspolicy inställd på "klicka för att fokusera", i ett försök att bete sig som Windows. Det innebär att ett fönster måste klickas på för att få fokus (bli aktivt). Detta är motsatsen till det traditionella beteendet "fokus följer mus", som innebär att ett fönster får fokus bara genom att muspekaren förs över det. Fönstret kommer inte till förgrunden förrän du klickar på det, men det kommer att kunna ta emot inmatning. Att ställa in fokuseringspolicyn till "fokus följer mus" gör kopiera-och-klistra-tekniken ännu mer användbar. Prova om du kan (vissa skrivbordsmiljöer stödjer det inte längre). Jag tror att om du ger det en chans kommer du att föredra det. Du hittar denna inställning i konfigurationsprogrammet för din fönsterhanterare.
13.3. Prova några enkla kommandon
Nu när vi har lärt oss att mata in text i terminalemulatorn kan vi prova några enkla kommandon. Vi börjar med kommandot date, som visar aktuell tid och datum.
[me@linuxbox ~]$ date
Thu Mar 8 15:09:41 EST 2025
Ett annat praktiskt kommando är uptime, som visar hur länge systemet har varit i gång och det genomsnittliga antalet processer under olika tidsperioder.
[me@linuxbox ~]$ uptime
15:12:22 up 3 days, 23:40, 7 users, load average: 0.37, 0.37, 0.64
För att se hur mycket ledigt utrymme vi har på våra diskar skriver vi df.
[me@linuxbox ~]$ df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda2 15115452 5012392 9949716 34% /
/dev/sda5 59631908 26545424 30008432 47% /home
/dev/sda1 147764 17370 122765 13% /boot
tmpfs 256856 0 256856 0% /dev/shm
På samma sätt kan vi visa mängden ledigt minne med kommandot free.
[me@linuxbox ~]$ free
total used free shared buffers cached
Mem: 513712 503976 9736 0 5312 122916
-/+ buffers/cache: 375748 137964
Swap: 1052248 104712 947536
13.4. Avsluta en terminalsession
Vi kan avsluta en terminalsession genom att stänga terminalemulatorns fönster, skriva kommandot exit i prompten eller trycka Ctrl-d.
[me@linuxbox ~]$ exit
13.4.1. Konsolen bakom kulisserna
Även om vi inte har någon terminalemulator som körs fortsätter flera terminalsessioner att köra bakom det grafiska skrivbordet. Vi kan komma åt dessa sessioner, som kallas virtuella terminaler eller virtuella konsoler, genom att trycka på Ctrl-Alt-F1 till Ctrl-Alt-F6 på de flesta Linux-distributioner. När en session nås presenteras en inloggningsprompt där vi kan ange vårt användarnamn och lösenord. För att växla från en virtuell konsol till en annan trycker du på Alt-F1 till Alt-F6. På de flesta system kan vi återgå till det grafiska skrivbordet genom att trycka på Alt-F7.
13.5. Sammanfattning
Det här kapitlet markerade början på vår resa in i Linux kommandorad med en introduktion till skalet, en kort inblick i kommandoraden och en genomgång av hur man startar och avslutar en terminalsession. Vi såg också hur man kör några enkla kommandon och gör lite lätt redigering på kommandoraden. Det var väl inte så skrämmande, eller hur?
I nästa kapitel lär vi oss några fler kommandon och vandrar runt i Linuxfilsystemet.
13.6. Vidare läsning
-
Om du vill veta mer om Steve Bourne, Bourne-skalets fader:
http://en.wikipedia.org/wiki/Steve_Bourne -
Om Brian Fox, den ursprungliga författaren av
bash:https://en.wikipedia.org/wiki/Brian_Fox_(computer_programmer) -
Om begreppet skal i datavetenskapen:
http://en.wikipedia.org/wiki/Shell_(computing)
14. 2 - Navigering
Det första vi behöver lära oss (förutom att skriva) är hur vi navigerar i filsystemet på vårt Linuxsystem. I det här kapitlet introducerar vi följande kommandon:
-
pwd— skriv ut namnet på aktuell arbetskatalog. -
cd— byta katalog. -
ls— lista kataloginnehåll.
14.1. Förstå filsystemträdet
Liksom Windows organiserar Unix-liknande operativsystem som Linux sina filer i det som kallas en hierarkisk katalogstruktur. Det betyder att filerna ordnas i en trädliknande struktur av kataloger (som i vissa andra system kallas mappar), vilka kan innehålla filer och andra kataloger. Den första katalogen i filsystemet kallas rotkatalogen. Rotkatalogen innehåller filer och underkataloger, som i sin tur innehåller fler filer och underkataloger.
Notera att till skillnad från Windows, som har ett separat filsystemträd för varje lagringsenhet, har Unix-liknande system som Linux alltid ett enda filsystemträd, oavsett hur många diskar eller lagringsenheter som är anslutna till datorn. Lagringsenheter ansluts (eller mer korrekt monteras) på olika punkter i trädet enligt systemadministratörens önskemål, det vill säga den person (eller de personer) som ansvarar för att underhålla systemet.
14.2. Den aktuella arbetskatalogen
De flesta av oss är nog vana vid en grafisk filhanterare som visar filsystemträdet, som i figur 1. Lägg märke till att trädet vanligtvis visas upp-och-ned, alltså med roten överst och grenarna nedanför.
Kommandoraden har däremot inga bilder, så för att navigera i filsystemträdet behöver vi tänka på ett annat sätt.
Tänk dig att filsystemet är en labyrint formad som ett upp-och-nervänt träd och att vi kan stå mitt i den. Vid varje tidpunkt befinner vi oss i en enda katalog, och vi kan se filerna i katalogen, vägen upp till katalogen ovanför oss (som kallas överordnad katalog) och eventuella underkataloger under oss. Katalogen vi står i kallas aktuell arbetskatalog. För att visa den använder vi kommandot pwd (print working directory).
[me@linuxbox ~]$ pwd
/home/me
När vi loggar in i systemet första gången (eller startar en terminalemulatorsession) sätts aktuell arbetskatalog till vår hemkatalog. Varje användarkonto får en egen hemkatalog, och det är den enda plats där en vanlig användare får skriva filer.
14.3. Lista innehållet i en katalog
För att lista filer och kataloger i den aktuella arbetskatalogen använder vi kommandot ls.
[me@linuxbox ~]$ ls
Desktop Documents Music Pictures Public Templates Videos
Vi kan förstås använda ls för att lista innehållet i vilken katalog som helst, inte bara den aktuella arbetskatalogen, och det finns många fler nyttiga saker kommandot kan göra. Vi ägnar mer tid åt ls i nästa kapitel.
14.4. Byta aktuell arbetskatalog
För att byta arbetskatalog (alltså var vi står i vår trädformade labyrint) använder vi kommandot cd. Skriv cd följt av ett mellanslag och sökvägen till önskad arbetskatalog. En sökväg är den väg vi tar längs trädets grenar för att nå katalogen vi vill till. Sökvägar kan anges på två sätt: som absoluta sökvägar eller relativa sökvägar. Vi börjar med absoluta sökvägar.
14.4.1. Absoluta sökvägar
En absolut sökväg börjar i rotkatalogen och följer gren för gren tills vägen till önskad katalog eller fil är klar. På vårt system finns till exempel en katalog där många av systemets program är installerade. Den katalogens sökväg är /usr/bin. Det betyder att det från rotkatalogen (som markeras av inledande snedstreck) finns en katalog som heter usr och i den en katalog som heter bin.
[me@linuxbox ~]$ cd /usr/bin
[me@linuxbox bin]$ pwd
/usr/bin
[me@linuxbox bin]$ ls
...Listing of many, many files ...
Nu ser vi att vi har bytt aktuell arbetskatalog till /usr/bin och att den är full av filer. Märkte du att skalprompten ändrades? Av bekvämlighetsskäl är den ofta inställd att automatiskt visa namnet på arbetskatalogen.
14.4.2. Relativa sökvägar
Medan en absolut sökväg börjar i rotkatalogen och leder till sitt mål, börjar en relativ sökväg i arbetskatalogen. Då används ett par särskilda notationer för att beskriva relativa lägen i filsystemträdet. Dessa är . (punkt) och .. (två punkter).
Notationen . syftar på arbetskatalogen och .. syftar på arbetskatalogens överordnade katalog. Låt oss till exempel byta arbetskatalog till /usr/bin igen.
[me@linuxbox ~]$ cd /usr/bin
[me@linuxbox bin]$ pwd
/usr/bin
Anta nu att vi vill byta arbetskatalog till överordnad katalog för /usr/bin, alltså /usr. Det kan vi göra på två sätt, antingen med en absolut sökväg:
[me@linuxbox bin]$ cd /usr
[me@linuxbox usr]$ pwd
/usr
eller med en relativ sökväg:
[me@linuxbox bin]$ cd ..
[me@linuxbox usr]$ pwd
/usr
Två olika metoder med samma resultat. Vilken ska vi använda? Den som kräver minst tangenttryckningar.
På samma sätt kan vi byta arbetskatalog från /usr till /usr/bin på två sätt, antingen med en absolut sökväg:
[me@linuxbox usr]$ cd /usr/bin
[me@linuxbox bin]$ pwd
/usr/bin
eller med en relativ sökväg:
[me@linuxbox usr]$ cd ./bin
[me@linuxbox bin]$ pwd
/usr/bin
Det finns en viktig sak att påpeka här. I nästan alla fall kan vi utelämna ./. Det är underförstått. Att skriva
[me@linuxbox usr]$ cd bin
gör samma sak. I allmänhet gäller att om vi inte anger en sökväg till något, så antas arbetskatalogen.
14.4.3. Några användbara genvägar
I tabell 2-1 ser vi några praktiska sätt att snabbt byta aktuell arbetskatalog.
| Genväg | Resultat |
|---|---|
|
Byter arbetskatalog till din hemkatalog. |
|
Byter arbetskatalog till föregående arbetskatalog. |
|
Byter arbetskatalog till hemkatalogen för |
14.5. Sammanfattning
Det här kapitlet förklarade hur skalet behandlar systemets katalogstruktur. Vi lärde oss om absoluta och relativa sökvägar och de grundläggande kommandon vi använder för att röra oss i den strukturen. I nästa kapitel använder vi den kunskapen för att göra en rundtur i ett modernt Linuxsystem.
14.6. Vidare läsning
-
Wikipedia-artikel om filsystem: https://sv.wikipedia.org/wiki/Filsystem
-
Sökväg (datorvetenskap) på Wikipedia: https://sv.wikipedia.org/wiki/Sökväg_(datorvetenskap)
-
En introduktion till katalogstruktur och filsystemsstandarder: https://tldp.org/LDP/Linux-Filesystem-Hierarchy/html/index.html
15. 3 - Utforska systemet
Nu när vi vet hur vi tar oss runt i filsystemet är det dags för en guidad tur i vårt Linuxsystem. Innan vi börjar lär vi oss först några fler kommandon som kommer till nytta längs vägen.
-
ls - lista kataloginnehåll.
-
file - avgör filtyp.
-
less - visa filinnehåll.
15.1. Mer nytta av ls
Kommandot ls är troligen det mest använda Linuxkommandot, och det finns goda skäl till det. Med ls kan vi se kataloginnehåll och avgöra många viktiga egenskaper hos filer och kataloger. Som vi redan har sett kan vi skriva ls för att få en lista över filer och underkataloger i aktuell arbetskatalog.
[me@linuxbox ~]$ ls
Desktop Documents Music Pictures Public Templates Videos
Utöver aktuell arbetskatalog kan vi ange vilken katalog som helst att lista, till exempel:
[me@linuxbox ~]$ ls /usr
bin games include lib local sbin share src
Vi kan till och med ange flera kataloger. I nästa exempel listar vi både användarens hemkatalog (symboliserad med tecknet "~") och katalogen /usr:
[me@linuxbox ~]$ ls ~ /usr
/home/me:
Desktop Documents Music Pictures Public Templates Videos
/usr:
bin games include lib local sbin share src
Vi kan också ändra utdataformatet för att få mer detaljer.
[me@linuxbox ~]$ ls -l
total 56
drwxrwxr-x 2 me me 4096 2025-10-26 17:20 Desktop
drwxrwxr-x 2 me me 4096 2025-10-26 17:20 Documents
drwxrwxr-x 2 me me 4096 2025-10-26 17:20 Music
drwxrwxr-x 2 me me 4096 2025-10-26 17:20 Pictures
drwxrwxr-x 2 me me 4096 2025-10-26 17:20 Public
drwxrwxr-x 2 me me 4096 2025-10-26 17:20 Templates
drwxrwxr-x 2 me me 4096 2025-10-26 17:20 Videos
Genom att lägga till -l i kommandot ändrade vi utdata till detaljerat format.
15.1.1. Flaggor och argument
Här kommer en viktig poäng om hur de flesta kommandon fungerar. Kommandon följs ofta av en eller flera flaggor som ändrar beteendet och dessutom av ett eller flera argument, alltså det som kommandot arbetar med. Därför ser de flesta kommandon ut så här:
command -options arguments
De flesta kommandon använder flaggor som består av ett enda tecken med inledande bindestreck, till exempel -l. Många kommandon, särskilt från GNU-projektet, stöder också långa flaggor med två inledande bindestreck, till exempel ett helt ord. Många kommandon tillåter också att flera korta flaggor skrivs ihop. I exemplet nedan får ls två flaggor: l för långt format och t för sortering efter filens ändringstid.
[me@linuxbox ~]$ ls -lt
Vi lägger till den långa flaggan --reverse för att vända sorteringsordningen.
[me@linuxbox ~]$ ls -lt --reverse
Observera att kommandoflaggor, liksom filnamn i Linux, är skiftlägeskänsliga.
| Flagga | Lång flagga | Beskrivning |
|---|---|---|
|
|
Lista alla filer, även de vars namn börjar med punkt och som normalt inte visas (det vill säga dolda filer). |
|
|
Som flaggan |
|
|
Normalt listar |
|
|
Lägg till ett indikationstecken i slutet av varje namn, till exempel ett snedstreck ( |
|
|
I detaljerat listformat visas filstorlekar i läsbart format i stället för i byte. |
|
Visa resultat i detaljerat format. |
|
|
|
Visa resultaten i omvänd ordning. Normalt visar |
|
Sortera resultat efter filstorlek. |
|
|
Sortera efter ändringstid. |
15.1.2. En närmare titt på långt format
Som vi såg tidigare gör flaggan -l att ls visar resultat i detaljerat format. Det formatet innehåller mycket användbar information. Här är katalogen Examples från ett äldre Ubuntu-system:
-rw-r--r-- 1 root root 3576296 2017-04-03 11:05 Experience ubuntu.ogg
-rw-r--r-- 1 root root 1186219 2017-04-03 11:05 kubuntu-leaflet.png
-rw-r--r-- 1 root root 47584 2017-04-03 11:05 logo-Edubuntu.png
-rw-r--r-- 1 root root 44355 2017-04-03 11:05 logo-Kubuntu.png
-rw-r--r-- 1 root root 34391 2017-04-03 11:05 logo-Ubuntu.png
-rw-r--r-- 1 root root 32059 2017-04-03 11:05 oo-cd-cover.odf
-rw-r--r-- 1 root root 159744 2017-04-03 11:05 oo-derivatives.doc
-rw-r--r-- 1 root root 27837 2017-04-03 11:05 oo-maxwell.odt
-rw-r--r-- 1 root root 98816 2017-04-03 11:05 oo-trig.xls
-rw-r--r-- 1 root root 453764 2017-04-03 11:05 oo-welcome.odt
-rw-r--r-- 1 root root 358374 2017-04-03 11:05 ubuntu Sax.ogg
Tabell 3-2 visar de olika fälten och deras betydelse.
| Fält | Betydelse |
|---|---|
|
Filens åtkomsträttigheter. Första tecknet anger filtypen. Av de olika typerna betyder ett inledande bindestreck en vanlig fil, medan |
|
Filens antal hårda länkar. Se avsnitten "Symboliska länkar" och "Hårda länkar" senare i detta kapitel. |
|
Användarnamn för filens ägare. |
|
Namn på gruppen som äger filen. |
|
Filens storlek i byte. |
|
Datum och tid för filens senaste ändring. |
|
Filens namn. |
15.2. Avgör en fils typ med file
När vi utforskar systemet är det nyttigt att veta vilken typ av data filer innehåller. För det använder vi kommandot file. Som vi nämnde tidigare behöver filnamn i Linux inte spegla filens innehåll. En fil som heter picture.jpg förväntas normalt innehålla en JPEG-komprimerad bild, men det är inget krav i Linux. Vi använder file så här:
file filename
När file körs skriver det ut en kort beskrivning av filens innehåll. Till exempel:
[me@linuxbox ~]$ file picture.jpg
picture.jpg: JPEG image data, JFIF standard 1.01
Det finns många slags filer. Faktum är att en av grundidéerna i Unix-liknande operativsystem som Linux är att "allt är en fil". Under de kommande lektionerna kommer vi att se hur sant det påståendet är.
15.3. Visa filinnehåll med less
Kommandot less är ett program för att visa textfiler. I vårt Linuxsystem finns det många filer som innehåller text läsbar för människor. Programmet less erbjuder ett bekvämt sätt att granska dem.
15.3.1. Vad är "text"?
Det finns många sätt att representera information i en dator. Alla metoder innebär att man definierar ett samband mellan informationen och några tal som används för att representera den. Datorer förstår ju i grunden bara tal, och all data omvandlas till numerisk representation.
Vissa representationssystem är komplexa (som komprimerade videofiler), andra är enkla. Ett av de äldsta och enklaste kallas ASCII-text. ASCII (uttalas "aski") står för American Standard Code for Information Interchange. Det är ett enkelt kodningsschema som först användes i teletypmaskiner för att mappa tangentbordstecken till tal.
Text är en enkel en-till-en-mappning mellan tecken och tal. Det är kompakt: femtio tecken text blir 50 byte data. Det är viktigt att förstå att text bara innehåller denna enkla mappning mellan tecken och tal. Det är inte samma sak som ett ordbehandlingsdokument, till exempel skapat i Microsoft Word eller LibreOffice Writer. Sådana filer innehåller, till skillnad från enkel ASCII-text, många icke-textuella element som beskriver struktur och formatering. Rena ASCII-textfiler innehåller bara själva tecknen och några få enkla styrkoder, som tabbar, vagnretur och radmatning.
I ett Linuxsystem lagras många filer i textformat, och det finns många Linuxverktyg som arbetar med textfiler. Även Windows känner igen formatets betydelse. Det välkända programmet NOTEPAD.EXE är en redigerare för rena ASCII-textfiler.
Varför vill vi granska textfiler? Därför att många filer som innehåller systeminställningar (konfigurationsfiler) lagras i detta format, och att kunna läsa dem ger oss insikt i hur systemet fungerar. Dessutom lagras en del av de faktiska program som systemet använder (skript) i detta format. I senare kapitel lär vi oss att redigera textfiler för att ändra systeminställningar och skriva egna skript, men just nu nöjer vi oss med att titta på innehållet.
Kommandot less används så här:
less filename
När less väl startat kan vi bläddra framåt och bakåt i en textfil. För att granska filen som definierar systemets alla användarkonton skriver vi till exempel:
[me@linuxbox ~]$ less /etc/passwd
När less är igång kan vi läsa filens innehåll. Om filen är längre än en sida kan vi bläddra upp och ned. För att avsluta less trycker vi q. Tabell 3-3 listar de vanligaste tangentbordskommandona i less.
| Kommando | Åtgärd |
|---|---|
|
Bläddra tillbaka en sida. |
|
Bläddra framåt en sida. |
|
Bläddra upp en rad. |
|
Bläddra ner en rad. |
|
Gå till slutet av textfilen. |
|
Gå till början av textfilen. |
|
Sök framåt till nästa förekomst av |
|
Sök efter nästa förekomst av föregående sökning. |
|
Visa hjälpskärmen. |
|
Avsluta |
15.3.2. Less Is More
Programmet less designades som en förbättrad ersättning för ett tidigare Unix-program som kallades more. Namnet less är en ordlek med frasen "less is more" — ett motto för modernistiska arkitekter och designers.
less faller inom kategorin program som kallas pagers, program som möjliggör enkel visning av långa textdokument sida för sida. Medan programmet more endast kunde bläddra framåt, tillåter less bläddring både framåt och bakåt och har många andra funktioner också.
15.4. Guidad rundtur
Filsystemets struktur på Linux liknar den i andra Unix-liknande system. Utformningen beskrivs faktiskt i en publicerad standard som heter Linux Filesystem Hierarchy Standard. Alla Linuxdistributioner följer inte standarden exakt, men de flesta ligger nära.
Nu ska vi vandra runt i filsystemet och se vad som får vårt Linuxsystem att ticka. Samtidigt får vi öva våra navigeringsfärdigheter. En sak vi kommer att upptäcka är att många intressanta filer är ren text som människor kan läsa. Under rundturen kan du göra så här:
-
cd till en angiven katalog
-
lista kataloginnehållet med ls -l
-
om du ser en intressant fil, avgör innehållet med file
-
om den verkar vara text, prova att visa den med less
-
om vi av misstag försöker visa en icke-textfil och terminalfönstret blir oläsligt, kan vi återställa med kommandot reset
Kom ihåg knepet med kopiera och klistra in. Om du använder mus kan du dubbelklicka på ett filnamn för att kopiera det och mittklicka för att klistra in det i kommandon.
När vi vandrar runt behöver du inte vara rädd för att titta på saker. Vanliga användare är i stort sett förhindrade att ställa till det. Det är systemadministratörens jobb. Om ett kommando klagar på något, gå bara vidare till något annat. Ta dig tid att utforska. Systemet är vårt att undersöka. Kom ihåg: i Linux finns inga hemligheter.
Tabell 3-4 listar några kataloger vi kan utforska. Det kan skilja lite mellan distributioner. Var inte rädd för att titta runt och prova mer.
| Katalog | Kommentarer |
|---|---|
|
Rotkatalogen. Här börjar allt. |
|
Innehåller binärfiler (program) som måste finnas för att systemet ska kunna starta och köra. Moderna Linuxdistributioner har fasat ut |
|
Innehåller Linuxkärnan, den initiala RAM-diskavbildningen (för drivrutiner som behövs vid uppstart) och starthanteraren. Intressanta filer är bland annat |
|
En särskild katalog som innehåller enhetsnoder. "Allt är en fil" gäller även enheter. Här upprätthåller kärnan en lista över alla enheter den känner till. |
|
Innehåller alla systemövergripande konfigurationsfiler. Allt i denna katalog bör vara läsbar text. Några favoriter är |
|
I normala konfigurationer tilldelas varje användare en katalog under |
|
Innehåller delade biblioteksfiler som används av systemets kärnprogram. Dessa liknar dynamiska länkbibliotek (DLL:er) i Windows. Denna katalog har fasats ut i moderna distributioner till förmån för |
|
Varje formaterad partition eller enhet som använder ett traditionellt Linuxfilsystem, till exempel |
|
På moderna Linuxsystem innehåller katalogen |
|
På äldre Linuxsystem innehåller katalogen |
|
Katalogen |
|
Ett virtuellt filsystem som upprätthålls av Linuxkärnan. "Filerna" det innehåller är titthål in i kärnan och kan avslöja många detaljer om datorns maskinvara. |
|
Hemkatalog för |
|
En modern ersättning för den traditionella katalogen |
|
Innehåller systembinärfiler, program som utför viktiga systemuppgifter som vanligtvis är reserverade för superanvändaren. Moderna Linuxdistributioner har fasat ut |
|
Innehåller information om enheter som kärnan har upptäckt. Detta liknar innehållet i |
|
Avsedd för tillfälliga filer som skapas av olika program. Vissa distributioner tömmer denna katalog varje gång systemet startas om. |
|
Troligen det största katalogträdet i ett Linuxsystem. Det innehåller alla program och stödfiler som används av vanliga användare. |
|
Innehåller de körbara program som installerats av Linuxdistributionen. Det är inte ovanligt att denna katalog rymmer tusentals program. |
|
Delade bibliotek för programmen i |
|
Här installeras program som inte ingår i distributionen men som är avsedda för systemövergripande användning. Program kompilerade från källkod installeras normalt i |
|
Innehåller ytterligare systemadministrationsprogram. |
|
Innehåller alla delade data som används av program i |
|
De flesta paket som installeras på systemet inkluderar någon form av dokumentation. I |
|
Här lagras data som sannolikt kommer att ändras. Olika databaser, köfiler, användarpost med mera finns här. |
|
Innehåller loggfiler, register över olika systemaktiviteter. De mest användbara är |
|
Finns i varje skrivbordsanvändares hemkatalog. De används för att lagra användarspecifika konfigurations- och programtillståndsdata för skrivbordsprogram. |
15.5. Symboliska länkar
När vi tittar runt kan vi stöta på en kataloglista (till exempel i /usr/lib) med en post som denna:
lrwxrwxrwx 1 root root 11 2025-08-11 07:34 libc.so.6 -> libc-2.6.so
Det här är en särskild filtyp som kallas symbolisk länk (även kallad mjuk länk eller symlänk). I de flesta Unix-liknande system kan en fil hänvisas till med flera namn. Nyttan är kanske inte uppenbar direkt, men det är en mycket användbar funktion.
Tänk dig följande: ett program behöver en delad resurs i en fil som heter "foo", men "foo" versionsuppdateras ofta. Då vore det bra att ha versionsnumret i filnamnet så att administratören eller andra intresserade kan se vilken version av "foo" som är installerad. Här uppstår ett problem. Om vi ändrar namnet på den delade resursen måste vi hitta varje program som använder den och ändra så att det söker efter det nya namnet varje gång en ny version installeras. Det låter inte särskilt roligt.
Det är här symboliska länkar räddar situationen. Anta att vi installerar version 2.6 av "foo" med filnamnet foo-2.6 och skapar en symbolisk länk som bara heter foo och pekar på foo-2.6. När ett program öppnar filen foo öppnar det i praktiken foo-2.6. Nu är alla nöjda. Programmen som är beroende av "foo" hittar den, och vi kan fortfarande se vilken version som faktiskt är installerad. När det är dags att uppgradera till foo-2.7 lägger vi bara till filen i systemet, tar bort den symboliska länken foo och skapar en ny symbolisk länk som pekar på den nya versionen. Det löser inte bara uppgraderingsproblemet, utan låter oss också ha båda versionerna installerade samtidigt. Tänk om foo-2.7 har en bugg (de där utvecklarna!) och vi behöver gå tillbaka till den gamla versionen. Då tar vi åter bort länken som pekar på den nya versionen och skapar en ny som pekar på den gamla.
Kataloglistningen i början av avsnittet (från /usr/lib i ett Fedora-system) visar en symbolisk länk med namnet libc.so.6 som pekar på den delade biblioteksfilen libc-2.6.so. Det betyder att program som söker efter libc.so.6 i själva verket får filen libc-2.6.so. Vi lär oss skapa symboliska länkar i nästa kapitel.
15.6. Hårda länkar
När vi ändå talar om länkar behöver vi nämna att det finns en andra länktyp som kallas hård länk. Hårda länkar gör också att filer kan ha flera namn, men på ett annat sätt. Vi går igenom skillnaderna mellan symboliska och hårda länkar mer i nästa kapitel.
15.7. Sammanfattning
Efter vår rundtur har vi lärt oss mycket om systemet. Vi har sett olika filer och kataloger och vad de innehåller. En sak att ta med sig är hur öppet systemet är. I Linux är många viktiga filer ren text som människor kan läsa. Till skillnad från många proprietära system gör Linux nästan allt tillgängligt för granskning och studier.
15.8. Vidare läsning
-
Hela versionen av Linux Filesystem Hierarchy Standard finns här:
-
En artikel om katalogstrukturen i Unix och Unix-liknande system:
-
En utförlig beskrivning av ASCII-textformatet:
16. 4 - Hantera filer och kataloger
Nu är vi redo för riktigt arbete. I det här kapitlet introduceras följande kommandon:
-
cp - kopiera filer och kataloger.
-
mv - flytta/byta namn på filer och kataloger.
-
mkdir - skapa kataloger.
-
rm - ta bort filer och kataloger.
-
ln - skapa hårda och symboliska länkar.
Dessa fem kommandon är bland de mest använda i Linux. De används för att hantera både filer och kataloger.
För att vara ärlig: vissa uppgifter som de här kommandona gör går enklare i en grafisk filhanterare. Där kan vi dra och släppa en fil från en katalog till en annan, klippa och klistra in filer, ta bort filer och så vidare. Varför använda de här gamla kommandoradsprogrammen då?
Svaret är kraft och flexibilitet. Enkla filoperationer är lätta i en grafisk filhanterare, men mer avancerade uppgifter kan vara enklare med kommandoraden. Hur skulle vi till exempel kopiera alla HTML-filer från en katalog till en annan, men bara kopiera filer som saknas i målkatalogen eller är nyare än versionerna där? Det är svårt i en filhanterare men ganska lätt på kommandoraden.
cp -u *.html destination
16.1. Jokertecken
Innan vi börjar använda kommandona behöver vi tala om en skalfunktion som gör dem så kraftfulla. Eftersom skalet arbetar så mycket med filnamn finns det specialtecken som gör att vi snabbt kan ange grupper av filnamn. Dessa specialtecken kallas jokertecken. Att använda jokertecken (även kallat globbning) gör det möjligt att välja filnamn utifrån teckenmönster. Tabell 4-1 visar jokertecknen och vad de väljer.
| Jokertecken | Betydelse |
|---|---|
|
Matchar alla tecken, inklusive inga alls. |
|
Matchar ett enskilt tecken. |
|
Matchar alla tecken som ingår i uppsättningen |
|
Matchar alla tecken som inte ingår i uppsättningen |
[[:class:]] |
Matchar alla tecken som tillhör den angivna klassen. |
Med jokertecken går det att bygga avancerade urvalskriterier för filnamn. Tabell 4-3 ger några exempel på mönster och vad de matchar.
| Teckenklass | Betydelse |
|---|---|
|
Matchar alla alfanumeriska tecken. |
|
Matchar alla bokstäver. |
|
Matchar alla siffror. |
|
Matchar alla gemener. |
|
Matchar alla versaler. |
Jokertecken kan användas med alla kommandon som tar filnamn som argument, men vi återkommer till det i kapitel 7.
| Mönster | Matchar |
|---|---|
|
Alla filer. |
|
Alla filer som börjar med |
|
Alla filer som börjar med |
|
Alla filer som börjar med |
|
Alla filer som börjar med antingen |
|
Alla filer som börjar med |
[[:upper:]]* |
Alla filer som börjar med en versal. |
|
Alla filer som inte börjar med en siffra. |
|
Alla filer som slutar med en gemen eller siffrorna |
16.1.1. Teckenintervall
Om du kommer från en annan Unix-liknande miljö eller har läst andra böcker i ämnet kan du ha stött på intervallnotationerna [A-Z] och [a-z]. Det är traditionella Unixnotationer och de fungerade också i äldre Linuxversioner. De kan fortfarande fungera, men du behöver vara försiktig eftersom de inte alltid ger förväntat resultat om miljön inte är rätt konfigurerad. Tills vidare bör du undvika dem och använda mönster som [[:upper:]] och [[:lower:]] i stället.
16.2. Punktfiler
Om vi tittar i hemkatalogen med ls och flaggan -a ser vi en rad filer och kataloger vars namn börjar med punkt. Som vi redan har diskuterat är dessa dolda filer. Det är inget särskilt filattribut; det betyder bara att filen inte visas i utdata från ls om inte -a eller -A används. Det dolda beteendet gäller också jokertecken. Dolda filer visas inte om vi inte använder ett mönster som .*. Under vissa omständigheter (beroende på hur skalet är inställt) kan vi dock också få med både . (aktuell katalog) och .. (överordnad katalog) i resultatet. För att utesluta dem kan vi använda mönster som .[!.]* eller .??*.
16.2.1. Jokertecken fungerar i grafiska gränssnitt också
Jokertecken är särskilt värdefulla, inte bara för att de används så ofta på kommandoraden, utan också för att vissa grafiska filhanterare stöder dem.
-
I Nautilus (filhanteraren i GNOME) kan du välja filer genom att trycka
Ctrl-soch ange ett urvalsmönster med jokertecken; då markeras filerna i den katalog som visas. -
I Dolphin (filhanteraren i KDE) finns ett val som heter
Filter…där du kan göra urval med jokertecken. Om du till exempel vill se alla filer som börjar med gemenaui katalogen/usr/bin, skriv/usr/bin/u*i filterrutan så visas resultatet.
Många idéer som från början fanns i kommandoradsgränssnittet har hittat sin väg till det grafiska gränssnittet också. Det är en av många saker som gör Linuxskrivbordet så kraftfullt.
16.3. mkdir - skapa kataloger
Kommandot mkdir används för att skapa kataloger. Det fungerar så här:
mkdir directory...
En notering om notation: när tre punkter följer ett argument i beskrivningen av ett kommando (som ovan) betyder det att argumentet kan upprepas. Kommandot nedan skapar därför en katalog som heter dir1:
mkdir dir1
medan kommandot nedan skapar tre kataloger med namnen dir1, dir2 och dir3:
mkdir dir1 dir2 dir3
16.4. cp - kopiera filer och kataloger
Kommandot cp kopierar filer eller kataloger. Det kan användas på två sätt. Följande kopierar en enskild fil eller katalog item1 till filen eller katalogen item2:
cp item1 item2
medan följande kommando kopierar flera objekt (filer eller kataloger) till en katalog:
cp item... directory
16.4.1. Användbara flaggor och exempel
Tabell 4-4 listar några vanliga flaggor för cp.
| Flagga | Lång flagga | Betydelse |
|---|---|---|
|
|
Kopiera filer och kataloger med alla deras attribut, inklusive ägarskap och rättigheter. |
|
|
Fråga användaren om bekräftelse innan en befintlig fil skrivs över. |
|
|
Kopiera kataloger och deras innehåll rekursivt. Denna flagga (eller |
|
|
Vid kopiering av filer från en katalog till en annan, kopiera bara filer som inte finns eller som är nyare än motsvarande filer i målkatalogen. |
|
|
Visa informativa meddelanden under kopieringen. |
Tabell 4-5 visar några typiska användningsfall för cp.
| Kommando | Resultat |
|---|---|
|
Kopiera |
|
Samma som föregående kommando, men om |
|
Kopiera |
|
Kopiera alla filer i |
|
Kopiera innehållet i katalogen |
16.5. mv - flytta och byt namn på filer
Kommandot mv används både för att flytta filer och byta namn på dem, beroende på hur det används. I båda fallen finns det ursprungliga filnamnet inte kvar efter operationen. mv används ungefär på samma sätt som cp, som här för att flytta eller byta namn på filen eller katalogen item1 till item2:
mv item1 item2
mv item... directory
16.5.1. Användbara flaggor och exempel
mv delar flera flaggor med cp, enligt tabell 4-6.
| Flagga | Lång flagga | Betydelse |
|---|---|---|
|
|
Fråga användaren om bekräftelse innan en befintlig fil skrivs över. |
|
|
Vid flytt av filer från en katalog till en annan, flytta bara filer som inte finns eller som är nyare än motsvarande filer i målkatalogen. |
|
|
Visa informativa meddelanden under flytten. |
| Kommando | Resultat |
|---|---|
|
Flytta |
|
Samma som föregående kommando, men om |
|
Flytta |
|
Om katalogen |
16.6. rm - ta bort filer och kataloger
Kommandot rm används för att ta bort (radera) filer och kataloger, som i följande exempel:
rm item...
Här är item en eller flera filer eller kataloger.
16.6.1. Användbara flaggor och exempel
| Flagga | Lång flagga | Betydelse |
|---|---|---|
|
|
Fråga användaren om bekräftelse innan en befintlig fil tas bort. |
|
|
Ta bort kataloger och deras innehåll rekursivt. |
|
|
Ignorera obefintliga filer och fråga inte. Åsidosätter |
|
|
Visa informativa meddelanden under borttagningen. |
| Kommando | Resultat |
|---|---|
|
Ta bort |
|
Samma som föregående kommando, men användaren tillfrågas om bekräftelse innan borttagning. |
|
Ta bort |
|
Samma som föregående kommando, men om |
16.6.2. Var försiktig med rm!
Unix-liknande operativsystem som Linux har inget kommando för att ångra radering. När du tar bort något med rm är det borta. Linux utgår från att du vet vad du gör.
Var särskilt försiktig med jokertecken. Här är ett klassiskt exempel. Om du vill ta bort bara HTML-filer i en katalog skriver du:
rm *.html
Det är korrekt. Men om du av misstag sätter ett mellanslag mellan \* och .html så här:
rm * .html
kommer rm att ta bort alla filer i katalogen och därefter klaga på att det inte finns någon fil som heter .html.
Ett användbart tips: när du använder jokertecken med rm, testa mönstret först med ls. Då ser du vilka filer som kommer att tas bort. Tryck sedan uppåtpil för att hämta kommandot och ersätt ls med rm.
16.7. ln - skapa länkar
Kommandot ln används för att skapa antingen hårda eller symboliska länkar. Det används på ett av två sätt. Följande skapar en hård länk:
ln file link
Följande skapar en symbolisk länk:
ln -s item link
där item är antingen en fil eller en katalog.
16.7.1. Hårda länkar
Hårda länkar är det ursprungliga Unixsättet att skapa länkar, jämfört med symboliska länkar som är modernare. Som standard har varje fil en enda hård länk som ger filen dess namn. När vi skapar en hård länk skapar vi en extra katalogpost för en fil. Hårda länkar har två viktiga begränsningar:
-
En hård länk kan inte peka på en fil utanför sitt eget filsystem. Det betyder att länken inte kan peka på en fil som ligger på en annan diskpartition.
-
En hård länk får inte peka på en katalog.
En hård länk går inte att skilja från själva filen. Till skillnad från en symbolisk länk syns ingen särskild markering i en kataloglistning. När en hård länk tas bort försvinner länken, men filens innehåll finns kvar (det vill säga att utrymmet inte frigörs) tills alla länkar till filen har tagits bort.
Det är viktigt att känna till hårda länkar eftersom du kan stöta på dem då och då, men i modern praxis används oftare symboliska länkar, som vi går vidare med nu.
16.7.2. Symboliska länkar
Symboliska länkar skapades för att komma runt begränsningarna hos hårda länkar. De fungerar genom att skapa en särskild filtyp som innehåller en textpekare till den refererade filen eller katalogen. På det sättet fungerar de ungefär som genvägar i Windows, även om symboliska länkar förstås fanns långt tidigare.
En fil som en symbolisk länk pekar på och själva länken är i stort sett oskiljbara i användning. Om vi till exempel skriver till den symboliska länken skrivs data till den refererade filen. Men när vi tar bort en symbolisk länk är det bara länken som tas bort, inte filen. Om filen tas bort före länken kommer länken att finnas kvar men peka på ingenting. Då säger man att länken är bruten. I många implementationer visar ls brutna länkar i en avvikande färg, till exempel rött.
Länkkonceptet kan kännas förvirrande, men håll ut. Vi ska prova allt det här i praktiken, och då klarnar det.
16.8. Bygg en övningsplats
Eftersom vi nu ska göra verkliga filoperationer bygger vi en säker plats att "leka" på med våra kommandon. Först behöver vi en arbetskatalog. Vi skapar en i hemkatalogen och kallar den playground.
16.8.1. Skapa kataloger
mkdir används för att skapa kataloger. För att skapa vår playground-katalog ser vi först till att vi står i hemkatalogen och skapar sedan den nya katalogen.
[me@linuxbox ~]$ cd
[me@linuxbox ~]$ mkdir playground
För att göra playground lite mer intressant skapar vi två kataloger i den: dir1 och dir2. Vi byter aktuell arbetskatalog till playground och kör mkdir igen.
[me@linuxbox ~]$ cd playground
[me@linuxbox playground]$ mkdir dir1 dir2
Lägg märke till att mkdir tar flera argument och därför kan skapa båda katalogerna med ett enda kommando.
16.8.2. Kopiera filer
Nu lägger vi in lite data i playground genom att kopiera en fil. Med kommandot cp kopierar vi filen passwd från katalogen /etc till aktuell arbetskatalog.
[me@linuxbox playground]$ cp /etc/passwd .
Notera att vi använder kortformen för aktuell arbetskatalog, den avslutande punkten. Om vi nu kör ls ser vi filen.
[me@linuxbox playground]$ ls -l
total 12
drwxrwxr-x 2 me me 4096 2025-01-10 16:40 dir1
drwxrwxr-x 2 me me 4096 2025-01-10 16:40 dir2
-rw-r--r-- 1 me me 1650 2025-01-10 16:07 passwd
För att se vad flaggan -v gör upprepar vi kopieringen med utförlig utdata.
[me@linuxbox playground]$ cp -v /etc/passwd .
`/etc/passwd' -> `./passwd'
cp kopierade filen igen men visade nu ett kort meddelande om operationen. Lägg märke till att cp skrev över den första kopian utan varning. Även här antar cp att vi vet vad vi gör. För att få en fråga lägger vi till flaggan -i (interaktiv).
[me@linuxbox playground]$ cp -i /etc/passwd .
cp: overwrite `./passwd'?
Svarar du y skrivs filen över; alla andra tecken (till exempel n) gör att cp lämnar filen som den är.
16.8.3. Flytta och byt namn på filer
Namnet passwd känns inte särskilt lekfullt, och det här är ju en lekplats, så vi byter namn till något annat.
[me@linuxbox playground]$ mv passwd fun
Nu skickar vi runt fun lite genom att flytta den omdöpta filen till katalogerna och tillbaka igen. Först till dir1:
[me@linuxbox playground]$ mv fun dir1
[me@linuxbox playground]$ mv dir1/fun dir2
[me@linuxbox playground]$ mv dir2/fun .
Sedan från dir1 till dir2:
[me@linuxbox playground]$ mv fun dir1
[me@linuxbox playground]$ mv dir1 dir2
[me@linuxbox playground]$ ls -l dir2
total 4
drwxrwxr-x 2 me me 4096 2025-01-11 06:06 dir1
[me@linuxbox playground]$ ls -l dir2/dir1
total 4
-rw-r--r-- 1 me me 1650 2025-01-10 16:33 fun
Till sist tillbaka till aktuell arbetskatalog:
[me@linuxbox playground]$ mv dir2/dir1 .
[me@linuxbox playground]$ mv dir1/fun .
16.8.4. Skapa hårda länkar
Nu provar vi länkar. Vi börjar med att skapa några hårda länkar till vår datafil:
[me@linuxbox playground]$ ln fun fun-hard
[me@linuxbox playground]$ ln fun dir1/fun-hard
[me@linuxbox playground]$ ln fun dir2/fun-hard
Nu har vi fyra instanser av filen fun. Låt oss titta i playground-katalogen.
[me@linuxbox playground]$ ls -l
total 16
drwxrwxr-x 2 me me 4096 2025-01-14 16:17 dir1
drwxrwxr-x 2 me me 4096 2025-01-14 16:17 dir2
-rw-r--r-- 4 me me 1650 2025-01-10 16:33 fun
-rw-r--r-- 4 me me 1650 2025-01-10 16:33 fun-hard
Vi ser att det andra fältet i listningen för både fun och fun-hard innehåller siffran 4, vilket är antalet hårda länkar som nu finns till filen. Kom ihåg att en fil alltid har minst en länk eftersom filens namn skapas via en länk. Hur vet vi då att fun och fun-hard faktiskt är samma fil? Här hjälper ls inte hela vägen. Vi ser att fun och fun-hard har samma storlek (fält 5), men listningen räcker inte för att vara säker. Då måste vi gå lite djupare.
Kommandot ls kan visa så kallad inodinformation. Det anropas med flaggan -i.
[me@linuxbox playground]$ ls -li
total 16
12353539 drwxrwxr-x 2 me me 4096 2025-01-14 16:17 dir1
12353540 drwxrwxr-x 2 me me 4096 2025-01-14 16:17 dir2
12353538 -rw-r--r-- 4 me me 1650 2025-01-10 16:33 fun
12353538 -rw-r--r-- 4 me me 1650 2025-01-10 16:33 fun-hard
I den här versionen av listningen är det första fältet inodnumret, och som vi ser delar fun och fun-hard samma inodnummer, vilket bekräftar att de är samma fil.
16.8.5. Skapa symboliska länkar
Symboliska länkar skapades för att lösa de två nackdelarna med hårda länkar:
-
Hårda länkar kan inte spänna över fysiska enheter.
-
Hårda länkar kan inte peka på kataloger, bara filer.
Symboliska länkar är en särskild filtyp som innehåller en textpekare till målfilen eller målkatalogen. Att skapa symboliska länkar liknar att skapa hårda länkar.
[me@linuxbox playground]$ ln -s fun fun-sym
[me@linuxbox playground]$ ln -s ../fun dir1/fun-sym
[me@linuxbox playground]$ ln -s ../fun dir2/fun-sym
Det första exemplet är rakt på sak: vi lägger till flaggan -s för att skapa en symbolisk länk i stället för en hård. Men de två följande då? Kom ihåg att när vi skapar en symbolisk länk skapar vi en textbeskrivning av var målfilen finns relativt länken. Det syns tydligare i ls-utdata:
[me@linuxbox playground]$ ls -l dir1
total 4
-rw-r--r-- 4 me me 1650 2025-01-10 16:33 fun-hard
lrwxrwxrwx 1 me me 6 2025-01-15 15:17 fun-sym -> ../fun
Listningen för fun-sym i dir1 visar med inledande l att det är en symbolisk länk, och att den pekar på ../fun, vilket stämmer. Relativt platsen för fun-sym finns fun i katalogen ovanför. Notera också att längden på den symboliska länken är 6, alltså antalet tecken i strängen ../fun, och inte längden på filen som länken pekar på.
När vi skapar symboliska länkar kan vi använda antingen absoluta sökvägar, som här:
[me@linuxbox playground]$ ln -s /home/me/playground/fun dir1/fun-sym
eller relativa sökvägar, som i vårt tidigare exempel. I de flesta fall är relativa sökvägar att föredra, eftersom de oftast gör att ett katalogträd med symboliska länkar och mål kan byta namn eller flyttas utan att länkar bryts.
Utöver vanliga filer kan symboliska länkar också peka på kataloger.
[me@linuxbox playground]$ ln -s dir1 dir1-sym
[me@linuxbox playground]$ ls -l
total 16
drwxrwxr-x 2 me me 4096 2025-01-15 15:17 dir1
lrwxrwxrwx 1 me me 4 2025-01-16 14:45 dir1-sym -> dir1
drwxrwxr-x 2 me me 4096 2025-01-15 15:17 dir2
-rw-r--r-- 4 me me 1650 2025-01-10 16:33 fun
-rw-r--r-- 4 me me 1650 2025-01-10 16:33 fun-hard
lrwxrwxrwx 1 me me 3 2025-01-15 15:15 fun-sym -> fun
16.8.6. Ta bort filer och kataloger
Som vi gick igenom tidigare används rm för att ta bort filer och kataloger. Vi använder det nu för att städa lite på vår övningsplats. Först tar vi bort en av våra hårda länkar.
[me@linuxbox playground]$ rm fun-hard
[me@linuxbox playground]$ ls -l
total 12
drwxrwxr-x 2 me me 4096 2025-01-15 15:17 dir1
lrwxrwxrwx 1 me me 4 2025-01-16 14:45 dir1-sym -> dir1
drwxrwxr-x 2 me me 4096 2025-01-15 15:17 dir2
-rw-r--r-- 3 me me 1650 2025-01-10 16:33 fun
lrwxrwxrwx 1 me me 3 2025-01-15 15:15 fun-sym -> fun
Det fungerade som väntat. Filen fun-hard är borta, och länkräkningen för fun har minskat från fyra till tre, vilket syns i andra fältet i listningen.
Nu tar vi bort filen fun, och för nöjes skull lägger vi till -i för att se vad som händer.
[me@linuxbox playground]$ rm -i fun
rm: remove regular file `fun'?
Skriv y i prompten så tas filen bort. Titta sedan på ls-utdata. Ser du vad som hände med fun-sym? Eftersom den är en symbolisk länk till en fil som inte längre finns är länken nu bruten.
[me@linuxbox playground]$ ls -l
total 8
drwxrwxr-x 2 me me 4096 2025-01-15 15:17 dir1
lrwxrwxrwx 1 me me 4 2025-01-16 14:45 dir1-sym -> dir1
drwxrwxr-x 2 me me 4096 2025-01-15 15:17 dir2
lrwxrwxrwx 1 me me 3 2025-01-15 15:15 fun-sym -> fun
De flesta Linuxdistributioner konfigurerar ls så att brutna länkar syns tydligt. En bruten länk är inte farlig i sig, men den är rörig. Om vi försöker använda en bruten länk får vi detta:
[me@linuxbox playground]$ less fun-sym
fun-sym: No such file or directory
Vi städar lite till och tar bort de symboliska länkarna.
[me@linuxbox playground]$ rm fun-sym dir1-sym
[me@linuxbox playground]$ ls -l
total 8
drwxrwxr-x 2 me me 4096 2025-01-15 15:17 dir1
drwxrwxr-x 2 me me 4096 2025-01-15 15:17 dir2
En sak att minnas om symboliska länkar är att de flesta filoperationer utförs på länkens mål, inte på länken själv. rm är ett undantag. När vi tar bort en länk är det länken som tas bort, inte målet.
Till sist tar vi bort vår playground-katalog. Vi går tillbaka till hemkatalogen och använder rm med den rekursiva flaggan (-r) för att ta bort playground och allt innehåll, inklusive underkataloger.
[me@linuxbox playground]$ cd
[me@linuxbox ~]$ rm -r playground
16.8.7. Skapa symlänkar i grafiskt gränssnitt
Filhanterarna i både GNOME och KDE erbjuder ett enkelt och automatiskt sätt att skapa symboliska länkar. I GNOME skapar du en länk i stället för att kopiera eller flytta filen genom att hålla Ctrl+Shift medan du drar filen. I KDE visas en liten meny när en fil släpps, där du kan välja att kopiera, flytta eller länka filen.
16.9. Sammanfattning
Vi har gått igenom mycket här, och det tar en stund innan allt sitter. Gör övningen med playground om och om igen tills den känns självklar. Det är viktigt att förstå de grundläggande kommandona för filhantering och jokertecken ordentligt. Bygg gärna ut övningen med fler filer och kataloger och använd jokertecken för att ange filer i olika operationer. Länkbegreppet är lite förvirrande i början, men ta dig tid att lära dig hur det fungerar. Det kan rädda dig många gånger.
16.10. Vidare läsning
-
En genomgång av symboliska länkar:
17. 5 - Arbeta med kommandon
Hittills har vi sett en rad mystiska kommandon, vart och ett med sina egna mystiska flaggor och argument. I det här kapitlet försöker vi skingra en del av mystiken och till och med skapa egna kommandon. Följande kommandon introduceras:
-
type— visa hur ett kommandonamn tolkas. -
which— visa vilket körbart program som kommer att köras. -
help— visa hjälp för skalets inbyggda kommandon. -
man— visa manualsida för ett kommando. -
apropos— visa en lista över passande kommandon. -
info— visa ett programs info-post. -
whatis— visa enradiga beskrivningar från manualsidor. -
alias— skapa ett alias för ett kommando.
17.1. Vad är egentligen kommandon?
Ett kommando kan vara en av fyra olika saker:
-
Ett körbart program, som alla filer vi såg i
/usr/bin. I denna kategori finns både kompilerade binärer, till exempel program skrivna i C och C++, och program skrivna i skriptspråk som skalet, Perl, Python, Ruby med flera. -
Ett kommando som är inbyggt i själva skalet.
bashstöder ett antal sådana inbyggda kommandon.cdär till exempel ett inbyggt kommando i skalet. -
En skalfunktion. Skalfunktioner är små skalskript inbyggda i miljön.
-
Ett alias. Alias är kommandon vi själva definierar, byggda av andra kommandon.
17.2. Identifiera kommandon
Det är ofta nyttigt att veta exakt vilken av de fyra typerna som används, och Linux har några sätt att ta reda på det.
17.2.1. type — visa ett kommandos typ
Kommandot type är ett inbyggt skalkommando som visar vilken typ av kommando skalet kommer att köra för ett givet kommandonamn. Det används så här:
type command
Här är command namnet på kommandot vi vill undersöka. Titta på några exempel:
[me@linuxbox ~]$ type type
type is a shell builtin
[me@linuxbox ~]$ type ls
ls is aliased to `ls --color=auto'
[me@linuxbox ~]$ type cp
cp is /usr/bin/cp
Här ser vi resultat för tre olika kommandon. Lägg märke till raden för ls: ls är faktiskt ett alias för ls med flaggan --color=auto tillagd. Nu vet vi varför utdata från ls visas i färg.
17.2.2. which — visa var ett körbart program finns
Ibland finns mer än en version av ett körbart program installerad på ett system. Det är ovanligt på skrivbordssystem, men inte ovanligt på större servrar. För att ta reda på den exakta platsen för ett körbart program använder vi which.
[me@linuxbox ~]$ which ls
/usr/bin/ls
which fungerar bara för körbara program, inte för inbyggda kommandon eller alias som ersätter faktiska körbara program. Om vi till exempel försöker använda which på cd får vi ingen respons eller ett felmeddelande:
[me@linuxbox ~]$ which cd
/usr/bin/which: no cd in (/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games)
Det här är ett fint sätt att säga "kommando hittades inte".
17.3. Hitta dokumentation för ett kommando
Nu när vi vet vad ett kommando kan vara kan vi börja leta upp den dokumentation som finns för varje kommandotyp.
17.3.1. help — få hjälp för inbyggda skalkommandon
bash har inbyggd hjälp för varje inbyggt kommando. För att använda den skriver du help följt av namnet på det inbyggda kommandot. Exempel:
[me@linuxbox ~]$ help cd
cd: cd [-L|[-P [-e]] [-@]] [dir]
Change the shell working directory.
Den faktiska utdatan är längre, men det viktiga är att help ger kortfattad dokumentation specifik för inbyggda kommandon.
En notering om notation: när hakparenteser förekommer i beskrivningen av ett kommandos syntax markerar de valfria delar. Ett lodstreck anger ömsesidigt uteslutande alternativ. Ta det föregående cd-kommandot som exempel:
cd [-L|[-P[-e]]] [dir]
Denna notation säger att kommandot cd valfritt kan följas av antingen -L eller -P; vidare, om flaggan -P anges kan flaggan -e läggas till, följt av det valfria argumentet dir.
Även om utdata från help för kommandot cd är kortfattad och korrekt, är den inte alls pedagogisk. Tips: genom att använda help med flaggan -m visar help sin utdata i ett alternativt format som liknar en manualsida.
17.3.2. --help — visa användningsinformation
Många körbara program stöder flaggan --help som visar en beskrivning av kommandots syntax och flaggor. Till exempel:
[me@linuxbox ~]$ mkdir --help
Usage: mkdir [OPTION] DIRECTORY...
Create the DIRECTORY(ies), if they do not already exist.
Vissa program stöder inte --help, men prova ändå. Ofta resulterar det i ett felmeddelande som ändå visar användningsinformationen.
17.3.3. man — visa ett programs manualsida
De flesta körbara program som är avsedda för kommandoraden har en formell dokumentationssida, en manualsida (man page). Ett särskilt sidvisarprogram som heter man används för att visa dem.
man program
Manualsidor kan variera lite i format, men innehåller vanligtvis följande:
-
En titel (sidans namn)
-
En sammanfattning av kommandots syntax
-
En beskrivning av kommandots syfte
-
En lista med beskrivning av varje flagga
Manualsidor innehåller däremot oftast inte exempel och är avsedda som uppslagsverk, inte handledningar. Som exempel kan vi visa manualsidan för kommandot ls.
[me@linuxbox ~]$ man ls
På de flesta Linuxsystem använder man less för att visa manualsidan, så alla välbekanta less-kommandon fungerar även där.
Manualen som man visar är uppdelad i avsnitt och täcker inte bara användarkommandon utan även systemadministrationskommandon, programmeringsgränssnitt, filformat med mera. Tabell 5-1 beskriver upplägget.
| Avsnitt | Innehåll |
|---|---|
|
Användarkommandon. |
|
Programmeringsgränssnitt för kärnans systemanrop. |
|
Programmeringsgränssnitt till C-biblioteket. |
|
Specialfiler, till exempel enhetsnoder och drivrutiner. |
|
Filformat. |
|
Spel och nöjen, till exempel skärmsläckare. |
|
Blandat. |
|
Systemadministrationskommandon. |
Ibland behöver vi hänvisa till ett visst avsnitt i manualen för att hitta det vi söker. Det gäller särskilt om vi letar efter ett filformat som också är namnet på ett kommando. Utan avsnittsnummer får vi alltid den första träffen, troligen i avsnitt 1. För att ange avsnittsnummer använder vi man så här:
man section search_term
Här är ett exempel:
[me@linuxbox ~]$ man 5 passwd
Detta visar manualsidan som beskriver filformatet för /etc/passwd.
17.3.4. apropos — visa relevanta kommandon
Det går också att söka i listan över manualsidor efter möjliga träffar utifrån en sökterm. Det är grovt men ibland hjälpsamt.
[me@linuxbox ~]$ apropos partition
fdisk (8) - manipulate disk partition table
sfdisk (8) - partition table manipulator for Linux
Det första fältet i varje rad av utdatan är namnet på manualsidan, och det andra fältet visar avsnittet. Observera att man med flaggan -k gör samma sak som apropos.
17.3.5. whatis — visa enradiga beskrivningar av manualsidor
Programmet whatis visar namnet och en enradig beskrivning av en manualsida som matchar ett angivet nyckelord:
[me@linuxbox ~]$ whatis ls
ls (1) - list directory contents
17.3.6. Den mest brutala manualsidan av dem alla
Notering:
Som vi har sett är manualsidorna som medföljer Linux och andra Unix-liknande system avsedda som referensdokumentation, inte som handledningar. Många manualsidor är svårlästa, men priset för svårast går troligen till manualsidan för bash.
17.3.7. info — visa ett programs info-post
GNU-projektet tillhandahåller ett alternativ till manualsidor för sina program, kallat info. Info-manualer visas med ett läsarprogram som, passande nog, heter info.
Infosidor är hyperlänkade ungefär som webbsidor. Programmet info läser info-filer som är strukturerade i enskilda noder, var och en med ett enskilt ämne. En hyperlänk känns igen på sin inledande asterisk och aktiveras genom att placera markören på den och trycka Enter.
För att starta info skriver du info valfritt följt av programnamnet. Tabell 5-2 beskriver kommandona som används för att styra läsaren medan en info-sida visas.
| Kommando | Åtgärd |
|---|---|
|
Visa hjälp för kommandot. |
|
Visa föregående sida. |
|
Visa nästa sida. |
|
Visa nästa nod. |
|
Visa föregående nod. |
|
Visa föräldranoden till den nod som visas just nu. |
|
Följ hyperlänken vid markörens position. |
|
Avsluta. |
De flesta kommandoradsprogram vi har diskuterat hittills ingår i GNU-projektets coreutils-paket, så genom att skriva:
[me@linuxbox ~]$ info coreutils
visas en menysida med hyperlänkar till varje program i coreutils-paketet.
17.3.8. README och andra dokumentationsfiler för program
Många programpaket installerade i systemet har dokumentationsfiler i katalogen /usr/share/doc. De flesta lagras i ren text och kan visas med less. Vissa filer är i HTML-format och kan visas i en webbläsare. Vi kan också stöta på filer som slutar med .gz. Det betyder att de är komprimerade med gzip. Gzip-paketet innehåller en särskild variant av less som heter zless och som kan visa innehållet i gzip-komprimerade textfiler.
17.4. Skapa egna kommandon med alias
Nu får vi vår första erfarenhet av programmering. Vi skapar ett eget kommando med alias. Innan vi börjar behöver vi visa ett litet kommandoradstrick. Man kan lägga flera kommandon på samma rad genom att separera dem med semikolon.
command1; command2; command3...
Här är exemplet vi ska använda:
[me@linuxbox ~]$ cd /usr; ls; cd -
bin games include lib local sbin share src
/home/me
[me@linuxbox ~]$
Som vi ser har vi kombinerat tre kommandon på en rad. Först byter vi katalog till /usr, sedan listar vi katalogen, och till sist återgår vi till den ursprungliga katalogen med cd -.
Nu gör vi den sekvensen till ett nytt kommando med alias. Först måste vi hitta på ett namn till kommandot. Vi provar test. Innan vi gör det är det klokt att kontrollera om namnet redan används.
[me@linuxbox ~]$ type test
test is a shell builtin
Hoppsan. Namnet test är upptaget. Vi provar foo:
[me@linuxbox ~]$ type foo
bash: type: foo: not found
Bra, foo är ledigt. Då skapar vi aliaset:
[me@linuxbox ~]$ alias foo='cd /usr; ls; cd -'
Lägg märke till strukturen:
alias name='string'
Efter kommandot alias anger vi namnet, direkt följt av likhetstecken och därefter en citerad sträng med innebörden som ska kopplas till namnet. När aliaset är definierat kan vi använda det överallt där skalet väntar sig ett kommando.
[me@linuxbox ~]$ foo
bin games include lib local sbin share src
/home/me
[me@linuxbox ~]$
Vi kan också använda type igen för att se aliaset:
[me@linuxbox ~]$ type foo
foo is aliased to `cd /usr; ls; cd -'
För att ta bort ett alias används unalias:
[me@linuxbox ~]$ unalias foo
[me@linuxbox ~]$ type foo
bash: type: foo: not found
Vi undvek medvetet att ge aliaset samma namn som ett befintligt kommando, men det är inte ovanligt att man gör just det. Ofta görs det för att lägga till en ofta önskad flagga varje gång ett vanligt kommando används. Vi såg tidigare hur ls ofta aliasas för att lägga till färgstöd:
[me@linuxbox ~]$ type ls
ls is aliased to `ls --color=auto'
För att se alla alias som finns definierade i miljön, kör alias utan argument:
[me@linuxbox ~]$ alias
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls --color=auto'
Det finns ett litet problem med att definiera alias direkt på kommandoraden: de försvinner när skalsessionen avslutas. I kapitel 11 går vi igenom hur du lägger in egna alias i filer som sätter upp miljön vid varje inloggning.
17.5. Sammanfattning
Nu när vi har lärt oss hur vi hittar dokumentation för kommandon: slå upp dokumentationen för alla kommandon vi har stött på hittills. Studera vilka extra flaggor som finns och prova dem.
17.6. Vidare läsning
Det finns många nätkällor för dokumentation om Linux och kommandoraden. Här är några av de bästa:
-
The Bash Reference Manual —
http://www.gnu.org/software/bash/manual/bashref.html -
The Bash FAQ —
http://mywiki.wooledge.org/BashFAQ -
GNU manuals —
http://www.gnu.org/manual/manual.html -
Wikipedia article on man pages —
http://en.wikipedia.org/wiki/Man_page
18. 6 - Omdirigering
I det här kapitlet släpper vi lös det som kanske är kommandoradens häftigaste funktion: I/O-omdirigering. I/O står för in- och utmatning, och med den här funktionen kan vi styra om in- och utmatning från och till filer, och dessutom koppla ihop flera kommandon i kraftfulla kommandokedjor. För att visa det introducerar vi följande kommandon:
-
cat— sammanfoga filer. -
sort— sortera textrader. -
uniq— rapportera eller utelämna upprepade rader. -
grep— skriv ut rader som matchar ett mönster. -
wc— skriv ut rad-, ord- och byteantal för varje fil. -
head— skriv ut första delen av en fil. -
tail— skriv ut sista delen av en fil. -
tee— läs från standard in och skriv till standard ut och filer.
18.1. Standard in, standard ut och standard fel
Många program vi har använt hittills producerar någon form av utdata. Utdata består ofta av två typer:
-
Programmets resultat, alltså den data programmet är avsett att producera.
-
Status- och felmeddelanden som berättar hur det går för programmet.
Om vi tittar på ett kommando som ls ser vi att både resultat och felmeddelanden visas på skärmen.
I linje med Unix-temat "allt är en fil" skickar program som ls i själva verket sina resultat till en särskild fil som kallas standard ut (ofta stdout) och sina statusmeddelanden till en annan fil som kallas standard fel (stderr). Som standard är både standard ut och standard fel kopplade till skärmen och sparas inte i en diskfil.
Dessutom tar många program indata från en funktion som kallas standard in (stdin), som som standard är kopplad till tangentbordet.
I/O-omdirigering låter oss ändra vart utdata går och varifrån indata kommer. Normalt går utdata till skärmen och indata kommer från tangentbordet, men med I/O-omdirigering kan vi ändra det.
18.2. Omdirigera standard ut
I/O-omdirigering gör det möjligt att omdefiniera vart standard ut går. För att omdirigera standard ut till en fil i stället för skärmen använder vi operatorn > följt av filnamnet.
[me@linuxbox ~]$ ls -l /usr/bin > ls-output.txt
Här skapade vi en lång listning av katalogen /usr/bin och skickade resultatet till filen ls-output.txt. Vi kan granska den omdirigerade utdata:
[me@linuxbox ~]$ ls -l ls-output.txt
-rw-rw-r-- 1 me me 167878 2025-02-01 15:07 ls-output.txt
Om vi öppnar filen med less ser vi att ls-output.txt faktiskt innehåller resultatet från ls-kommandot.
[me@linuxbox ~]$ less ls-output.txt
Nu upprepar vi testet, men med en twist. Vi ändrar katalognamnet till en katalog som inte finns:
[me@linuxbox ~]$ ls -l /bin/usr > ls-output.txt
ls: cannot access /bin/usr: No such file or directory
Vi fick ett felmeddelande. Det är rimligt eftersom vi angav den obefintliga katalogen /bin/usr, men varför visades felmeddelandet på skärmen i stället för att omdirigeras till ls-output.txt? Svaret är att ls inte skickar felmeddelanden till standard ut. I stället skickar det, liksom de flesta välskrivna Unixprogram, felmeddelanden till standard fel (stderr). Eftersom vi bara omdirigerade standard ut och inte standard fel gick felmeddelandet fortfarande till skärmen.
Först tittar vi på vad som hände med utdatafilen:
[me@linuxbox ~]$ ls -l ls-output.txt
-rw-rw-r-- 1 me me 0 2025-02-01 15:08 ls-output.txt
Filen har nu längd noll. Orsaken är att när vi omdirigerar med > skrivs målfilen alltid om från början. Eftersom ls-kommandot inte gav något resultat utan bara ett felmeddelande började omdirigeringen skriva om filen och avbröts sedan av felet, vilket gav en tom fil.
Faktum är att om vi behöver tömma en fil (eller skapa en ny tom fil) kan vi använda ett litet trick:
[me@linuxbox ~]$ > ls-output.txt
Att bara använda omdirigeringsoperatorn utan kommando framför tömmer en befintlig fil eller skapar en ny tom fil.
Hur gör vi då om vi vill lägga till utdata i slutet av en fil i stället för att skriva över från början? Då använder vi operatorn >>:
[me@linuxbox ~]$ ls -l /usr/bin >> ls-output.txt
Med >> läggs utdata till i filen. Om filen inte redan finns skapas den, precis som med >.
[me@linuxbox ~]$ ls -l /usr/bin >> ls-output.txt
[me@linuxbox ~]$ ls -l /usr/bin >> ls-output.txt
[me@linuxbox ~]$ ls -l /usr/bin >> ls-output.txt
[me@linuxbox ~]$ ls -l ls-output.txt
-rw-rw-r-- 1 me me 503634 2025-02-01 15:45 ls-output.txt
Vi upprepade ls-kommandot tre gånger, vilket gav en utdatafil som blev tre gånger så stor.
18.3. Gruppera kommandon
Föreställ dig att vi vill köra en serie kommandon och skicka resultatet till en loggfil. Med det vi redan kan skulle vi kunna göra så här:
[me@linuxbox ~]$ command1 > logfile.txt
[me@linuxbox ~]$ command2 >> logfile.txt
[me@linuxbox ~]$ command3 >> logfile.txt
Tekniken fungerar, men den innebär en hel del upprepad skrivning. Som vi såg i förra kapitlet kan vi lägga flera kommandon på samma rad:
[me@linuxbox ~]$ command1; command2; command3
Vi skulle alltså kunna lägga alla kommandon och omdirigeringar på en enda rad:
[me@linuxbox ~]$ command1 > logfile.txt; command2 >> logfile.txt; command3 >> logfile.txt
Men tänk om vi kunde behandla hela följden som en enda enhet med en enda utström? Det kan vi genom att skapa ett gruppkommando. Då omger vi sekvensen med klammerparenteser:
[me@linuxbox ~]$ { command1; command2; command3; } > logfile.txt
När sekvensen omges av klammerparenteser betraktar skalet den som ett enda kommando i omdirigeringshänseende. Notera att skalet kräver blanksteg runt klammerparenteserna, och att sista kommandot i sekvensen måste avslutas med semikolon eller radbrytning.
18.4. Omdirigera standard fel
Att omdirigera standard fel är lite mindre bekvämt eftersom det saknar en egen omdirigeringsoperator. För att omdirigera standard fel måste vi hänvisa till dess fildeskriptor. Ett program kan skriva till flera numrerade filströmmar. Vi har kallat de tre första standard in, standard ut och standard fel, men skalet refererar till dem internt som fildeskriptor 0, 1 respektive 2.
Eftersom standard fel är fildeskriptor 2 kan vi omdirigera standard fel så här:
[me@linuxbox ~]$ ls -l /bin/usr 2> ls-error.txt
Fildeskriptorn 2 skrivs direkt före omdirigeringsoperatorn för att skicka standard fel till filen ls-error.txt.
18.5. Omdirigera standard ut och standard fel till samma fil
I vissa fall vill vi samla all utdata från ett kommando i en enda fil. Då måste både standard ut och standard fel omdirigeras samtidigt. Det finns två sätt. Här är det traditionella sättet:
[me@linuxbox ~]$ ls -l /bin/usr > ls-output.txt 2>&1
Med den metoden gör vi två omdirigeringar. Först omdirigerar vi standard ut till filen ls-output.txt, och sedan omdirigerar vi fildeskriptor 2 (standard fel) till fildeskriptor 1 (standard ut) med notationen 2>&1.
Lägg märke till att ordningen är avgörande. Omdirigeringen av standard fel måste alltid komma efter omdirigeringen av standard ut, annars fungerar det inte.
>ls-output.txt 2>&1
2>&1 >ls-output.txt
Nyare versioner av bash har en andra, smidigare metod för den här kombinerade omdirigeringen:
[me@linuxbox ~]$ ls -l /bin/usr &> ls-output.txt
Vi kan också lägga till båda strömmarna i slutet av en fil så här:
[me@linuxbox ~]$ ls -l /bin/usr &>> ls-output.txt
18.6. Gör dig av med oönskad utdata
Ibland är tystnad guld och vi vill inte ha utdata från ett kommando — vi vill bara kasta bort den. Systemet erbjuder ett sätt: omdirigera utdata till en särskild fil som heter /dev/null. Den här filen är en systemenhet som ofta kallas bit-hink; den tar emot indata och gör ingenting med den.
[me@linuxbox ~]$ ls -l /bin/usr 2> /dev/null
18.6.1. /dev/null i Unix-kulturen
Bit-hinken är ett gammalt Unixbegrepp, och eftersom det är så allmängiltigt har det dykt upp i många delar av Unixkulturen. Om någon säger att dina kommentarer skickas till /dev/null vet du nu vad det betyder.
18.7. Omdirigera standard in
Hittills har vi inte stött på många kommandon som använder standard in, så vi behöver introducera ett.
18.7.1. cat - sammanfoga filer
Kommandot cat läser en eller flera filer och kopierar dem till standard ut:
cat [file...]
Vi kan använda det för att visa filer utan sidindelning.
[me@linuxbox ~]$ cat ls-output.txt
Eftersom cat kan ta mer än en fil som argument kan det också användas för att slå ihop filer.
cat movie.mpeg.0* > movie.mpeg
Om cat inte får något argument läser det från standard in.
[me@linuxbox ~]$ cat
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
Utan filnamnsargument kopierar cat standard in till standard ut, och vi ser textraden upprepad. Vi kan använda det beteendet för att skapa korta textfiler:
[me@linuxbox ~]$ cat > lazy_dog.txt
The quick brown fox jumps over the lazy dog.
Vi kan använda cat för att kopiera filen till stdout igen.
[me@linuxbox ~]$ cat lazy_dog.txt
The quick brown fox jumps over the lazy dog.
Nu provar vi att omdirigera standard in.
[me@linuxbox ~]$ cat < lazy_dog.txt
The quick brown fox jumps over the lazy dog.
Med omdirigeringsoperatorn < ändrar vi källan för standard in från tangentbordet till filen lazy_dog.txt.
18.8. Rörledningar
Möjligheten för kommandon att läsa data från standard in och skriva till standard ut används av en skalfunktion som kallas rörledning (pipeline). Med rörledningsoperatorn | kan standard ut från ett kommando skickas in i standard in för ett annat.
command1 | command2
Vi kan använda less för att visa, sida för sida, utdata från vilket kommando som helst som skickar resultat till standard ut:
[me@linuxbox ~]$ ls -l /usr/bin | less
18.8.1. Skillnaden mellan > och |
OBS:
Omdirigeringsoperatorn kopplar ett kommando till en fil, medan rörledningsoperatorn kopplar utdata från ett kommando till indata för ett annat kommando.
command1 > file1
command1 | command2
Lärdomen här är att omdirigeringsoperatorn > tyst skapar eller skriver över filer, så den bör behandlas med respekt.
18.9. Filter
Rörledningar används ofta för att utföra mer avancerade databehandlingar. Det går att sätta flera kommandon i samma rörledning. Kommandon som används så kallas ofta filter. Filter tar indata, förändrar den på något sätt och skriver sedan ut resultatet.
Det första filtret vi provar är sort:
[me@linuxbox ~]$ ls /bin /usr/bin | sort | less
Genom att lägga till sort i rörledningen ändrade vi datat till en enda sorterad lista.
18.9.1. uniq - rapportera eller utelämna upprepade rader
Kommandot uniq används ofta tillsammans med sort. Som standard tar det bort dubbletter från en sorterad lista.
[me@linuxbox ~]$ ls /bin /usr/bin | sort | uniq | less
[me@linuxbox ~]$ ls /bin /usr/bin | sort | uniq -d | less
18.9.2. wc - skriv ut rad-, ord- och byteantal
Kommandot wc (word count) används för att visa antal rader, ord och byte i filer.
[me@linuxbox ~]$ wc ls-output.txt
7902 64566 503634 ls-output.txt
Flaggan -l begränsar utdata till att bara visa antal rader. I kombination med en rörledning är det ett smidigt sätt att räkna saker.
[me@linuxbox ~]$ ls /bin /usr/bin | sort | uniq | wc -l
2728
18.9.3. grep - skriv ut rader som matchar ett mönster
grep är ett kraftfullt program för att hitta textmönster i filer.
grep pattern [file...]
Anta att vi vill hitta alla filer i vår programlista som innehåller ordet zip i namnet:
[me@linuxbox ~]$ ls /bin /usr/bin | sort | uniq | grep zip
bunzip2
bzip2
gunzip
gzip
unzip
zip
zipcloak
zipgrep
zipinfo
zipnote
zipsplit
Här är några praktiska flaggor för grep:
-
-igör attgrepignorerar skiftläge vid sökning. -
-lgör attgrepbara skriver ut namn på filer som innehåller matchande text. -
-vgör attgrepbara skriver ut rader som inte matchar mönstret. -
-wgör attgrepbara matchar hela ord.
18.9.4. head / tail - skriv ut första / sista delen av filer
Ibland vill vi inte ha all utdata från ett kommando. Vi kanske bara vill ha de första eller sista raderna. Kommandot head skriver ut de första 10 raderna i en fil, och tail skriver ut de sista 10. Antalet kan ändras med flaggan -n.
[me@linuxbox ~]$ head -n 5 ls-output.txt
[me@linuxbox ~]$ tail -n 5 ls-output.txt
Dessa kommandon kan också användas i rörledningar:
[me@linuxbox ~]$ ls /usr/bin | tail -n 5
znew
zonetab2pot.py
zonetab2pot.pyc
zonetab2pot.pyo
zsoelim
Genom att använda -n med både head och tail kan vi ta ut ett utdrag från mitten av en fil:
[me@linuxbox ~]$ head -n -5 text_header_footer.txt | tail -n +6 > text.txt
tail har också en flagga som gör det möjligt att följa innehållet i en fil i realtid. Det är användbart för att bevaka hur loggfiler skrivs.
[me@linuxbox ~]$ tail -f /var/log/messages
Med flaggan -f fortsätter tail att övervaka filen, och när nya rader läggs till visas de omedelbart. Det fortsätter tills vi trycker Ctrl-c.
18.9.5. tee - läs från standard in och skriv till standard ut och filer
I Linux finns ett kommando som heter tee och som skapar en T-koppling på röret. Programmet tee läser från standard in och kopierar till både standard ut och till en eller flera filer. Det är användbart när vi vill fånga innehållet i en rörledning i ett mellanled.
[me@linuxbox ~]$ ls /usr/bin | tee ls.txt | grep zip
bunzip2
bzip2
gunzip
gzip
unzip
zip
zipcloak
zipgrep
zipinfo
zipnote
zipsplit
18.10. Sammanfattning
Som alltid: läs dokumentationen för varje kommando vi gått igenom i kapitlet. Vi har bara sett den mest grundläggande användningen. De har alla en rad intressanta flaggor. När vi får mer erfarenhet av Linux märker vi att kommandoradens omdirigering är extremt användbar för att lösa specialiserade problem.
18.10.1. Linux handlar om fantasi
När jag blir ombedd att förklara skillnaden mellan Windows och Linux använder jag ofta en leksaksjämförelse. Windows är som en Game Boy: det kommer förseglat, polerat och begränsat till vad någon annan bestämt att du behöver. Linux, å andra sidan, är som världens största Mekano-låda: en enorm samling delar som kan anta vilken form din fantasi önskar. Det gör vad du vill.
18.11. Vidare läsning
-
Wikipedia-artikel om standardströmmar: https://sv.wikipedia.org/wiki/Standardströmmar
-
Omdirigering på Wikipedia: https://sv.wikipedia.org/wiki/Omdirigering_(datorvetenskap)
-
En introduktion till I/O-omdirigering: https://www.tldp.org/LDP/abs/html/io-redirection.html
19. 7 - Se världen som skalet ser den
I det här kapitlet ska vi titta på en del av den "magi" som sker på kommandoraden när vi trycker Enter. Vi kommer att undersöka flera intressanta och ganska avancerade funktioner i skalet, men med bara ett enda nytt kommando.
-
Varje gång vi skriver ett kommando och trycker Enter utför bash flera ersättningar i texten innan kommandot körs. Vi har sett några exempel på hur en enkel teckensekvens, till exempel *, kan ha mycket innebörd för skalet. Processen som gör detta kallas expansion. Med expansion skriver vi in något, och innan skalet agerar utvidgas det till något annat.
19.1. Expansion
För att visa vad vi menar tittar vi på kommandot echo. echo är ett inbyggt skalkommando med en mycket enkel uppgift: det skriver sina textargument till standard ut.
Rakt och enkelt. Alla argument som skickas till echo skrivs ut. Vi provar ett exempel till:
[me@linuxbox ~]$ echo this is a test
this is a test
[me@linuxbox ~]$ echo *
Desktop Documents ls-output.txt Music Pictures Public Templates Videos
Varför skrev inte echo ut *? Det enkla svaret är att skalet expanderar * till något annat innan kommandot echo körs. Kommandot echo såg aldrig *, bara det expanderade resultatet.
19.1.1. Sökvägsexpansion
Mekanismen bakom jokertecken kallas sökvägsexpansion. Om vi provar några av teknikerna från tidigare kapitel ser vi att de egentligen är just expansioner. Givet en hemkatalog som ser ut så här:
[me@linuxbox ~]$ ls
Desktop
Documents
Music
Pictures
Public
Templates
Videos
ls-output.txt
[me@linuxbox ~]$ echo D*
Desktop Documents
[me@linuxbox ~]$ echo *s
Documents Pictures Templates Videos
[me@linuxbox ~]$ echo [[:upper:]]*
Desktop Documents Music Pictures Public Templates Videos
[me@linuxbox ~]$ echo /usr/*/share
/usr/kerberos/share /usr/local/share
19.1.2. Sökvägsexpansion av dolda filer
För att matcha filnamn som börjar med en punkt (dolda filer) måste vi vara försiktiga, eftersom skalet normalt inte expanderar filnamn som börjar med en punkt.
echo .[!.]*
ls -A
19.1.3. Tildundersökning
Som vi kanske minns från introduktionen av cd har tilde-tecknet (~) en särskild betydelse. När det används i början av ett ord expanderas det till hemkatalogen för den angivna användaren, eller - om ingen användare anges - hemkatalogen för aktuell användare.
[me@linuxbox ~]$ echo ~
/home/me
[me@linuxbox ~]$ echo ~bob
/home/bob
19.1.4. Aritmetisk expansion
Skalet kan utföra aritmetik via expansion. Det gör att vi kan använda skalprompten som en räknare.
[me@linuxbox ~]$ echo $((2 + 2))
4
Aritmetisk expansion använder formen:
$((expression))
där uttryck är ett aritmetiskt uttryck som består av värden och operatorer.
| Operator | Beskrivning |
|---|---|
|
Addition |
|
Subtraktion |
|
Multiplikation |
|
Division |
|
Modulo, dvs. rest |
|
Exponentiering |
Aritmetisk expansion stöder bara heltal (inga decimaler), men kan utföra en hel del olika operationer. Tabell 7-1 beskriver några av operatorerna. Mellanslag är inte signifikanta i aritmetiska uttryck, och uttryck kan nästlas.
[me@linuxbox ~]$ echo $(($((5**2)) * 3))
75
[me@linuxbox ~]$ echo $(((5**2) * 3))
75
[me@linuxbox ~]$ echo Five divided by two equals $((5/2))
Five divided by two equals 2
[me@linuxbox ~]$ echo with $((5%2)) left over.
with 1 left over.
19.1.5. Klammerexpansion
Kanske den märkligaste expansionen kallas klammerexpansion. Med den kan vi skapa flera textsträngar från ett mönster som innehåller klammerparenteser. Exempel:
[me@linuxbox ~]$ echo Front-{A,B,C}-Back
Front-A-Back Front-B-Back Front-C-Back
[me@linuxbox ~]$ echo Number_{1..5}
Number_1 Number_2 Number_3 Number_4 Number_5
[me@linuxbox ~]$ echo {01..15}
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
[me@linuxbox ~]$ echo {Z..A}
Z Y X W V U T S R Q P O N M L K J I H G F E D C B A
[me@linuxbox ~]$ echo a{A{1,2},B{3,4}}b
aA1b aA2b aB3b aB4b
Den vanligaste tillämpningen är att skapa listor med filer eller kataloger som ska skapas.
[me@linuxbox ~]$ mkdir Photos
[me@linuxbox ~]$ cd Photos
[me@linuxbox Photos]$ mkdir {2007..2009}-{01..12}
19.1.6. Parameterexpansion
Vi rör bara kort vid parameterexpansion i det här kapitlet, men vi kommer tillbaka till den grundligt senare. Det är en funktion som oftast är mer användbar i skalskript än direkt på kommandoraden. Många av dess möjligheter bygger på systemets förmåga att lagra små datamängder och ge varje sådan mängd ett namn. Många sådana datamängder - mer korrekt kallade variabler - finns tillgängliga för oss att granska. Variabeln USER innehåller till exempel vårt användarnamn. För att anropa parameterexpansion och visa innehållet i USER gör vi så här:
[me@linuxbox ~]$ echo $USER
me
[me@linuxbox ~]$ printenv | less
[me@linuxbox ~]$ echo $SUER
Om vi stavar namnet på en variabel fel sker expansionen ändå, men resultatet blir en tom sträng.
19.1.7. Kommandosubstitution
Kommandosubstitution låter oss använda utdata från ett kommando som en expansion.
[me@linuxbox ~]$ echo $(ls)
Desktop Documents ls-output.txt Music Pictures Public Templates Videos
[me@linuxbox ~]$ ls -l $(which cp)
-rwxr-xr-x 1 root root 71516 2025-12-05 08:58 /bin/cp
[me@linuxbox ~]$ ls -l `which cp`
-rwxr-xr-x 1 root root 71516 2025-12-05 08:58 /bin/cp
Vi är inte begränsade till enkla kommandon. Hela rörledningar kan också användas.
19.2. Citera
Nu när vi har sett hur många expansioner skalet kan göra är det dags att lära oss hur vi kan styra detta. Ta till exempel följande:
19.2.1. Dubbla citationstecken
Första typen av citering vi tittar på är dubbla citationstecken. Om vi placerar text i dubbla citationstecken förlorar alla specialtecken sin särskilda betydelse och behandlas som vanliga tecken. Undantagen är $, \ (omvänt snedstreck) och ` (omvänd apostrof).
[me@linuxbox ~]$ ls -l "two words.txt"
-rw-rw-r-- 1 me me 18 2016-02-20 13:03 two words.txt
[me@linuxbox ~]$ mv "two words.txt" two_words.txt
[me@linuxbox ~]$ echo "$USER $((2+2)) $(df -h)"
Det betyder att orduppdelning, sökvägsexpansion, tildeexpansion och klammerexpansion undertrycks, men parameterexpansion, aritmetisk expansion och kommandosubstitution utförs fortfarande.
19.2.2. Enkla citationstecken
Om vi behöver undertrycka alla expansioner använder vi enkla citationstecken. Här är en jämförelse mellan ociterat, dubbla citationstecken och enkla citationstecken:
[me@linuxbox ~]$ echo text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER
text /home/me/ls-output.txt a b foo 4 me
[me@linuxbox ~]$ echo "text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER"
text ~/*.txt {a,b} foo 4 me
[me@linuxbox ~]$ echo 'text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER'
text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER
19.2.3. Escape-tecken
Ibland vill vi citera bara ett enda tecken. Då kan vi sätta ett omvänt snedstreck före tecknet. I detta sammanhang kallas det escape-tecken. Ofta görs detta inuti dubbla citationstecken för att selektivt förhindra expansion:
[me@linuxbox ~]$ echo "The balance for user $USER is: \$5.00"
The balance for user me is: $5.00
[me@linuxbox ~]$ mv bad\&filename good_filename
19.2.4. Backslash-escape-sekvenser
Backslash-tecknet har en särskild betydelse i dubbla citationstecken. Det används för att skapa escape-sekvenser som representerar specialtecken.
Det är också vanligt att använda escape för att ta bort specialbetydelsen hos ett tecken i ett filnamn. Det går att använda tecken i filnamn som normalt har särskild betydelse för skalet, till exempel $, !, &, blanksteg med flera. För att inkludera ett sådant tecken i ett filnamn kan vi göra så här:
| Escape-sekvens | Betydelse |
|---|---|
|
Ljudsignal (bell) |
|
Backsteg |
|
Nyrad |
|
Vagnretur |
|
Tabb |
Om vi vill att ett omvänt snedstreck ska visas, escape:ar vi det med \\.
sleep 10; echo -e "Time's up\a"
sleep 10; echo "Time's up" $'\a'
19.3. Sammanfattning
När vi går vidare med skalet kommer vi att använda expansion och citering allt oftare, så det är klokt att bygga en stabil förståelse för hur de fungerar. Man kan faktiskt hävda att det är bland de viktigaste ämnena att lära sig om skalet. Utan ordentlig förståelse för expansion förblir skalet en källa till mystik och förvirring, och mycket av dess kraft går förlorad.
19.4. Vidare läsning
-
Manualsidan för
bashhar större avsnitt om både expansion och citering. -
Bash Reference Manual:
20. 8 - Avancerade tangentbordsknep
Jag brukar skämtsamt beskriva Unix som "operativsystemet för människor som gillar att skriva". Att det över huvud taget har en kommandorad är ett tecken på det. Men kommandoradsanvändare gillar inte att skriva mer än nödvändigt. Varför skulle annars så många kommandon ha så korta namn, som cp, ls, mv och rm? Ett av kommandoradens kärnmål är faktiskt lättja: att få så mycket gjort som möjligt med så få tangenttryckningar som möjligt. Ett annat mål är att aldrig behöva lyfta händerna från tangentbordet och sträcka sig efter musen.
I det här kapitlet tittar vi på bash-funktioner som gör tangentbordsarbetet snabbare och effektivare. Följande kommandon dyker upp:
-
clear- rensa skärmen -
history- visa innehållet i historiklistan
20.1. Redigering på kommandoraden
bash använder ett bibliotek (en delad samling rutiner som olika program kan använda) som heter Readline för att implementera redigering på kommandoraden. Vi har redan sett lite av det. Vi vet till exempel att piltangenterna flyttar markören, men det finns mycket mer. Tänk på dem som extra verktyg i arbetet. Det är inte viktigt att lära sig alla, men många är väldigt användbara. Välj det som passar dig.
Vissa tangentsekvenser nedan (särskilt de med Alt-tangenten) kan fångas av det grafiska gränssnittet för andra funktioner. Alla sekvenser bör fungera korrekt i en virtuell konsol.
20.1.1. Markörförflyttning
Följande tabell listar tangenter för att flytta markören:
| Tangent | Åtgärd |
|---|---|
|
Flytta markören till radens början. |
|
Flytta markören till radens slut. |
|
Flytta markören ett tecken framåt; samma som högerpiltangenten. |
|
Flytta markören ett tecken bakåt; samma som vänsterpiltangenten. |
|
Flytta markören ett ord framåt. |
|
Flytta markören ett ord bakåt. |
|
Rensa skärmen och flytta markören till övre vänstra hörnet. Kommandot |
20.1.2. Ändra text
Eftersom vi kan göra misstag när vi skriver kommandon behöver vi ett effektivt sätt att rätta dem. Tabell 8-2 beskriver tangentkommandon för redigering av tecken på kommandoraden.
| Tangent | Åtgärd |
|---|---|
|
Ta bort tecknet vid markörens position. |
|
Byt plats på tecknet vid markören och tecknet före det. |
|
Byt plats på ordet vid markören och ordet före det. |
|
Gör om tecknen från markörens position till slutet av ordet till gemener. |
|
Gör om tecknen från markörens position till slutet av ordet till versaler. |
20.1.3. Klipp ut och klistra in text (killing och yanking)
I Readline-dokumentationen används termerna killing och yanking för det vi vanligtvis kallar klippa ut och klistra in. Det som klipps ut lagras i en buffert (ett tillfälligt minnesområde) som kallas kill-ring.
| Tangent | Åtgärd |
|---|---|
|
Klipp ut text från markören till radens slut. |
|
Klipp ut text från markören till radens början. |
|
Klipp ut text från markören till slutet av det aktuella ordet. |
|
Klipp ut text från markören till början av det aktuella ordet. Om markören står i början av ett ord klipps föregående ord ut. |
|
Klistra in text från kill-ringen vid markörens position. |
20.1.4. Meta-tangenten
Många av de tangenter vi har använt är modifierare (Ctrl och Alt) kombinerade med andra tangenter. Det finns en tredje modifierare som kallas Meta. På moderna tangentbord finns det ingen tangent som heter Meta, men den finns ofta som Alt-tangenten. För att använda Meta-tangenten håller du ner Alt och trycker på en annan tangent. Till exempel: Meta-a.
20.2. Komplettering
Ett annat sätt som skalet kan hjälpa oss är komplettering, som sker när vi trycker Tab medan vi skriver ett kommando. Låt oss se hur det fungerar.
[me@linuxbox ~]$ ls
Desktop Documents Music Pictures Public Templates Videos ls-output.txt
Skriv följande men tryck inte Enter:
[me@linuxbox ~]$ ls l
Tryck nu Tab:
[me@linuxbox ~]$ ls ls-output.txt
Ser du hur skalet fyllde i raden åt oss? Prova en till. Återigen: tryck inte Enter.
[me@linuxbox ~]$ ls Do
[me@linuxbox ~]$ ls Documents
Ingen komplettering, bara tystnad. Det beror på att D matchar mer än en post i katalogen. För att komplettering ska lyckas måste "ledtråden" vara entydig. Går vi vidare så här:
| Tangent | Åtgärd |
|---|---|
|
Visa en lista över möjliga kompletteringar. På de flesta system kan du även göra detta genom att trycka |
|
Infoga alla möjliga kompletteringar. Detta är användbart när du vill använda mer än en möjlig träff. |
20.2.1. Programmerbar komplettering
Kompletteringsfunktionen är programmerbar och kan anpassas för olika typer av kommandon och argument.
[me@linuxbox ~]$ set | less
20.3. Använda historik
Som vi upptäckte i kapitel 1 håller bash en historik över kommandon som har körts. Listan lagras i hemkatalogen i en fil som heter .bash_history. Historikfunktionen är en användbar resurs för att minska mängden skrivande, särskilt tillsammans med redigering på kommandoraden.
20.3.1. Söka i historiken
Vi kan när som helst visa innehållet i historiklistan så här:
[me@linuxbox ~]$ history | less
Som standard lagrar de flesta moderna Linuxdistributioner de senaste 1000 kommandona. Vi ser hur värdet ändras i kapitel 11.
Anta att vi vill hitta kommandon vi använde för att lista /usr/bin. Ett sätt är:
[me@linuxbox ~]$ history | grep /usr/bin
88 ls -l /usr/bin > ls-output.txt
88 är radnumret i historiklistan. Vi kan använda det direkt med en annan expansionstyp som kallas historikexpansion. För att använda raden gör vi så här:
[me@linuxbox ~]$ !88
bash expanderar !88 till innehållet på rad 88 i historiklistan.
bash kan också söka i historiklistan inkrementellt. Tryck Ctrl-r följt av texten du söker efter. När du hittar den kan du antingen trycka Enter för att köra kommandot eller Ctrl-j för att kopiera raden från historiklistan till den aktuella kommandoraden.
(reverse-i-search)`/usr/bin': ls -l /usr/bin > ls-output.txt
[me@linuxbox ~]$ ls -l /usr/bin > ls-output.txt
Skalprompten återkommer, och kommandoraden är laddad och redo. Tabell 8-5 listar tangenter för att hantera historiklistan.
| Tangent | Åtgärd |
|---|---|
|
Gå till föregående post i historiken. Samma som uppåtpilen. |
|
Gå till nästa post i historiken. Samma som nedåtpilen. |
|
Gå till början av historiklistan. |
|
Gå till slutet av historiklistan. |
|
Omvänd inkrementell sökning. |
|
Omvänd sökning, icke-inkrementell. |
|
Framåtsökning, icke-inkrementell. |
|
Kör aktuell post i historiklistan och gå vidare till nästa. |
20.3.2. Historikexpansion
Skalet erbjuder en särskild expansionstyp för poster i historiklistan med tecknet !. Vi har redan sett hur utropstecknet kan följas av ett tal för att infoga en historikpost. Flera andra expansioner finns också, enligt tabell 8-6.
| Sekvens | Åtgärd |
|---|---|
|
Upprepa senaste kommandot. |
|
Upprepa post nummer number i historiklistan. |
|
Upprepa senaste historikposten som börjar med |
|
Upprepa senaste historikposten som innehåller |
Var försiktig med formerna !sträng och !?sträng om du inte är helt säker på vad historiken innehåller. Vi kan minska risken något genom att lägga till :p till expansionen. Då skriver skalet ut resultatet av expansionen och lägger det i historiken. Exempel:
[me@linuxbox ~]$ !ls:p
ls -l /usr/bin > ls-output.txt
Nu när kommandot har hämtats och placerats som senaste post i historiken kan vi köra det med uppåtpil + Enter eller !! + Enter.
För övrigt lagras inte själva historikexpansionen (som !!) i historiken, men resultatet gör det.
20.3.3. script
Utöver historikfunktionen i bash innehåller de flesta Linuxdistributioner ett program som heter script som kan användas för att spela in en hel terminalsession och lagra den i en fil.
script [file]
Om ingen fil anges används filen typescript.
20.4. Sammanfattning
I det här kapitlet gick vi igenom några av tangentbordstricken som skalet erbjuder för att hjälpa hårdnackade skrivare att minska arbetsbördan. Med tiden, när vi arbetar mer på kommandoraden, kan vi återvända till kapitlet och plocka upp fler av knepen. Tills vidare kan du se dem som valfria men potentiellt mycket användbara.
20.5. Vidare läsning
-
Wikipedia har en bra artikel om datorterminaler:
21. 9 - Behörigheter
Operativsystem i Unixtraditionen skiljer sig från system i MS-DOS-traditionen genom att de inte bara är fleruppgiftssystem utan också fleranvändarsystem.
Vad innebär det i praktiken? Att mer än en person kan använda datorn samtidigt. Även om en vanlig dator sannolikt bara har ett tangentbord och en skärm kan den ändå användas av flera användare. Om datorn till exempel är ansluten till ett nätverk eller internet kan fjärranvändare logga in via ssh (secure shell) och arbeta på datorn. Fjärranvändare kan till och med köra grafiska program och få grafikutmatningen visad på en fjärrskärm.
Fleranvändarfunktionen i Linux är ingen ny "innovation", utan en egenskap som sitter djupt i operativsystemets konstruktion. Med tanke på miljön där Unix skapades är det helt logiskt. Förr i tiden, innan datorer var "personliga", var de stora, dyra och centraliserade. Ett typiskt universitetssystem kunde till exempel bestå av en stor central dator i en byggnad och terminaler utspridda över campus, alla anslutna till centraldatorn. Datorn kunde stödja många användare samtidigt.
I det här kapitlet tittar vi på den här grundläggande delen av systemsäkerheten och introducerar följande kommandon:
-
id— visa användaridentitet. -
chmod— ändra en fils läge. -
umask— ställ in standardbehörigheter för filer. -
su— kör ett skal som en annan användare. -
sudo— kör ett kommando som en annan användare. -
chown— ändra ägare för en fil. -
chgrp— ändra en fils gruppägande. -
addgroup— lägg till en användare eller en grupp i systemet. -
usermod— ändra ett användarkonto. -
passwd— ändra en användares lösenord.
21.1. Användare, gruppmedlemmar och alla andra
När vi utforskade systemet i kapitel 3 kan vi ha stött på problem när vi försökte granska en fil som /etc/shadow:
[me@linuxbox ~]$ file /etc/shadow
/etc/shadow: regular file, no read permission
[me@linuxbox ~]$ less /etc/shadow
/etc/shadow: Permission denied
Orsaken är att vi som vanliga användare inte har rätt att läsa filen.
I Unix säkerhetsmodell kan en användare äga filer och kataloger. När en användare äger en fil eller katalog har användaren kontroll över åtkomsten. Användare kan i sin tur tillhöra en grupp med en eller flera användare som ges åtkomst till filer och kataloger av ägaren. Utöver att ge åtkomst till en grupp kan ägaren också ge en uppsättning rättigheter till alla andra, ofta kallade others (ibland world).
För att få information om vår identitet använder vi kommandot id:
[me@linuxbox ~]$ id
uid=500(me) gid=500(me) groups=500(me)
På andra system, till exempel Ubuntu, kan utdata se lite annorlunda ut:
[me@linuxbox ~]$ id
uid=1000(me) gid=1000(me) groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),108(lpadmin),114(admin),1000(me)
Liksom många saker i Linux kommer denna information från ett par textfiler. Användarkonton definieras i /etc/passwd och grupper definieras i /etc/group. När användarkonton och grupper skapas ändras dessa filer tillsammans med /etc/shadow, som innehåller information om användarens lösenord.
21.2. Läsa, skriva och köra
Åtkomsträttigheter för filer och kataloger definieras i termer av läsrättighet, skrivrättighet och körningsrättighet. Om vi tittar på utdata från ls får vi en ledtråd om hur detta är implementerat:
[me@linuxbox ~]$ > foo.txt
[me@linuxbox ~]$ ls -l foo.txt
-rw-rw-r-- 1 me me 0 2016-03-06 14:52 foo.txt
De första 10 tecknen i listningen är filattributen. Första tecknet anger filtypen. Tabell 9-1 beskriver de filtyper vi troligen ser oftast (det finns fler, mindre vanliga också):
| Attribut | Filtyp |
|---|---|
|
En vanlig fil. |
|
En katalog. |
|
En symbolisk länk. |
|
En teckenspecialfil (character special file). |
|
En blockspecialfil (block special file). |
De återstående nio tecknen av filattributen, kallade filläget, representerar läs-, skriv- och körbehörigheterna för filens ägare, filens gruppägare och alla andra.
User Group Other
rwx rwx rwx
Tabell 9-2 beskriver effekten som attributen r, w och x har på filer och kataloger.
| Attribut | Filer | Kataloger |
|---|---|---|
|
Tillåter att filen öppnas och läses. |
Tillåter att katalogens innehåll listas. |
|
Tillåter att filen skrivs till eller trunkeras. |
Tillåter att filer i katalogen skapas, tas bort och byter namn, förutsatt att exekveringsbehörighet också är satt. |
|
Tillåter att filen behandlas som ett program och körs. |
Tillåter att man går in i katalogen och att katalogens metadata kan läsas. |
Tabell 9-3 ger några exempel på inställningar av filattribut.
| Filattribut | Betydelse |
|---|---|
|
En vanlig fil som är läsbar, skrivbar och körbar enbart av ägaren. |
|
En vanlig fil som är läsbar och skrivbar enbart av ägaren. |
|
En vanlig fil som är läsbar och skrivbar av ägaren; läsbar av gruppen och övriga. |
|
En vanlig fil som är läsbar, skrivbar och körbar av ägaren; läsbar och körbar av alla andra. |
|
En vanlig fil som är läsbar och skrivbar enbart av ägaren och gruppägaren. |
|
En symbolisk länk med skenbehörigheter. |
|
En katalog där ägaren och gruppen kan gå in samt skapa, byta namn på och ta bort filer. |
|
En katalog där ägaren kan gå in, skapa, byta namn på och ta bort filer; gruppen kan bara gå in. |
21.2.1. chmod - ändra filläge
För att ändra läge (behörigheter) för en fil eller katalog används kommandot chmod. Observera att bara filens ägare eller superanvändaren kan ändra en fils eller katalogs läge. chmod stödjer två olika sätt att ange lägessändringar: oktalt tal och symbolisk notation.
21.2.2. Vad i hela friden är oktal?
Oktal (bas 8) och dess kusin hexadecimal (bas 16) är talsystem som ofta används för att uttrycka tal i datorer. Vi människor, tack vare att vi (eller åtminstone de flesta av oss) föds med tio fingrar, räknar i bas 10. Datorer föddes däremot med bara ett finger och räknar därför i binärt (bas 2). Deras talsystem har bara två siffror: 0 och 1. Binär räkning ser ut så här:
| Oktalt | Binärt | Filläge |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Med tre oktala siffror kan vi ställa in filläget för ägaren, gruppägaren och övriga.
[me@linuxbox ~]$ > foo.txt
[me@linuxbox ~]$ ls -l foo.txt
-rw-rw-r-- 1 me me 0 2016-03-06 14:52 foo.txt
[me@linuxbox ~]$ chmod 600 foo.txt
[me@linuxbox ~]$ ls -l foo.txt
-rw------- 1 me me 0 2016-03-06 14:52 foo.txt
Genom att ange argumentet 600 kunde vi ställa in ägarens behörigheter till läs- och skrivbehörighet samtidigt som vi tog bort alla behörigheter från gruppägaren och övriga.
chmod stödjer också symbolisk notation för att ange fillägen. Symbolisk notation delas in i tre delar:
-
vem som påverkas av ändringen
-
vilken operation som utförs
-
vilken behörighet som ställs in
För att ange vem som påverkas används en kombination av tecknen u, g, o och a.
| Symbol | Betydelse |
|---|---|
|
Användare (user), filens eller katalogens ägare. |
|
Gruppägare (group owner). |
|
Övriga (others). |
|
Alla (all). |
Behörigheter anges med tecknen r, w och x. Tabell 9-6 ger några exempel.
| Notation | Betydelse |
|---|---|
|
Lägg till exekveringsbehörighet för ägaren. |
|
Ta bort exekveringsbehörighet för ägaren. |
|
Lägg till exekveringsbehörighet för användare, grupp och övriga. |
|
Ta bort läs- och skrivbehörighet för alla utom ägaren och gruppägaren. |
|
Sätt gruppägare och övriga till enbart läs- och skrivbehörighet. |
|
Lägg till exekveringsbehörighet för ägaren och sätt grupp och övriga till läs- och exekveringsbehörighet. |
21.2.3. Ställa in filläge med det grafiska gränssnittet
Nu när vi har sett hur behörigheter för filer och kataloger ställs in kan vi bättre förstå behörighetsdialogerna i det grafiska gränssnittet. I både Filer (GNOME) och Dolphin (KDE) kan man högerklicka på en fil- eller katalogikon för att visa en egenskapsdialog. Här är ett exempel från GNOME:
Här kan vi se inställningarna för ägaren, gruppen och övriga.
21.3. umask - sätt standardbehörigheter
Kommandot umask styr vilka standardbehörigheter en fil får när den skapas. Det använder oktal notation för att uttrycka en mask av bitar som ska tas bort från fillägets attribut. Vi tittar på ett exempel:
[me@linuxbox ~]$ rm -f foo.txt
[me@linuxbox ~]$ umask
0002
[me@linuxbox ~]$ > foo.txt
[me@linuxbox ~]$ ls -l foo.txt
-rw-rw-r-- 1 me me 0 2025-03-06 14:53 foo.txt
Vi tog först bort eventuell gammal kopia av foo.txt för att starta rent. Sedan körde vi umask utan argument för att se aktuellt värde. Svaret blev 0002 (0022 är ett annat vanligt standardvärde), vilket är den oktala representationen av masken. Därefter skapade vi en ny foo.txt och tittade på behörigheterna.
[me@linuxbox ~]$ rm foo.txt
[me@linuxbox ~]$ umask 0000
[me@linuxbox ~]$ > foo.txt
[me@linuxbox ~]$ ls -l foo.txt
-rw-rw-rw- 1 me me 0 2025-03-06 14:58 foo.txt
Om vi sätter masken till 0000 (i praktiken avstängd) ser vi att filen nu är skrivbar för alla.
För det mesta behöver vi inte ändra masken; standardvärdet som distributionen ger fungerar bra.
21.3.1. Några speciella behörigheter
Även om vi vanligtvis ser en oktal behörighetsmask uttryckt som ett tresiffrigt tal är det tekniskt korrektare att uttrycka den med fyra siffror. Förutom läs-, skriv- och körbehörighet finns det några andra, mindre använda, behörighetsinställningar: setuid (4000), setgid (2000) och sticky (1000).
chmod u+s program
chmod g+s dir
chmod +t dir
Exempel i ls-utdata:
-rwsr-xr-x
drwxrwsr-x
drwxrwxrwt
21.4. Byta identitet
Ibland kan vi behöva anta en annan användares identitet. Ofta vill vi få superanvändarprivilegier för en administrativ uppgift, men det går också att "bli" en annan vanlig användare för till exempel kontotestning. Det finns tre sätt att anta alternativ identitet:
-
Logga ut och logga in igen som den andra användaren.
-
Använd kommandot su.
-
Använd kommandot sudo.
21.4.1. su - kör ett skal med ersatt användar- och grupp-ID
Kommandot su används för att starta ett skal som en annan användare. Syntaxen ser ut så här:
su [-[l]] [user]
Om flaggan -l inkluderas blir den resulterande skalsessionen ett inloggningsskal för den angivna användaren. Det betyder att användarens miljö laddas och att arbetskatalogen byts till användarens hemkatalog. Det är oftast det vi vill ha.
[me@linuxbox ~]$ su
Password:
[root@linuxbox ~]#
[root@linuxbox ~]# exit
[me@linuxbox ~]$
Om ingen användare anges antas superanvändaren. Notera att (lite märkligt) -l kan förkortas till -, vilket är den vanligaste formen.
su -c 'command'
[me@linuxbox ~]$ su -c 'ls -l /root/*'
Password:
21.4.2. sudo - kör ett kommando som en annan användare
Kommandot sudo liknar su på många sätt men har viktiga extra möjligheter. Administratören kan konfigurera sudo så att en vanlig användare får köra kommandon som en annan användare (oftast superanvändaren) på ett kontrollerat sätt. I synnerhet kan användaren begränsas till ett eller flera specifika kommandon och inga andra.
[me@linuxbox ~]$ sudo backup_script
Password:
System Backup Starting...
En annan viktig skillnad är att sudo inte kräver tillgång till superanvändarens lösenord. Vid autentisering med sudo används användarens eget lösenord.
[me@linuxbox ~]$ sudo -l
User me may run the following commands on this host:
(ALL) ALL
För att se vilka privilegier som beviljas av sudo kan vi använda flaggan -l.
21.4.3. Moderna Linux-distributioner och sudo
Ett återkommande problem för vanliga användare är att utföra uppgifter som kräver superanvändarprivilegier. Det handlar om saker som att installera och uppdatera programvara, redigera systemkonfigurationsfiler och komma åt enheter. I Windows-världen löser man ofta detta genom att ge användarna administratörsrättigheter. Det gör att de kan utföra dessa uppgifter, men det innebär också att program som körs av användaren har samma behörigheter. I de flesta fall är det önskvärt, men det tillåter även skadlig programvara (som virus) att härja fritt på datorn.
I Unix-världen har det alltid funnits en tydligare uppdelning mellan vanliga användare och administratörer, tack vare Unix fleranvändararv. Metoden i Unix är att ge superanvändarprivilegier bara vid behov. För detta används vanligen kommandona su och sudo.
Förr i tiden förlitade sig de flesta Linux-distributioner på su för detta ändamål. su krävde inte den konfiguration som sudo krävde, och att ha ett root-konto är tradition i Unix. Det skapade dock ett problem. Användarna frestades att arbeta som root i onödan. Faktum är att vissa användare körde sina system enbart som root-användaren, eftersom det gör sig av med alla de irriterande "permission denied"-meddelandena. Det är så man sänker säkerheten i ett Linux-system till nivån hos ett Windows-system. Ingen bra idé.
När Ubuntu introducerades valde skaparna en annan väg. Som standard inaktiverar Ubuntu inloggning till root-kontot (genom att inte sätta något lösenord för kontot) och använder istället sudo för att bevilja superanvändarprivilegier. Det initiala användarkontot ges full tillgång till superanvändarprivilegier via sudo, och kan ge liknande befogenheter till efterföljande användarkonton. Denna metod att bevilja privilegier är numera den accepterade standarden i de flesta moderna distributioner.
21.5. chown - ändra filägare och grupp
Kommandot chown används för att ändra ägare och gruppägare för en fil eller katalog. Superanvändarprivilegier krävs för att använda kommandot. Syntaxen ser ut så här:
chown [owner][:[group]] file...
| Argument | Resultat |
|---|---|
|
Ändra filens ägare till användaren |
|
Ändra ägaren till |
|
Ändra gruppägaren till |
|
Ändra ägaren till |
[janet@linuxbox ~]$ sudo cp myfile.txt ~tony
[janet@linuxbox ~]$ sudo chown tony: ~tony/myfile.txt
21.5.1. chgrp - ändra gruppägarskap
I äldre Unixversioner ändrade kommandot chown bara filägare, inte gruppägare. För det ändamålet användes ett separat kommando, chgrp. Det fungerar i stort sett som chown, men är mer begränsat.
21.6. Vi använder våra privilegier
Nu när vi har lärt oss hur hela behörighetssystemet fungerar är det dags att använda det i praktiken. Vi ska visa lösningen på ett vanligt problem - att sätta upp en delad katalog. Vi återvänder till våra vänner janet och tony. De har båda musiksamlingar och vill skapa en gemensam katalog där de kan lagra sina musikfiler som Ogg Vorbis eller MP3. Som tidigare har användaren janet tillgång till superanvändarprivilegier via sudo.
[janet@linuxbox ~]$ sudo groupadd music
[janet@linuxbox ~]$ sudo usermod -a -G music janet
[janet@linuxbox ~]$ sudo usermod -a -G music tony
[janet@linuxbox ~]$ sudo mkdir /usr/local/share/Music
[janet@linuxbox ~]$ sudo chown :music /usr/local/share/Music
[janet@linuxbox ~]$ sudo chmod 2775 /usr/local/share/Music
Detta sätter setgid-biten så att filer som skapas i katalogen ärver katalogens gruppägare. Om systemets umask är 0022 kan gruppmedlemmarna fortfarande inte skriva i varandras filer, så användarna behöver umask 0002 i stället.
[janet@linuxbox ~]$ umask 0002
[janet@linuxbox ~]$ > /usr/local/share/Music/test_file
[janet@linuxbox ~]$ mkdir /usr/local/share/Music/test_dir
21.7. Byta lösenord
Det sista ämnet vi tar upp i det här kapitlet är hur man sätter lösenord för sig själv (och för andra användare om man har tillgång till superanvändarprivilegier). För att sätta eller ändra ett lösenord används kommandot passwd. Syntaxen ser ut så här:
passwd [user]
[me@linuxbox ~]$ passwd
Changing password for me.
Current password:
New password:
Retype new password:
passwd: password updated successfully
Kommandot passwd försöker framtvinga starka lösenord och varnar om lösenord som är för korta, för lika tidigare lösenord, ordlistord eller för lätta att gissa.
| Kommando | Beskrivning |
|---|---|
|
Visar senaste inloggning för alla användare eller en angiven användare. |
|
Skapa en ny användare eller uppdatera standardinformation för nya användare. |
|
Ta bort ett användarkonto och relaterade filer. |
|
Ändra ett användarkonto. |
|
Skapa en ny grupp. |
|
Ta bort en grupp. |
|
Ändra en gruppdefinition i systemet. |
21.8. Sammanfattning
I det här kapitlet såg vi hur Unix-liknande system som Linux hanterar användarbehörigheter för att styra läs-, skriv- och köråtkomst till filer och kataloger. Grundtankarna bakom detta behörighetssystem går tillbaka till Unix tidiga dagar och har stått sig förvånansvärt väl över tid. Men den inbyggda behörighetsmekanismen i Unix-liknande system saknar den finmaskighet som mer moderna system kan erbjuda.
21.9. Vidare läsning
-
Wikipedia har en bra artikel om skadeprogram:
22. 10 - Processer
Moderna operativsystem kan vanligen köra flera uppgifter samtidigt, vilket betyder att de skapar en illusion av att göra mer än en sak åt gången genom att snabbt växla mellan program som körs. Linuxkärnan hanterar detta med hjälp av processer. Processer är det sätt Linux organiserar de olika program som väntar på sin tur vid processorn.
Ibland blir en dator seg eller så slutar ett program att svara. I det här kapitlet ska vi titta på några av de verktyg som finns på kommandoraden och som låter oss undersöka vad program gör och hur man avslutar processer som inte beter sig som de ska.
Det här kapitlet introducerar följande kommandon:
-
ps- visa en ögonblicksbild av aktuella processer. -
top- visa uppgifter. -
jobs- lista aktiva jobb. -
bg- lägg ett jobb i bakgrunden. -
fg- lägg ett jobb i förgrunden. -
kill- skicka en signal till en process. -
killall- avsluta processer efter namn. -
nice- kör ett program med ändrad schemaläggningsprioritet. -
renice- ändra prioritet för körande processer. -
nohup- kör ett kommando som inte påverkas av hangup-signaler. -
halt/poweroff/reboot- stoppa, stäng av eller starta om systemet. -
shutdown- stäng av eller starta om systemet.
22.1. Hur en process fungerar
När ett system startar sätter kärnan i gång några av sina egna aktiviteter som processer och startar ett program som heter init. init startar i sin tur systemd, som startar alla systemtjänster. I äldre Linuxdistributioner kör init en serie skalskript (placerade i /etc) som kallas init-skript för att utföra en liknande funktion. Många systemtjänster är implementerade som demonprogram - program som bara ligger i bakgrunden och gör sitt jobb utan något användargränssnitt. Så även om vi inte är inloggade är systemet åtminstone lite sysselsatt med rutinarbete.
Att ett program kan starta andra program uttrycks i processmodellen som att en föräldraprocess skapar en barnprocess. Kärnan håller ordning på information om varje process för att allt ska vara organiserat. Varje process får till exempel ett nummer som kallas process-ID (PID). PID tilldelas i stigande ordning, där init alltid får PID 1. Kärnan håller också reda på hur mycket minne som tilldelats varje process och om processerna är redo att återuppta körningen. Precis som filer har processer också ägare, användar-ID, effektiva användar-ID:n och så vidare.
Det vanligaste verktyget för att visa processer (det finns flera) är kommandot ps. Programmet ps har många flaggor, men i sin enklaste form används det så här:
22.2. Visa processer
Resultatet i det här exemplet listar två processer, process 5198 och process 10129, alltså bash respektive ps. Som vi ser visar ps inte särskilt mycket som standard, bara de processer som hör till den aktuella terminalsessionen. För att se mer behöver vi lägga till några flaggor, men innan vi gör det tittar vi på de andra fälten som ps visar. TTY är en förkortning för "teletype" och syftar på den styrande terminalen för processen. Här märks Unix höga ålder. Fältet TIME är den mängd processortid som processen har förbrukat. Som vi ser får ingen av processerna datorn att arbeta särskilt hårt.
[me@linuxbox ~]$ ps
PID TTY TIME CMD
5198 pts/1 00:00:00 bash
10129 pts/1 00:00:00 ps
Om vi lägger till en flagga kan vi få en större bild av vad systemet gör.
[me@linuxbox ~]$ ps x
PID TTY STAT TIME COMMAND
2799 ? Ssl 0:00 /usr/libexec/bonobo-activation-server -ac
2820 ? Sl 0:01 /usr/libexec/evolution-data-server-1.10
15794 ? Ss 0:00 kdeinit Running...
15797 ? S 0:00 dcopserver --nosid
| Tillstånd | Betydelse |
|---|---|
|
Körs eller är redo att köras. |
|
Sover, väntar på en händelse. |
|
Oavbrytbar sömn, väntar vanligtvis på I/O. |
|
Stoppad. |
|
Zombieprocess. |
|
Högt prioriterad process. |
|
Lågt prioriterad process. |
[me@linuxbox ~]$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 2136 644 ? Ss Mar05 0:31 init
root 2 0.0 0.0 0 0 ? S< Mar05 0:00 [kthreadd]
| Flagga | Funktion |
|---|---|
|
Lista våra körande processer. |
|
Lista alla körande processer. |
|
Inkludera fullständiga kommandonamn. |
|
Utförlig listning. |
| Rubrik | Betydelse |
|---|---|
|
Användar-ID, processens ägare. |
|
Processoranvändning i procent. |
|
Minnesanvändning i procent. |
|
Virtuell minnesstorlek. |
|
Resident set size, mängden fysiskt RAM som används. |
|
Tidpunkt då processen startade. |
|
Mängden processortid som processen har förbrukat. |
[me@linuxbox ~]$ ps uw 44719
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
me 44719 0.0 0.0 13480 6492 pts/1 S 15:57 0:00 bash
22.2.1. Visa processer dynamiskt med top
Även om kommandot ps kan avslöja mycket om vad maskinen gör ger det bara en ögonblicksbild av systemets tillstånd vid det ögonblick då ps körs. För att se en mer dynamisk bild av maskinens aktivitet använder vi kommandot top:
[me@linuxbox ~]$ top
Programmet top visar en fortlöpande uppdaterad vy (som standard var tredje sekund) av systemets processer, sorterade efter processaktivitet. Namnet top kommer av att programmet används för att se de "översta" processerna i systemet. top-vyn består av två delar: en systemsammanfattning längst upp och därefter en tabell över processer sorterade efter processoraktivitet:
| Rad | Fält | Betydelse |
|---|---|---|
1 |
|
Programmets namn. |
1 |
|
Aktuell tid på dygnet. |
1 |
|
Systemets drifttid (uptime). |
1 |
|
Två användare är inloggade. |
1 |
|
Antal körbara processer i genomsnitt över tid. |
2 |
|
Sammanfattning av antalet processer och deras tillstånd. |
3 |
|
Hur processorn används. |
4 |
|
Användning av fysiskt RAM. |
5 |
|
Användning av virtuellt minne. |
top tar emot ett antal tangentbordskommandon. De två mest intressanta är h, som visar programmets hjälpskärm, och q, som avslutar top.
22.3. Styra processer
Nu när vi kan se och övervaka processer ska vi också få lite kontroll över dem. I våra experiment använder vi ett litet program som heter xlogo som försökskanin. Programmet xlogo är ett exempelprogram som följer med X Window System (den underliggande motor som gör att grafiken på skärmen fungerar, även om den håller på att ersättas av Wayland) och som helt enkelt visar ett skalbart fönster med X-logotypen.
[me@linuxbox ~]$ xlogo
Först ska vi lära känna vårt försöksobjekt.
22.3.1. Avbryta en process
Låt oss se vad som händer när vi kör xlogo igen. Skriv först kommandot xlogo och kontrollera att programmet kör. Gå sedan tillbaka till terminalfönstret och tryck Ctrl-c.
[me@linuxbox ~]$ xlogo
[me@linuxbox ~]$
I en terminal avbryter Ctrl-c ett program. Det betyder att vi artigt ber programmet att avsluta sig. När vi tryckte Ctrl-c stängdes xlogo-fönstret och skalprompten kom tillbaka.
22.3.2. Lägga en process i bakgrunden
Anta att vi vill få tillbaka skalprompten utan att avsluta programmet xlogo. Det kan vi göra genom att lägga programmet i bakgrunden. Tänk på terminalen som att den har en förgrund (med sådant som syns på ytan, som skalprompten) och en bakgrund (med sådant som är dolt bakom ytan). För att starta ett program så att det omedelbart hamnar i bakgrunden lägger vi till ett et-tecken (&) efter kommandot.
[me@linuxbox ~]$ xlogo &
[1] 28236
[me@linuxbox ~]$
Efter att kommandot skrevs dök xlogo-fönstret upp och skalprompten kom tillbaka, men några märkliga siffror skrevs också ut. Meddelandet är en del av skalets funktion för jobbstyrning. Med det talar skalet om att vi har startat jobb nummer 1 ([1]) och att det har PID 28236. Om vi kör ps kan vi se processen.
[me@linuxbox ~]$ jobs
[1]+ Running xlogo &
22.3.3. Flytta tillbaka en process till förgrunden
En process i bakgrunden tar inte emot tangentbordsinmatning från terminalen, inklusive försök att avbryta den med Ctrl-c. För att flytta tillbaka en process till förgrunden använder vi kommandot fg så här:
[me@linuxbox ~]$ fg %1
xlogo
Kommandot fg, följt av ett procenttecken och jobbnumret (kallat en jobbspecifikation), gör jobbet. Om vi bara har ett bakgrundsjobb är jobbspecifikationen valfri. För att avsluta xlogo, tryck Ctrl-c.
22.3.4. Stoppa (pausa) en process
Ibland vill vi stoppa en process utan att avsluta den. Ofta gör man det för att kunna flytta en process i förgrunden till bakgrunden. För att stoppa en förgrundsprocess och placera den i bakgrunden trycker vi Ctrl-z. Prova. Skriv xlogo vid prompten, tryck Enter och därefter Ctrl-z:
[me@linuxbox ~]$ xlogo
[1]+ Stopped xlogo
[me@linuxbox ~]$ bg %1
[1]+ xlogo &
När xlogo har stoppats kan vi kontrollera att programmet verkligen har stannat genom att försöka ändra storlek på xlogo-fönstret. Vi kommer att se att det går att ändra storlek på fönstret, men logotypen ritas inte om, vilket visar att programmet är stoppat. Vi kan antingen fortsätta programmets körning i förgrunden med kommandot fg, eller återuppta körningen i bakgrunden med kommandot bg:
22.3.5. Ändra processprioritet
Som vi såg i utdata från kommandot ps (och även top) finns det ett processattribut som kallas niceness och som syftar på den schemaläggningsprioritet som ges till en process. I vissa situationer, till exempel vid videokodning eller processorbaserad ray tracing, kan vi vilja ge en process högre prioritet (mindre niceness). Och om vi i stället vill att en process ska använda mindre processortid kan vi ge den högre niceness. Niceness kan justeras med kommandona nice och renice. Det är viktigt att komma ihåg att bara superanvändaren får höja prioriteten för en process och att vanliga användare bara får sänka prioriteten för processer de själva äger.
[me@linuxbox ~]$ nice -n 10 cpu-hog
[me@linuxbox ~]$ sudo nice -n -10 must-run-fast
Kommandot nice startar en process med en angiven niceness. Justeringar av niceness uttrycks från -20 (mest gynnsamt) till 19 (minst gynnsamt), med standardvärdet 0 (ingen justering). Låt oss se hur det fungerar. Föreställ dig att vi har ett program som heter cpu-hog och att vi vill köra det med lägre prioritet än dess normala 20. Vi kan starta programmet med nice så här:
[me@linuxbox ~]$ renice -n 19 379215
22.4. Signaler
22.4.1. Skicka signaler till processer med kill
Kommandot kill används för att skicka signaler till processer. Den vanligaste syntaxen ser ut så här:
kill [-signal] PID...
Om ingen signal anges skickas signalen TERM som standard. Tabell 10-5 listar de vanligaste signalerna.
| Nummer | Namn | Betydelse |
|---|---|---|
|
|
Hangup. |
|
|
Avbrott. |
|
|
Tvinga omedelbar avslutning. |
|
|
Avsluta. |
|
|
Fortsätt en stoppad process. |
|
|
Pausa en process. |
|
|
Terminalstopp. |
[me@linuxbox ~]$ xlogo &
[1] 13546
[me@linuxbox ~]$ kill -1 13546
[1]+ Hangup xlogo
[me@linuxbox ~]$ kill -INT 13601
[me@linuxbox ~]$ kill -SIGINT 13608
Processer, precis som filer, har ägare, och du måste vara ägare till en process eller superanvändare för att skicka signaler till den med kill.
| Nummer | Namn | Betydelse |
|---|---|---|
|
|
Avsluta. |
|
|
Segmenteringsfel. |
|
|
Fönsteränding. |
För att visa en fullständig lista över signaler:
[me@linuxbox ~]$ kill -l
22.4.2. Göra en process tålig mot hangup
Som vi diskuterade ovan reagerar många kommandoradsprogram på signalen HUP genom att avslutas när deras styrande terminal "lägger på" (det vill säga stängs eller kopplas ned). För att förhindra detta kan vi starta programmet med kommandot nohup. Här är ett exempel.
[me@linuxbox ~]$ nohup xlogo
22.4.3. Skicka signaler till flera processer med killall
Det går också att skicka signaler till flera processer som matchar ett angivet programnamn eller användarnamn med kommandot killall. Syntaxen ser ut så här:
killall [-u user] [-signal] name...
[me@linuxbox ~]$ xlogo &
[1] 18801
[me@linuxbox ~]$ xlogo &
[2] 18802
[me@linuxbox ~]$ killall xlogo
[1]- Terminated xlogo
[2]+ Terminated xlogo
22.5. Stänga ned systemet
Att stänga ned systemet innebär att alla processer i systemet avslutas i ordnad form, samtidigt som en del viktiga städåtgärder utförs (som att synkronisera alla monterade filsystem) innan systemet stängs av. Det finns fyra kommandon som kan göra detta: halt, poweroff, reboot och shutdown. De tre första är ganska självförklarande och används i regel utan några kommandoradsflaggor. Här är ett exempel:
[me@linuxbox ~]$ sudo reboot
[me@linuxbox ~]$ sudo shutdown -h now
[me@linuxbox ~]$ sudo shutdown -r now
Kommandot shutdown är lite mer intressant. Med det kan vi ange vilken åtgärd som ska utföras (stoppa, stänga av eller starta om) och även lägga in en fördröjning före nedstängningen. Oftast används det så här för att stoppa systemet:
22.6. Fler processrelaterade kommandon
| Kommando | Beskrivning |
|---|---|
|
Visa en processlista ordnad som ett träd som visar förälder-barn-relationer. |
|
Visa en ögonblicksbild av systemresursanvändningen. |
|
Ett grafiskt program som ritar en graf över systembelastningen över tid. |
|
Som |
22.7. Sammanfattning
De flesta moderna system har en mekanism för att hantera flera processer. Linux tillhandahåller en rik uppsättning verktyg för detta ändamål. Med tanke på att Linux är världens mest använda serveroperativsystem är det fullt rimligt. Till skillnad från vissa andra system förlitar sig Linux dock främst på kommandoradsverktyg för processhantering. Det finns visserligen grafiska processverktyg för Linux, men kommandoradsverktygen föredras i hög grad eftersom de är snabba och lätta. De grafiska verktygen kan se trevliga ut, men de skapar ofta själva en hel del systembelastning, vilket delvis motverkar syftet.
22.8. Vidare läsning
-
Wikipedia-artikel om processer: https://sv.wikipedia.org/wiki/Process_(datorvetenskap)
-
En introduktion till Linux-processer: https://www.tldp.org/LDP/tlk/kernel/processes.html
-
Förstå processhantering i Linux: https://www.linux.com/training-tutorials/understanding-process-management-linux/
Del 2 - Konfiguration och miljö
23. 11 - Miljön
Som vi nämnde tidigare håller skalet under vår skalsession reda på en mängd information som kallas miljön. Program använder data som lagras i miljön för att avgöra sådant som rör systemets konfiguration. De flesta program använder konfigurationsfiler för att lagra sina inställningar, men en del program tittar också efter värden i miljön för att anpassa sitt beteende. När vi vet detta kan vi använda miljön för att skräddarsy hur vi arbetar i skalet.
I det här kapitlet arbetar vi med följande kommandon:
-
printenv- skriv ut delar av eller hela miljön. -
set- sätt skalflaggor. -
export- exportera miljön till program som körs senare. -
alias- skapa ett alias för ett kommando. -
source- kör kommandon från en fil i det aktuella skalet.
23.1. Vad lagras i miljön?
Skalet lagrar två grundtyper av data i miljön, även om de i bash vid första anblicken knappt går att skilja åt. Det handlar om miljövariabler och skalvariabler. Skalvariabler är data som den aktuella instansen av bash har placerat där, medan miljövariabler är allt annat. Utöver variabler lagrar skalet också viss programlogik, nämligen alias och skalfunktioner. Vi tog upp alias i kapitel 5, och skalfunktioner (som hänger ihop med skalskript) tar vi upp i del 4.
23.1.1. Undersöka miljön
För att se vad som finns lagrat i miljön kan vi använda antingen det inbyggda kommandot set i bash eller programmet printenv. Kommandot set visar både skalvariabler och miljövariabler, medan printenv bara visar de senare. Eftersom listan över miljöns innehåll blir ganska lång är det bäst att skicka utdata från något av kommandona till less.
[me@linuxbox ~]$ printenv | less
[me@linuxbox ~]$ printenv USER
me
[me@linuxbox ~]$ set | less
[me@linuxbox ~]$ echo $HOME
/home/me
[me@linuxbox ~]$ alias
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls --color=auto'
23.1.2. Några intressanta variabler
Miljön innehåller ganska många variabler, och även om din miljö kan skilja sig från den som visas här kommer du sannolikt att hitta de variabler som listas i tabell 11-1.
| Variabel | Innehåll |
|---|---|
|
Namnet på displayen om vi kör en grafisk miljö. |
|
Namnet på programmet som ska användas för textredigering. |
|
Namnet på användarens standardskal. |
|
Sökvägen till vår hemkatalog. |
|
Anger teckenuppsättning och sorteringsordning för vårt språk. |
|
Den föregående arbetskatalogen. |
|
Namnet på programmet som ska användas för sidvis utmatning. |
|
En kolonseparerad lista över kataloger som genomsöks när vi anger namnet på ett körbart program. |
|
Promptsträng 1, definierar innehållet i skalprompten. |
|
Den aktuella arbetskatalogen. |
|
Namnet på din terminaltyp. |
|
Anger vår tidszon. |
|
Vårt användarnamn. |
23.2. Hur upprättas miljön?
När vi loggar in i systemet startar programmet bash och läser en serie konfigurationsskript som kallas startfiler, vilka definierar standardmiljön som delas av alla användare. Därefter läses fler startfiler i vår hemkatalog som definierar vår personliga miljö. Den exakta ordningen beror på vilken typ av skalsession som startas. Det finns två typer.
-
En inloggningsskalsession. En inloggningsskalsession är en där vi ombeds ange användarnamn och lösenord. Det sker till exempel när vi loggar in i en grafisk miljö. Det sker också när vi startar en session på en virtuell konsol.
-
En icke-inloggningsskalsession. En sådan uppstår vanligtvis när vi startar en terminalsession i det grafiska skrivbordet med vår terminalemulator.
Inloggningsskal läser en eller flera startfiler enligt tabell 11-2.
| Fil | Innehåll |
|---|---|
|
Ett globalt konfigurationsskript som gäller alla användare. |
|
En användares personliga startfil. |
|
Läses om |
|
Läses om varken |
| Fil | Innehåll |
|---|---|
|
Ett globalt konfigurationsskript som gäller alla användare. |
|
En användares personliga startfil. |
Utöver att läsa dessa startfiler ärver icke-inloggningsskal också miljövariabler från sin överordnade process, vanligtvis ett inloggningsskal.
23.2.1. Vad finns i en startfil?
Om vi tittar in i en typisk .bash_profile kan den se ut ungefär så här:
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# User specific environment and startup programs
PATH=$PATH:$HOME/bin
export PATH
Rader som börjar med # är kommentarer och körs inte av skalet. De finns där för människors skull. Det första intressanta sker på fjärde raden, i följande kod:
[me@linuxbox ~]$ foo="This is some "
[me@linuxbox ~]$ echo $foo
This is some
[me@linuxbox ~]$ foo=$foo"text."
[me@linuxbox ~]$ echo $foo
This is some text.
23.2.2. Utforska hur barnprocesser ärver sin miljö
Den sista punkten förtjänar lite mer utveckling. Skalvariabler är lokala för den aktuella instansen av skalet och kopieras inte till de barn som skalet startar. Låt oss visa det.
[me@linuxbox ~]$ foo="bar"
[me@linuxbox ~]$ bash
[me@linuxbox ~]$ ps
PID TTY TIME CMD
1011638 pts/9 00:00:00 bash
1011650 pts/9 00:00:00 bash
1011662 pts/9 00:00:00 ps
[me@linuxbox ~]$ echo $foo
[me@linuxbox ~]$ foo="barbar"
[me@linuxbox ~]$ exit
[me@linuxbox ~]$ echo $foo
bar
Först sätter vi en skalvariabel i vårt aktuella skal.
23.2.3. Starta ett program med en tillfällig miljö
Ett annat praktiskt knep som skalet erbjuder är möjligheten att köra ett kommando och samtidigt ge det en tillfällig miljövariabel. Ibland vill vi köra ett program och ge det ett särskilt miljövärde. Ett bra exempel är kommandot man, som tittar efter en miljövariabel med namnet MANWIDTH som talar om för man hur brett dess utdata ska formateras.
[me@linuxbox ~]$ MANWIDTH=75 man ls
[me@linuxbox ~]$ alias man='MANWIDTH=75 man'
23.3. Ändra miljön
Nu när vi vet var startfilerna finns och vad de innehåller kan vi ändra dem för att anpassa vår miljö.
23.3.1. Vilka filer bör vi ändra?
Som allmän regel gäller att om vi vill lägga till kataloger i PATH eller definiera ytterligare miljövariabler, ska vi lägga dessa ändringar i .bash_profile (eller motsvarande fil enligt distributionen; Ubuntu använder till exempel .profile). För allt annat lägger vi ändringarna i .bashrc.
23.3.2. Textredigerare
För att redigera (alltså ändra) skalets startfiler, liksom de flesta andra konfigurationsfiler i systemet, använder vi ett program som kallas textredigerare. En textredigerare är ett program som på vissa sätt liknar en ordbehandlare, eftersom den låter oss redigera ord på skärmen med en flyttbar markör. Den skiljer sig från en ordbehandlare genom att bara hantera ren text och ofta innehåller funktioner som är avsedda för att skriva program. Textredigerare är utvecklares centrala verktyg för att skriva kod och systemadministratörers centrala verktyg för att hantera de konfigurationsfiler som styr systemet.
23.3.3. Använda en textredigerare
Textredigerare startas från kommandoraden genom att vi skriver namnet på redigeraren följt av namnet på filen vi vill redigera. Om filen inte redan finns antar redigeraren att vi vill skapa en ny fil. Här är ett exempel med gedit:
[me@linuxbox ~]$ gedit some_file
[me@linuxbox ~]$ cp .bashrc .bashrc.bak
[me@linuxbox ~]$ nano .bashrc
Detta startar textredigeraren gedit och läser in filen some_file, om den finns.
GNU nano 8.6
# .bashrc
File: .bashrc
Grafiska textredigerare är ganska självförklarande, så vi går inte igenom dem här. I stället koncentrerar vi oss på vår första textbaserade redigerare, nano. Låt oss starta nano och redigera filen .bashrc. Men innan vi gör det ska vi först öva lite "säker datoranvändning". Varje gång vi redigerar en viktig konfigurationsfil är det klokt att skapa en säkerhetskopia av filen först. Det skyddar oss om vi råkar förstöra filen under redigeringen. För att skapa en säkerhetskopia av filen .bashrc gör vi så här:
Det spelar ingen roll vad vi kallar säkerhetskopian, bara namnet är begripligt. Filändelser som .bak, .sav, .old och .orig är alla vanliga sätt att markera att det är en säkerhetskopia. Och kom ihåg att cp skriver över befintliga filer utan att säga till.
umask 0002
export HISTCONTROL=ignoredups
export HISTSIZE=1000
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
Nu när vi har en säkerhetskopia startar vi redigeraren.
| Rad | Betydelse |
|---|---|
|
Ställer in |
|
Gör att historikfunktionen ignorerar dubbletter av på varandra följande kommandon. |
|
Ökar historikens storlek från 500 till 1000 rader. |
|
Skapar ett kommando som heter |
|
Skapar ett kommando som heter |
När nano startar får vi en skärm som denna:
# Change umask to make directory sharing easier
umask 0002
# Ignore duplicates in command history and increase
# history size to 1000 lines
export HISTCONTROL=ignoredups
export HISTSIZE=1000
# Add some helpful aliases
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
Om ditt system inte har nano installerat kan du använda en grafisk redigerare i stället.
23.3.4. Varför kommentarer är viktiga
Kommentarer i konfigurationsfiler är viktiga för att dokumentera ändringar och göra filerna lättare att förstå.
23.4. Aktivera våra ändringar
De ändringar vi har gjort i .bashrc börjar inte gälla förrän vi stänger terminalsessionen och startar en ny, eftersom filen .bashrc bara läses i början av en session. Vi kan dock tvinga bash att läsa in den ändrade filen .bashrc på nytt med följande kommando:
[me@linuxbox ~]$ source ~/.bashrc
[me@linuxbox ~]$ ll
23.4.1. Lite mer om source
Kommandot source (som kan förkortas .) är ett inbyggt skal-kommando som läser in en fil direkt i det aktuella skalet, som om dess innehåll hade skrivits in på tangentbordet. Ja, allt det där märkliga vi har sett i skalets startfiler är helt enkelt sådant som skalet förstår och kan agera på. Många äldre textbaserade operativsystem (DOS, CP/M och så vidare) fungerade mest som enkla programstartare. Unixskal kan förstås göra det också, som vi redan har sett, men de kan också göra mycket mer.
23.5. Sammanfattning
I det här kapitlet lärde vi oss en grundläggande färdighet - att redigera konfigurationsfiler med en textredigerare. När vi framöver läser manualsidor för kommandon bör vi lägga märke till vilka miljövariabler de stöder. Där kan det finnas en och annan pärla. I senare kapitel ska vi lära oss om skalfunktioner, en kraftfull funktion som du också kan lägga in i bash-startfilerna för att utöka din arsenal av egna kommandon.
23.6. Vidare läsning
-
Avsnittet
INVOCATIONi manualsidan förbashgår igenombash-startfilerna i närmast plågsam detalj.
24. 12 - En varsam introduktion till vi(m)
Det finns ett gammalt skämt om en besökare i New York som frågar en förbipasserande efter vägen till stadens berömda konserthall för klassisk musik:
Besökare: Ursäkta, hur kommer jag till Carnegie Hall?
Förbipasserande: Öva, öva, öva!
Att lära sig Linux kommandorad är, liksom att bli en skicklig pianist, inget man snappar upp på en eftermiddag. Det kräver många års övning. I det här kapitlet introducerar vi textredigeraren vi (uttalas "vi" på engelskt vis, ungefär "vee eye"), ett av kärnprogrammen i Unixtraditionen. vi är något ökänt för sitt speciella användargränssnitt, men när vi ser en mästare sätta sig vid tangentbordet och börja "spela" bevittnar vi verkligen stor konst. Vi blir inte mästare i det här kapitlet, men när vi är klara kommer vi åtminstone att kunna spela "Blinka lilla stjärna" i vi.
24.1. Varför vi bör lära oss vi
I vår tid med grafiska redigerare och lättanvända textbaserade redigerare som nano, varför ska vi då lära oss vi? Det finns tre goda skäl.
-
vifinns nästan alltid tillgängligt. Det kan vara en räddning om vi sitter på ett system utan grafiskt gränssnitt, till exempel en fjärrserver eller ett lokalt system med trasig GUI-konfiguration.nanoblir visserligen allt vanligare men finns ännu inte överallt. POSIX, en standard för programkompatibilitet på Unixsystem, kräver attvifinns. -
viär lättviktigt och snabbt. För många uppgifter är det enklare att startaviän att leta reda på den grafiska textredigeraren i menyerna och vänta på att dess många megabyte ska laddas. Dessutom ärvibyggt för snabb skrivning. Som vi snart ska se behöver en vanvi-användare aldrig lyfta fingrarna från tangentbordet under redigering. -
Vi vill inte att andra Linux- och Unixanvändare ska tro att vi är fegisar.
Okej, kanske två goda skäl.
24.2. Lite bakgrund
Den första versionen av vi skrevs 1976 av Bill Joy, då student vid University of California i Berkeley och senare medgrundare till Sun Microsystems. vi har fått sitt namn från ordet "visual", eftersom den var avsedd att möjliggöra redigering på en bildskärm med en rörlig markör. Före visuella redigerare fanns radredigerare som arbetade med en rad text i taget. För att ange en ändring bad man en radredigerare att gå till en viss rad och beskrev sedan vilken ändring som skulle göras, till exempel att lägga till eller ta bort text. När bildskärmsterminaler slog igenom (i stället för skrivarterminaler som teletyper) blev visuell redigering möjlig. vi innehåller faktiskt en kraftfull radredigerare som heter ex, och vi kan använda radredigeringskommandon även när vi arbetar i vi.
24.3. Starta och avsluta vi
För att starta vi skriver vi helt enkelt följande:
[me@linuxbox ~]$ vi
En skärm ungefär som denna bör visas:
VIM - Vi Improved
version 8.0.707
by Bram Moolenaar et al.
type :q<Enter> to exit
type :help<Enter> or <F1> for on-line help
Precis som när vi tidigare använde nano är det första vi behöver lära oss hur man avslutar programmet. För att avsluta skriver vi följande kommando (lägg märke till att kolon är en del av kommandot):
:q
Skalprompten bör då komma tillbaka. Om vi av någon anledning inte vill avsluta (vanligtvis för att vi har gjort en ändring i en fil som ännu inte har sparats), kan vi tala om för vi att vi verkligen menar allvar genom att lägga till ett utropstecken:
:q!
Om du "går vilse" i vi, prova att trycka Esc två gånger för att hitta tillbaka.
24.3.1. Kompatibilitetsläge
I startskärmen ovan ser vi texten "Running in Vi compatible mode." Det betyder att vim kör i ett läge som ligger närmare det vanliga beteendet hos vi än de utökade funktionerna i vim. I det här kapitlet vill vi använda vim med dess utökade beteende. Det går att lösa på flera sätt. Prova att köra vim i stället för vi. Om det fungerar kan du överväga att lägga till alias vi='vim' i filen .bashrc. Ett annat alternativ är att använda detta kommando för att lägga till en rad i din vim-konfigurationsfil:
echo "set nocp" >> ~/.vimrc
24.4. Redigeringslägen
Låt oss starta vi igen, den här gången med namnet på en fil som inte finns. Så här kan vi skapa en ny fil med vi:
[me@linuxbox ~]$ rm -f foo.txt
[me@linuxbox ~]$ vi foo.txt
Om allt går som det ska får vi en skärm som liknar denna:
24.4.1. Gå in i infogningsläge
För att lägga till text i filen måste vi först gå in i infogningsläge. Det gör vi genom att trycka i. Därefter bör vi längst ned på skärmen se följande om vim kör i sitt vanliga utökade läge (detta visas inte i kompatibilitetsläge för vi):
-- INSERT --
Nu kan vi skriva in lite text. Prova detta:
The quick brown fox jumps over the lazy dog.
För att lämna infogningsläge och återgå till normalläge trycker vi Esc.
24.4.2. Spara vårt arbete
För att spara ändringen vi just gjorde i filen måste vi gå in i kommandoläge. Det gör vi genom att trycka : medan vi är i normalläge. Då bör ett kolon visas längst ned på skärmen.
:w
"foo.txt" [New] 1L, 45C written
24.5. Flytta runt markören
I normalläge erbjuder vi ett stort antal kommandon för markörförflyttning, varav vissa delas med less. Tabell 12-1 visar ett urval.
| Tangent | Flyttar markören |
|---|---|
|
Ett tecken åt höger. |
|
Ett tecken åt vänster. |
|
En rad nedåt. |
|
En rad uppåt. |
|
Till början av aktuell rad. |
|
Till det första icke-blankstegstecknet på aktuell rad. |
|
Till slutet av aktuell rad. |
|
Till början av nästa ord eller skiljetecken. |
|
Till början av nästa ord, utan hänsyn till skiljetecken. |
|
Till början av föregående ord eller skiljetecken. |
|
Till början av föregående ord, utan hänsyn till skiljetecken. |
|
En sida nedåt. |
|
En sida uppåt. |
|
Till en angiven radnummer. |
|
Till filens sista rad. |
24.6. Grundläggande redigering
Det mesta redigeringsarbete består av några få grundläggande åtgärder, som att lägga in text, ta bort text och flytta runt text genom att klippa ut och klistra in. vi stöder förstås allt detta, men på sitt eget speciella sätt. vi har också en begränsad form av ångra-funktion. Om vi skriver u i normalläge kommer vi att ångra den senaste ändringen vi gjorde. Det kommer väl till pass när vi provar några av de grundläggande redigeringskommandona.
24.6.1. Lägga till text
vi har flera olika sätt att gå in i infogningsläge. Vi har redan använt kommandot i för att lägga in text.
The quick brown fox jumps over the lazy dog. It was cool.
Line 2
Line 3
Line 4
Line 5
24.6.2. Öppna en rad
Ett annat sätt att lägga in text är att "öppna" en rad. Det innebär att en tom rad förs in mellan två befintliga rader och att vi går in i infogningsläge. Detta finns i två varianter, som beskrivs i tabell 12-2.
| Kommando | Öppnar |
|---|---|
|
Raden under aktuell rad. |
|
Raden ovanför aktuell rad. |
24.6.3. Ta bort text
Som väntat erbjuder vi flera olika sätt att ta bort text, och de kretsar alla kring två tangenttryckningar. Först har vi kommandot x, som tar bort tecknet där markören står. x kan föregås av ett tal som anger hur många tecken som ska tas bort. Kommandot d är mer allmänt användbart. Precis som x kan det föregås av ett tal som anger hur många gånger borttagningen ska göras. Dessutom följs d alltid av ett rörelsekommando som styr hur stor borttagningen blir. Tabell 12-3 ger några exempel.
| Kommando | Tar bort |
|---|---|
|
Det aktuella tecknet. |
|
Det aktuella tecknet och de två följande tecknen. |
|
Den aktuella raden. |
|
Den aktuella raden och de fyra följande raderna. |
|
Från aktuell markörposition till början av nästa ord. |
|
Från markörens position till slutet av aktuell rad. |
|
Från markörens position till radens början. |
|
Från markörens position till det första icke-blankstegstecknet på raden. |
|
Från aktuell rad till filens slut. |
|
Från aktuell rad till den tjugonde raden i filen. |
24.6.4. Klippa ut, kopiera och klistra in text
Kommandot d tar inte bara bort text utan "klipper ut" den också. Varje gång vi använder d kopieras det som tas bort till en inklistringsbuffert (tänk urklipp), som vi senare kan hämta tillbaka med kommandot p för att klistra in innehållet efter markören eller med kommandot P för att klistra in det före markören.
| Kommando | Kopierar |
|---|---|
|
Den aktuella raden. |
|
Den aktuella raden och de fyra följande raderna. |
|
Från aktuell markörposition till början av nästa ord. |
|
Från aktuell markörposition till slutet av aktuell rad. |
|
Från aktuell markörposition till radens början. |
|
Från aktuell markörposition till det första icke-blankstegstecknet på raden. |
|
Från aktuell rad till filens slut. |
|
Från aktuell rad till den tjugonde raden i filen. |
24.6.5. Sammanfoga rader
vi är ganska strikt i sin syn på vad en rad är. Normalt går det inte att flytta markören till slutet av en rad och ta bort radslutstecknet för att slå ihop raden med raden nedanför. Därför har vi ett särskilt kommando, J (inte att förväxla med j, som används för markörförflyttning), för att sammanfoga rader.
24.7. Sök och ersätt
vi kan flytta markören till platser som bestäms av sökningar. Det kan ske antingen inom en enskild rad eller genom hela filen. Det kan också utföra textersättningar med eller utan bekräftelse från användaren.
24.7.1. Söka inom en rad
Kommandot f söker i en rad och flyttar markören till nästa förekomst av ett angivet tecken. Kommandot fa skulle till exempel flytta markören till nästa förekomst av tecknet a på den aktuella raden. När vi väl har gjort en teckensökning inom en rad kan sökningen upprepas genom att skriva semikolon (;).
24.7.2. Söka i hela filen
För att flytta markören till nästa förekomst av ett ord eller en fras används kommandot /. Det fungerar på samma sätt som vi tidigare lärde oss i programmet less. När du skriver / visas ett snedstreck längst ned på skärmen. Skriv sedan ordet eller frasen som ska sökas efter, följt av Enter. Markören flyttas då till nästa plats som innehåller söksträngen. En sökning kan upprepas med den föregående söksträngen genom kommandot n. Här är ett exempel:
/Line
Placera markören på första raden i filen. Skriv följande och tryck Enter:
24.7.3. Globalt sök och ersätt
vi använder kommandoläge för att utföra sök-och-ersätt-åtgärder (kallade substitution i vi) över ett radintervall eller en hel fil. För att ändra ordet Line till line i hela filen skulle vi skriva följande kommando:
:%s/Line/line/g
| Del | Betydelse |
|---|---|
|
Gå in i kommandoläge. |
|
Intervall från första raden till sista raden. |
|
Substitutionsoperation. |
|
Sökmönster och ersättningstext. |
|
Ersätt varje förekomst på varje rad. |
Låt oss dela upp kommandot och se vad varje del gör.
:%s/line/Line/gc
replace with Line (y/n/a/q/l/^E/^Y)?
| Tangent | Åtgärd |
|---|---|
|
Utför ersättningen. |
|
Hoppa över denna förekomst. |
|
Ersätt denna och alla efterföljande förekomster. |
|
Avsluta ersättningen. |
|
Utför denna ersättning och avsluta sedan. |
|
Rulla nedåt respektive uppåt. |
24.8. Redigera flera filer
Det är ofta praktiskt att redigera mer än en fil åt gången. Vi kan behöva ändra flera filer, eller så vill vi kopiera innehåll från en fil till en annan. Med vi kan vi öppna flera filer för redigering genom att ange dem på kommandoraden.
vi file1 file2 file3...
[me@linuxbox ~]$ ls -l /usr/bin > ls-output.txt
[me@linuxbox ~]$ vi foo.txt ls-output.txt
24.8.1. Byta mellan filer
För att växla från en fil till nästa använder vi :bn. För att gå tillbaka till föregående fil använder vi :bp.
Kommandot :buffers visar en lista över de filer som redigeras, och :buffer 2 växlar till buffert 2.
24.8.2. Öppna ytterligare filer för redigering
Det går också att lägga till filer i den aktuella redigeringssessionen. Kommandot :e i kommandoläge (kort för "edit"), följt av ett filnamn, öppnar ytterligare en fil. Låt oss avsluta den nuvarande sessionen och gå tillbaka till kommandoraden.
:e ls-output.txt
24.8.3. Kopiera innehåll från en fil till en annan
När vi redigerar flera filer vill vi ofta kopiera en del av en fil till en annan fil som vi arbetar med. Det görs enkelt med de vanliga yank-och-klistra-in-kommandona som vi använde tidigare. Vi kan visa det så här. Först byter vi till buffert 1 (foo.txt) genom att skriva:
:buffer 1
yy
:buffer 2
p
24.8.4. Infoga en hel fil i en annan
Det går också att infoga en hel fil i en fil som vi redigerar. För att se detta i praktiken avslutar vi vår vi-session och startar en ny med bara en enda fil.
[me@linuxbox ~]$ vi ls-output.txt
:r foo.txt
24.9. Spara vårt arbete
Precis som med allt annat i vi finns det flera olika sätt att spara de filer vi redigerat. Vi har redan tagit upp kommandot :w, men det finns några andra varianter som också kan vara användbara.
-
I normalläge sparar
ZZden aktuella filen och avslutarvi. På samma sätt kombinerar kommandot:wqkommandona:woch:qtill ett enda kommando som både sparar filen och avslutar. -
Kommandot
:wkan också ta ett valfritt filnamn. Det fungerar ungefär som "Spara som …". Om vi till exempel redigerarfoo.txtoch vill spara en alternativ version som heterfoo1.txt, skriver vi: -
Obs: Även om detta kommando sparar filen under ett nytt namn, ändrar det inte namnet på filen vi redigerar. När vi fortsätter att redigera arbetar vi alltså fortfarande med
foo.txt, inte medfoo1.txt.
24.10. bash kan också vi
I kapitel 8 tittade vi på olika sätt att redigera innehållet på kommandoraden. De redigeringskommandon som bash använder är inte slumpmässiga. De är inspirerade av textredigeraren emacs. Det är standardläget i bash, men bash stöder också kommandoradsredigering i vi-stil. Den funktionen aktiveras lätt med följande kommando:
[me@linuxbox ~]$ set -o vi
[me@linuxbox ~]$ set -o emacs
När detta är gjort kan vi använda många av de vi-liknande redigeringskommandon vi redan har lärt oss. Låt oss prova. Skriv följande exempeltext vid kommandoprompten:
24.11. Sammanfattning
Med denna grundläggande uppsättning färdigheter kan vi nu utföra det mesta av den textredigering som behövs för att underhålla ett typiskt Linuxsystem. Att lära sig använda vim regelbundet kommer att löna sig i längden. Eftersom redigerare i vi-stil är så djupt inbäddade i Unixkulturen kommer vi att stöta på många andra program som har påverkats av dess utformning. less är ett bra exempel på detta inflytande.
24.12. Vidare läsning
-
Vim, with Vigor - en fortsättning på denna handledning på LinuxCommand.org som tar läsaren till en mellannivå i skicklighet. Den finns här:
-
Learning The vi Editor - en Wikibook från Wikipedia som ger en kortfattad vägledning till
vioch flera av dess släktingar, däriblandvim. Den finns här: -
Bill Joy - en Wikipediaartikel om skaparen av
vi: -
Bram Moolenaar - en Wikipediaartikel om skaparen av
vim:
25. 13 - Anpassa prompten
I det här kapitlet ska vi titta på en till synes obetydlig detalj - vår skalprompt. Denna granskning kommer att avslöja en del av skalets och terminalemulatorns inre arbetssätt. Precis som så mycket annat i Linux är skalprompten mycket konfigurerbar.
25.1. Promptens anatomi
Vår standardprompt ser ut ungefär så här:
[me@linuxbox ~]$
Lägg märke till att den innehåller vårt användarnamn, vårt värdnamn och vår aktuella arbetskatalog, men hur blev den så? Det visar sig vara mycket enkelt. Prompten definieras av en miljövariabel som heter PS1 (kort för "prompt string 1"). Vi kan visa innehållet i PS1 med kommandot echo.
[me@linuxbox ~]$ echo $PS1
[\u@\h \W]\$
Oroa dig inte om ditt resultat inte ser ut exakt som i exemplet ovan. Varje Linuxdistribution definierar promptsträngen lite olika, ibland ganska fantasifullt.
Av resultatet ser vi att PS1 innehåller några av de tecken vi känner igen från prompten, till exempel hakparenteserna, snabel-a-tecknet och dollartecknet, men resten är gåtfullt. Den uppmärksamme känner igen dem som specialtecken med omvänt snedstreck, ungefär som dem vi såg i kapitel 7. Tabell 13-1 visar en del av de tecken som bash behandlar särskilt i promptsträngen.
| Sekvens | Visat värde |
|---|---|
|
ASCII-signal (bell). |
|
Aktuellt datum i formatet dag, månad, datum. |
|
ASCII escape-tecken. |
|
Värdnamn utan avslutande domännamn. |
|
Fullständigt värdnamn. |
|
Antal jobb som körs i den aktuella skalsessionen. |
|
Namn på den aktuella terminalenheten. |
|
Ny rad. |
|
Vagnretur (carriage return). |
|
Namn på skalprogrammet. |
|
Aktuell tid i 24-timmarsformat |
|
Aktuell tid i 12-timmarsformat. |
|
Aktuell tid i 12-timmarsformat med AM/PM. |
|
Aktuell tid i 24-timmarsformat |
|
Aktuell användares användarnamn. |
|
Skalets versionsnummer. |
|
Skalets versions- och releasenummer. |
|
Namn på aktuell arbetskatalog. |
|
Sista delen av aktuell arbetskatalogs namn. |
|
Historikposition för det aktuella kommandot. |
|
Antal kommandon som matats in under denna skalsession. |
|
Visar |
|
Markerar början av icke-utskrivbara tecken. |
|
Markerar slutet av icke-utskrivbara tecken. |
25.2. Prova några alternativa promptutformningar
Med denna lista över specialtecken kan vi ändra prompten och se vad som händer. Först säkerhetskopierar vi den nuvarande promptsträngen så att vi kan återställa den senare. Det gör vi genom att kopiera den till en annan skalvariabel som vi skapar själva.
[me@linuxbox ~]$ ps1_old="$PS1"
[me@linuxbox ~]$ echo $ps1_old
[\u@\h \W]\$
[me@linuxbox ~]$ PS1="$ps1_old"
Vi skapar en ny variabel som heter ps1_old och tilldelar den värdet i PS1. Att strängen faktiskt kopierades kan vi kontrollera med kommandot echo.
[me@linuxbox ~]$ PS1=
Vi kan när som helst under terminalsessionen återställa den ursprungliga prompten genom att helt enkelt göra tvärtom.
PS1="\$ "
Nu när vi är redo att gå vidare kan vi se vad som händer om promptsträngen är tom.
PS1="\[\a\]\$ "
Om vi inte tilldelar promptsträngen någonting får vi ingenting. Ingen prompt alls! Prompten finns fortfarande där, men visar ingenting, precis som vi bad den om. Eftersom det ser lite obehagligt ut ersätter vi den med en minimal prompt.
PS1="\A \h \$ "
PS1="<\u@\h \W>\$ "
25.3. Lägga till färg
De flesta terminalemulatorer reagerar på vissa teckensekvenser som inte skrivs ut för att styra sådant som teckenattribut (till exempel färg, fetstil och den fruktade blinkande texten) och markörens position. Vi återkommer strax till markörposition, men först tittar vi på färg.
25.3.1. Terminalförvirring
På den gamla goda tiden, när terminaler var kopplade till fjärrdatorer, fanns det många konkurrerande terminalmärken, och alla fungerade olika. De hade olika tangentbord och olika sätt att tolka styrinformation. Unix och Unix-liknande system har två ganska komplicerade delsystem för att hantera detta Babel av terminalstyrning (kallade termcap och terminfo). Om du letar längst in i inställningarna för din terminalemulator kan du hitta en inställning för typ av terminalemulering.
I ett försök att få terminaler att tala ett gemensamt språk tog American National Standards Institute (ANSI) fram en standarduppsättning teckensekvenser för styrning av bildskärmsterminaler. Gamla DOS-användare minns kanske filen ANSI.SYS, som användes för att aktivera tolkningen av dessa koder.
\e[0;30m
| Sekvens | Textfärg | Sekvens | Textfärg |
|---|---|---|---|
|
Svart |
|
Mörkgrå |
|
Röd |
|
Ljusröd |
|
Grön |
|
Ljusgrön |
|
Brun |
|
Gul |
|
Blå |
|
Ljusblå |
|
Lila |
|
Ljuslila |
|
Cyan |
|
Ljuscyan |
|
Ljusgrå |
|
Vit |
Teckenfärg styrs genom att man skickar en ANSI-escape-kod till terminalemulatorn, inbäddad i teckenströmmen som ska visas. Styrkoden "skrivs" inte ut på skärmen, utan tolkas i stället av terminalen som en instruktion. Som vi såg i tabell 13-1 används sekvenserna \[ och \] för att kapsla in tecken som inte skrivs ut. En ANSI-escape-kod börjar med \e (oktalt 033, koden som skapas av Esc), följt av ett valfritt teckenattribut och därefter en instruktion. Koden för att sätta textfärgen till normal (attribut = 0) och svart text ser till exempel ut så här:
PS1="\[\e[0;31m\]<\u@\h \W>\$ "
PS1="\[\e[0;31m\]<\u@\h \W>\$\[\e[0m\] "
Låt oss försöka skapa en röd prompt. Vi lägger in escape-koden i början.
| Sekvens | Bakgrundsfärg | Sekvens | Bakgrundsfärg |
|---|---|---|---|
|
Svart |
|
Blå |
|
Röd |
|
Lila |
|
Grön |
|
Cyan |
|
Brun |
|
Ljusgrå |
Vi kan skapa en prompt med röd bakgrund så här:
PS1="\[\e[0;41m\]<\u@\h \W>\$\[\e[0m\] "
25.4. Flytta markören
Escape-koder kan användas för att placera markören. Det används ofta för att visa en klocka eller någon annan information på en annan plats på skärmen, till exempel i ett övre hörn varje gång prompten ritas. Tabell 13-4 visar escape-koder som flyttar markören.
| Escape-kod | Åtgärd |
|---|---|
|
Flytta markören till rad |
|
Flytta markören upp |
|
Flytta markören ner |
|
Flytta markören framåt |
|
Flytta markören bakåt |
|
Rensa skärmen och flytta markören till övre vänstra hörnet. |
|
Rensa från markörens position till slutet av den aktuella raden. |
|
Spara den aktuella markörpositionen. |
|
Återställ den sparade markörpositionen. |
PS1="\[\e[s\e[0;0H\e[0;41m\e[K\e[1;33m\t\e[0m\e[u\]<\u@\h \W>\$ "
| Sekvens | Åtgärd |
|---|---|
|
Påbörja en icke-utskrivbar sekvens. |
|
Spara markörpositionen. |
|
Flytta markören till övre vänstra hörnet. |
|
Ställ in bakgrundsfärgen till röd. |
|
Rensa till slutet av raden. |
|
Ställ in textfärgen till gul. |
|
Visa aktuell tid. |
|
Stäng av färg. |
|
Återställ den sparade markörpositionen. |
|
Avsluta den icke-utskrivbara sekvensen. |
|
Promptsträngen. |
25.5. Spara prompten
Vi vill förstås inte skriva in den där besten varje gång, så vi behöver spara prompten någonstans. Vi kan göra prompten permanent genom att lägga in den i filen .bashrc. Lägg då till dessa två rader i filen:
PS1="\[\e[s\e[0;0H\e[0;41m\e[K\e[1;33m\t\e[0m\e[u\]<\u@\h \W>\$ "
export PS1
25.6. Sammanfattning
Tro det eller ej, men det finns mycket mer man kan göra med promptar med hjälp av skalfunktioner och skript som vi inte har tagit upp här, men detta är en bra början. Alla kommer inte att bry sig nog mycket för att ändra prompten, eftersom standardprompten oftast duger gott. Men för oss som gillar att pilla erbjuder skalet många timmars trivsamt experimenterande.
25.7. Vidare läsning
-
Bash Prompt HOWTO:
-
Wikipedia om ANSI escape-koder:
Del 3 - Vanliga uppgifter och viktiga verktyg
26. 14 - Pakethantering
Om vi tillbringar någon tid i Linuxvärlden kommer vi att höra många åsikter om vilken av de många Linuxdistributionerna som är "bäst". Ofta blir sådana diskussioner riktigt fåniga och handlar om sådant som hur snygg skrivbordsbakgrunden är (vissa använder inte Ubuntu på grund av standardfärgschemat!) och andra bagateller.
Det viktigaste måttet på en distributions kvalitet är dess paketsystem och hur livskraftig dess användar- och stödgemenskap är. Ju mer tid vi tillbringar med Linux, desto tydligare blir det att dess programvarulandskap är mycket dynamiskt. Saker förändras ständigt. De flesta ledande Linuxdistributioner ger ut nya versioner var sjätte månad och många enskilda program uppdateras varje dag. För att hänga med i denna programvarustorm behöver vi bra verktyg för pakethantering.
26.1. Paketsystem
Olika distributioner använder olika paketsystem, och som huvudregel gäller att ett paket som är avsett för en distribution inte är kompatibelt med en annan. De flesta distributioner hör hemma i något av två läger vad gäller paketteknik: Debian-lägret med .deb och Red Hat-lägret med .rpm. Det finns några viktiga undantag, som Gentoo, Slackware och Arch, men de flesta andra använder något av dessa två grundsystem, som visas i tabell 14-1.
| Paketsystem | Distributioner |
|---|---|
Debian-stil ( |
Debian, Ubuntu, Linux Mint, Raspberry Pi OS |
Red Hat-stil ( |
Fedora, CentOS, Red Hat Enterprise Linux, OpenSUSE |
26.2. Hur ett paketsystem fungerar
Metoden för programvarudistribution i den proprietära programvarubranschen brukar innebära att man köper någon form av installationsmedium, till exempel en "installationsskiva", eller besöker en leverantörs webbplats och hämtar en produkt, för att sedan köra en "installationsguide" som installerar ett nytt program i systemet.
26.2.1. Paketfiler
Den grundläggande programvaruenheten i ett paketsystem är paketfilen. En paketfil är en komprimerad samling filer som tillsammans utgör programvarupaketet. Ett paket kan bestå av många program och datafiler som stöder programmen. Förutom de filer som ska installeras innehåller paketfilen också metadata om paketet, till exempel en textbeskrivning av paketet och dess innehåll. Dessutom innehåller många paket skript som körs före respektive efter installationen och utför konfigurationsuppgifter före och efter själva paketinstallationen.
26.2.2. Programförråd
Även om vissa programvaruprojekt väljer att själva paketera och distribuera sin programvara, skapas de flesta paket i dag av distributionsleverantörerna och engagerade tredje parter. Paket görs tillgängliga för distributionens användare i centrala programförråd som kan innehålla många tusen paket, vart och ett särskilt byggt och underhållet för distributionen.
26.2.3. Beroenden
Program är sällan helt fristående; i stället förlitar de sig på att andra programvarukomponenter finns tillgängliga för att få sitt arbete gjort. Vanliga aktiviteter, som till exempel in- och utmatning, hanteras av rutiner som delas av många program. Dessa rutiner lagras i så kallade delade bibliotek, som tillhandahåller viktiga tjänster åt mer än ett program. Om ett paket kräver en delad resurs, till exempel ett delat bibliotek, säger man att det har ett beroende. Moderna pakethanteringssystem erbjuder alla någon form av beroendeupplösning för att säkerställa att när ett paket installeras, installeras också alla dess beroenden.
26.2.4. Paketverktyg på hög och låg nivå
Pakethanteringssystem består vanligtvis av två typer av verktyg.
-
Verktyg på låg nivå som hanterar uppgifter som att installera och ta bort paketfiler.
-
Verktyg på hög nivå som söker i metadata och löser beroenden.
| Distributioner | Lågnivåverktyg | Högnivåverktyg |
|---|---|---|
Debian-stil |
|
|
Fedora, Red Hat Enterprise Linux, CentOS |
|
|
26.3. Vanliga uppgifter inom pakethantering
Många åtgärder kan utföras med kommandoradsverktygen för pakethantering. Vi ska titta på de vanligaste. Var medveten om att verktygen på låg nivå också stöder skapandet av paketfiler, men det ligger utanför denna boks ram.
I diskussionen nedan syftar termen package_name på det faktiska namnet på ett paket, till skillnad från termen package_file, som är namnet på filen som innehåller paketet. Dessutom måste paketförrådet frågas av innan några paketåtgärder kan utföras, så att den lokala kopian av dess databas kan synkroniseras. Red Hats program dnf gör detta automatiskt och uppdaterar den lokala databasen om det har gått för lång tid sedan senaste uppdateringen. Debians program apt måste däremot köras med kommandot update för att uttryckligen uppdatera den lokala databasen. Det behöver göras då och då. I exemplen nedan körs apt update före varje åtgärd, men i verkligheten behöver detta bara göras var några timme för att vara på den säkra sidan.
26.3.1. Hitta ett paket i ett programförråd
| Stil | Kommando(n) |
|---|---|
Debian |
|
Red Hat |
|
Med hjälp av verktyg på hög nivå kan man söka i metadata från programförråden och hitta ett paket utifrån namn eller beskrivning (se tabell 14-3).
dnf search emacs
26.3.2. Installera ett paket från ett programförråd
| Stil | Kommando(n) |
|---|---|
Debian |
|
Red Hat |
|
Verktyg på hög nivå gör det möjligt att hämta ett paket från ett programförråd och installera det med full beroendeupplösning (se tabell 14-4).
apt update; apt install emacs
26.3.3. Installera ett paket från en paketfil
| Stil | Kommando(n) |
|---|---|
Debian |
|
Red Hat |
|
Om en paketfil har hämtats från en annan källa än ett programförråd kan den installeras direkt (dock utan beroendeupplösning) med ett verktyg på låg nivå (se tabell 14-5).
rpm -i emacs-22.1-7.fc7-i386.rpm
Vid användning av verktyg på låg nivå direkt utförs ingen beroendeupplösning.
26.3.4. Ta bort ett paket
| Stil | Kommando(n) |
|---|---|
Debian |
|
Red Hat |
|
Paket kan avinstalleras med antingen verktyg på hög nivå eller låg nivå. Tabell 14-6 visar verktygen på hög nivå.
apt remove emacs
26.3.5. Uppdatera paket från ett programförråd
| Stil | Kommando(n) |
|---|---|
Debian |
|
Red Hat |
|
Den vanligaste uppgiften inom pakethantering är att hålla systemet uppdaterat med de senaste versionerna av paketen. Verktyg på hög nivå kan utföra denna viktiga uppgift i ett enda steg (se tabell 14-7).
apt update; apt upgrade
26.3.6. Uppgradera ett paket från en paketfil
| Stil | Kommando(n) |
|---|---|
Debian |
|
Red Hat |
|
Om en uppdaterad version av ett paket har hämtats från en källa som inte är ett programförråd kan den installeras och ersätta den tidigare versionen (se tabell 14-8).
rpm -U emacs-22.1-7.fc7-i386.rpm
26.3.7. Lista installerade paket
| Stil | Kommando(n) |
|---|---|
Debian |
|
Red Hat |
|
26.3.8. Ta reda på om ett paket är installerat
| Stil | Kommando(n) |
|---|---|
Debian |
|
Red Hat |
|
För att till exempel avgöra om paketet emacs är installerat på ett Debian-liknande system kan vi använda följande kommando:
dpkg --status emacs
26.3.9. Visa information om ett installerat paket
| Stil | Kommando(n) |
|---|---|
Debian |
|
Red Hat |
|
Om namnet på ett installerat paket är känt kan vi använda kommandona i tabell 14-11 för att visa en beskrivning av paketet.
apt-cache show emacs
26.3.10. Ta reda på vilket paket som installerade en fil
| Stil | Kommando(n) |
|---|---|
Debian |
|
Red Hat |
|
För att avgöra vilket paket som ansvarar för installationen av en viss fil kan vi använda kommandona i tabell 14-12.
rpm -qf /usr/bin/vim
26.4. Distributionsoberoende paketformat
Under de senaste åren har distributionsleverantörer tagit fram universella paketformat som inte är knutna till någon särskild Linuxdistribution. Hit hör Snaps (utvecklade och marknadsförda av Canonical), Flatpaks (ursprungligen drivna av Red Hat men numera allmänt tillgängliga) och AppImages. Även om de fungerar lite olika är målet detsamma: att samla ett program och alla dess beroenden i ett enda stycke som kan installeras som en enhet. Detta är inte en helt ny idé. Under Linux tidiga år (tänk sent 1990-tal) fanns en teknik som kallades statisk länkning, där ett program och de bibliotek det behövde slogs samman till en enda stor binärfil.
Det finns vissa fördelar med detta sätt att paketera. Den främsta är att det minskar arbetet som krävs för att distribuera ett program. I stället för att anpassa programmet till de bibliotek och andra stödfiler som ingår i distributionens grundsystem byggs programmet en gång och kan sedan installeras på vilket system som helst. En del av dessa format kör dessutom programmet i en containerbaserad sandlåda för att ge extra säkerhet.
26.5. Sammanfattning
I de kommande kapitlen ska vi utforska många olika program inom ett brett spektrum av användningsområden. Även om de flesta av dessa program vanligen är installerade som standard kan vi behöva installera fler paket om de nödvändiga programmen inte finns. Med vår nyvunna kunskap om (och uppskattning för) pakethantering bör vi inte ha några problem att installera och hantera de program vi behöver.
26.5.1. Myten om att det är svårt att installera program i Linux
Människor som byter till Linux från andra plattformar tror ibland att det är svårt att installera programvara. I praktiken gör Linuxdistributioner det vanligtvis enklare att skaffa programvara, eftersom programförråden erbjuder en slags allt-i-ett-butik, och många enhetsdrivrutiner ingår direkt i kärnan.
26.6. Vidare läsning
-
Kapitlet om pakethantering i Debian GNU/Linux FAQ ger en översikt över pakethantering i Debiansystem:
-
Hemsidan för RPM-projektet:
-
Wikipedia om metadata:
-
Jämförelse av Snap, Flatpak och AppImage:
27. 15 - Lagringsmedier
I tidigare kapitel tittade vi på hur man hanterar data på filnivå. I det här kapitlet ska vi i stället se på data på enhetsnivå. Linux har imponerande möjligheter att hantera lagringsenheter, oavsett om det gäller fysisk lagring, som hårddiskar, nätverkslagring eller virtuella lagringsenheter som RAID (Redundant Array of Independent Disks) och LVM (Logical Volume Manager).
Eftersom detta inte är en bok om systemadministration kommer vi dock inte att försöka täcka hela ämnet på djupet. Det vi ska göra är att introducera några av de begrepp och centrala kommandon som används för att hantera lagringsenheter.
-
För att genomföra övningarna i detta kapitel använder vi ett USB-minne, en extern USB-hårddisk och en DVD/CD-RW-skiva (på system som har en cd-brännare).
-
Vi ska titta på följande kommandon:
-
mount- montera ett filsystem. -
umount- avmontera ett filsystem. -
parted- program för att hantera partitioner. -
mkfs- skapa ett filsystem. -
fsck- kontrollera och reparera ett filsystem. -
dd- konvertera och kopiera en fil. -
genisoimage- skapa en ISO 9660-avbildningsfil. -
wodim- skriva data till optiska lagringsmedier. -
sha256sum- beräkna och kontrollera SHA256-kontrollsummor.
27.1. Montera och avmontera lagringsenheter
De senaste framstegen på Linuxskrivbordet har gjort hanteringen av lagringsenheter mycket enkel för skrivbordsanvändare. För det mesta ansluter vi en enhet till systemet och så "bara fungerar det". Förr i tiden (säg omkring 2004) behövde allt detta göras manuellt. På system utan skrivbordsmiljö (alltså servrar) är detta fortfarande till stor del ett manuellt förfarande, eftersom servrar ofta har extrema lagringsbehov och komplicerade konfigurationer.
Det första steget i att hantera en lagringsenhet är att ansluta enheten till filsystemsträdet. Denna process kallas montering och gör det möjligt för enheten att samspela med operativsystemet. Som vi minns från kapitel 2 har Unix-liknande operativsystem, som Linux, ett enda filsystemsträd med enheter anslutna på olika ställen. Det skiljer sig från andra operativsystem, som MS-DOS och Windows, som har separata filsystemträd för varje enhet (till exempel C:\, D:\ och så vidare).
| Fält | Innehåll | Beskrivning |
|---|---|---|
1 |
Enhet |
Enhetsfilen, etiketten eller UUID:t som är kopplat till den fysiska enheten. |
2 |
Monteringspunkt |
Katalogen där enheten är ansluten till filsystemsträdet. |
3 |
Filsystemstyp |
Exempel: |
4 |
Flaggor |
Monteringsflaggor som skrivskyddat eller no-exec. |
5 |
Frekvens |
Anger om filsystemet säkerhetskopieras med |
6 |
Ordning |
Anger i vilken ordning filsystem kontrolleras med |
27.1.1. Visa en lista över monterade filsystem
Kommandot mount används för att montera filsystem. Om vi kör kommandot utan argument visas en lista över de filsystem som för närvarande är monterade:
[me@linuxbox ~]$ mount
/dev/sda2 on / type ext4 (rw)
/dev/sda5 on /home type ext4 (rw)
/dev/sda1 on /boot type ext4 (rw)
/dev/sdd1 on /media/disk type vfat (rw,nosuid,nodev,noatime)
Formatet på listan är:
device on mount_point type file_system_type (options)
För att bara visa blockenheter kan vi filtrera utdatan:
[me@linuxbox ~]$ mount | grep /dev/sd
På moderna skrivbordssystem monteras flyttbara enheter ofta automatiskt. Om vi vill montera om ett USB-minne någon annanstans kan vi avmontera det och skapa en ny monteringspunkt:
[me@linuxbox ~]$ sudo -i
[root@linuxbox ~]# umount /dev/sdc
[root@linuxbox ~]# mkdir /mnt/flash
[root@linuxbox ~]# mount -t vfat /dev/sdc /mnt/flash
Om vi försöker avmontera medan vår aktuella arbetskatalog ligger inuti det monterade filsystemet får vi ett felmeddelande om att enheten är upptagen:
[root@linuxbox cdrom]# umount /dev/sdc
umount: /mnt/flash: device is busy
[root@linuxbox cdrom]# cd
[root@linuxbox ~]# umount /dev/sdc
27.1.2. Varför är det viktigt att avmontera
Avmontering är viktigt eftersom Linux använder buffering omfattande. Data kan fortfarande sitta i minnet och vänta på att skrivas till enheten. Avmontering spolar kvarvarande data så att enheten kan tas bort på ett säkert sätt.
27.2. Ta reda på enhetsnamn
Det är ibland svårt att avgöra namnet på en enhet. Förr i tiden var det inte särskilt svårt. En enhet satt alltid på samma plats och ändrade sig inte. Unix-liknande system gillar det så. När Unix utvecklades innebar "att byta diskenhet" att man använde en gaffeltruck för att flytta en apparat stor som en tvättmaskin ur datorrummet. Under senare år har den typiska hårdvarukonfigurationen på skrivbordssystem blivit betydligt mer dynamisk, och Linux har utvecklats för att bli mer flexibelt än sina föregångare.
[me@linuxbox ~]$ sudo tail -f /var/log/syslog
[me@linuxbox ~]$ sudo journalctl -f
I exemplen ovan utnyttjade vi det moderna Linuxskrivbordets förmåga att mer eller mindre automatiskt montera enheten och först därefter ta reda på dess namn. Men vad händer om vi administrerar en server eller en annan miljö där detta inte sker? Hur tar vi reda på det då?
[me@linuxbox ~]$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 111.8G 0 disk
|-sda1 8:1 0 976M 0 part /boot/efi
|-sda3 8:3 0 14.9G 0 part [SWAP]
sdb 8:16 0 931.5G 0 disk
sr0 11:0 1 1024M 0 rom
Låt oss först se hur systemet namnger enheter. Om vi listar innehållet i katalogen /dev (där alla enheter finns) ser vi att det finns mycket, mycket många enheter.
[me@linuxbox ~]$ sudo mount /dev/sdc /mnt/flash
[me@linuxbox ~]$ df
Innehållet i listan avslöjar några mönster i hur enheter namnges. Tabell 15-2 visar några av dessa mönster.
| Mönster | Enhet |
|---|---|
|
Diskettenheter. |
|
Äldre IDE/PATA-diskar. |
|
Skrivare. |
|
SCSI-diskar och de flesta moderna diskliknande enheter, inklusive SATA, USB-lagringsenheter och flashminnen. |
|
Optiska enheter. |
27.3. Testa och reparera filsystem
Med tiden kan filsystem utveckla problem på grund av felaktig avstängning, hårdvarufel och programfel. Linux tillhandahåller verktyget fsck (File System Consistency Check) för att kontrollera och reparera filsystem.
I vår tidigare diskussion om filen /etc/fstab såg vi några mystiska siffror i slutet av varje rad. Varje gång systemet startar kontrollerar det rutinmässigt filsystemens integritet innan de monteras. Det görs med programmet fsck (förkortning för "filesystem check"). Den sista siffran i varje fstab-rad anger i vilken ordning enheterna kontrolleras. Filsystem med en avslutande nolla kontrolleras inte rutinmässigt.
Förutom att kontrollera filsystemets integritet kan fsck också reparera korrupta filsystem med varierande framgång, beroende på skadans omfattning. På Unix-liknande filsystem placeras återvunna fildelar ofta i katalogen lost+found i filsystemets rot.
För att kontrollera vår EXT4_Disk-partition, som bör avmonteras först, kan vi göra så här:
[me@linuxbox ~]$ sudo umount /dev/sdd1
[me@linuxbox ~]$ sudo fsck /dev/sdd1
fsck from util-linux 2.37.2
e2fsck 1.46.5 (30-Dec-2021)
EXT4_Disk: clean, 11/7331840 files, 606693/29296640 blocks
Numera är filsystemkorruption ganska ovanligt om det inte rör sig om hårdvaruproblem som en trasig disk. På många system stannar en korruption som upptäcks vid uppstart systemet och ber dig köra fsck innan det fortsätter.
27.3.1. What the fsck?
I Unixkulturen används ordet fsck ofta i stället för ett populärt ord som det delar tre bokstäver med. Det är särskilt passande, med tanke på att du troligen kommer att yttra det nämnda ordet om du någonsin hamnar i en situation där du tvingas köra fsck.
27.4. Skapa nya filsystem
Låt oss säga att vi har en extern USB-hårddisk med ett enda FAT32-filsystem och vill dela upp den i två partitioner: ett Linuxinhemskt filsystem (ext4) och en andra partition formaterad som NTFS för användning tillsammans med Windows. Det innebär två steg.
-
Skapa en ny partitionslayout.
-
Skapa nya, tomma filsystem på disken.
I exemplen som följer är det helt avgörande att du noggrant uppmärksammar vilka enhetsnamn som faktiskt används i ditt system och inte använder namnen som förekommer i denna text!
27.4.1. Hantera partitioner med parted
parted är ett av många program (både för kommandoraden och grafiska) som låter oss arbeta direkt med diskliknande enheter (som hårddiskar och USB-minnen) på mycket låg nivå. Vi ansluter disken och använder lsblk för att få fram dess namn:
[me@linuxbox ~]$ lsblk
[me@linuxbox ~]$ sudo umount /dev/sdd1
[me@linuxbox ~]$ sudo parted /dev/sdd
GNU Parted 3.4
Using /dev/sdd
Welcome to GNU Parted! Type 'help' to view a list of commands.
(parted)
Som vi ser är den externa USB-hårddisken ansluten som /dev/sdd, och den innehåller en partition med namnet /dev/sdd1.
(parted) print
Med parted kan vi redigera, ta bort och skapa partitioner på enheten. För att arbeta med vår hårddisk måste vi först avmontera den (om det behövs) och sedan starta programmet parted så här:
(parted) rm 1
(parted) mkpart
Partition type? primary/extended? primary
File system type? [ext2]? ext4
Start? 1
End? 120000
(parted) mkpart
Partition type? primary/extended? primary
File system type? [ext2]? ntfs
Start? 120001
End? 240000
(parted) print
(parted) quit
27.4.2. Skapa ett nytt filsystem med mkfs
För att skapa ett nytt filsystem använder vi kommandot mkfs. Vi kan specificera filsystemtypen med flaggan -t. Till exempel, för att skapa ett ext4-filsystem:
[me@linuxbox ~]$ sudo mkfs -t ext4 -L EXT4_Disk /dev/sdd1
[me@linuxbox ~]$ sudo mkfs -t ntfs --quick -L NTFS_Disk /dev/sdd2
Olika filsystemstyper stöder olika flaggor. Se manualsidan för mkfs för detaljer.
27.5. Flytta data direkt till och från enheter
Även om vi oftast tänker på data som organiserade i filer, är det också möjligt att tänka på data i "rå" form. Om vi betraktar en hårddisk som en stor samling block snarare än filer och kataloger kan vi utföra nyttiga uppgifter, till exempel klona enheter.
Programmet dd kopierar datablock från en plats till en annan. Det använder en traditionell syntax:
dd if=input_file of=output_file [bs=block_size [count=blocks]]
Kommandot dd är mycket kraftfullt. Det kallas ibland skämtsamt "destroy disk" eftersom användare inte sällan anger fel if- eller of-specifikation. Dubbelkolla alltid indata och utdata innan du trycker Enter.
Om vi hade två USB-minnen av samma storlek, tilldelade /dev/sdb och /dev/sdc, kan vi kopiera det första direkt till det andra så här:
[me@linuxbox ~]$ sudo dd if=/dev/sdb of=/dev/sdc
Om bara den första enheten vore ansluten kan vi i stället kopiera dess innehåll till en avbildningsfil för senare återställning:
[me@linuxbox ~]$ sudo dd if=/dev/sdb of=flash_drive.img
27.6. Skapa CD-ROM-avbildningar
Att skriva till en inspelningsbar CD-ROM eller DVD består i regel av två steg:
-
Skapa en ISO-avbildningsfil som innehåller en exakt avbildning av filsystemet.
-
Skriva avbildningsfilen till det optiska mediet.
27.6.1. Skapa en avbildningskopia av en CD-ROM
Om vi vill göra en ISO-avbildning av en befintlig data-CD-ROM kan vi använda dd för att läsa alla datablock från enheten och kopiera dem till en lokal fil. Förutsatt att den optiska enheten är /dev/cdrom kan vi skapa en ISO-avbildning så här:
dd if=/dev/cdrom of=ubuntu.iso
Detta fungerar även för data-DVD-skivor, men inte för ljud-CD-skivor eftersom de inte använder ett vanligt filsystem.
27.6.2. Skapa en avbildning från en samling filer
För att skapa en ISO-avbildning från ett katalogträd kan vi använda genisoimage. Först samlar vi filerna i en katalog och kör sedan:
genisoimage -o cd-rom.iso -R -J ~/cd-rom-files
Flaggan -R lägger till Rock Ridge-tillägg för långa filnamn och POSIX-rättigheter, medan -J aktiverar Joliet-tillägg för långa Windows-filnamn.
27.6.3. Ett program med ett annat namn
Om du tittar i äldre handledningar kommer du ofta att se programmen mkisofs och cdrecord. Många Linuxdistributioner tillhandahåller numera deras ersättare som genisoimage och wodim.
27.7. Skriva CD-ROM-avbildningar
När vi väl har en ISO-avbildningsfil kan vi bränna den till optiskt medium.
27.7.1. Montera en ISO-avbildning direkt
Vi kan montera en ISO-avbildning direkt från hårddisken och behandla den som monterat optiskt medium genom att använda loop-enheten:
mkdir /mnt/iso_image
mount -t iso9660 -o loop image.iso /mnt/iso_image
När avbildningen är monterad fungerar den ungefär som en riktig CD-ROM eller DVD. Kom ihåg att avmontera den när du är klar.
27.7.2. Rensa en skrivbar CD-ROM
Skrivbara CD-RW-medier måste raderas innan de kan återanvändas. Det snabbaste rensningsläget är vanligtvis fast:
wodim dev=/dev/cdrw blank=fast
27.7.3. Skriva en avbildning
För att bränna en avbildningsfil använder vi wodim med skrivarenheten och avbildningens filnamn:
wodim dev=/dev/cdrw image.iso
Två vanliga flaggor är -v för utförlig utdata och -dao för disc-at-once-läge.
27.8. Verifiera data
Vid hämtning av stora filer, som Linuxinstallationsavbildningar, är det klokt att kontrollera att nedladdningen slutfördes korrekt och inte blev korrupt. Ett vanligt verktyg för detta är sha256sum, en modern ersättare för det äldre md5sum.
Anta att vi har laddat ner linuxmint-22-cinnamon-64bit.iso och den tillhörande kontrollsummefilen sha256sum.txt. Vi kan granska kontrollsummefilen så här:
[me@linuxbox ~]$ cat sha256sum.txt
7a04b54830004e945c1eda6ed6ec8c57ff4b249de4b331bd021a849694f29b8f *linuxmint-22-cinnamon-64bit.iso
78a2438346cfe69a1779b0ac3fc05499f8dc7202959d597dd724a07475bc6930 *linuxmint-22-mate-64bit.iso
55e917b99206187564029476f421b98f5a8a0b6e54c49ff6a4cb39dcfeb4bd80 *linuxmint-22-xfce-64bit.iso
Vi kan jämföra kontrollsumman för vår nedladdade fil direkt:
[me@linuxbox ~]$ sha256sum -b linuxmint-22-cinnamon-64bit.iso
7a04b54830004e945c1eda6ed6ec8c57ff4b249de4b331bd021a849694f29b8f *linuxmint-22-cinnamon-64bit.iso
Eller låta sha256sum verifiera listade filer automatiskt:
[me@linuxbox ~]$ sha256sum -c sha256sum.txt
linuxmint-22-cinnamon-64bit.iso: OK
27.9. Sammanfattning
Hantering av lagringsenheter i Linux sträcker sig från enkel automatisk montering på skrivbordssystem till mycket flexibel lågnivåkontroll med verktyg som mount, parted, mkfs, dd, wodim och sha256sum. Även utan att fördjupa sig i systemadministration är det nyttigt att förstå hur enheter namnges, monteras, partitioneras, formateras, kopieras och verifieras.
27.10. Vidare läsning
-
Wikipedia-artikel om filsystem: https://sv.wikipedia.org/wiki/Filsystem
-
En introduktion till Linux-filsystem: https://www.tldp.org/LDP/sag/html/filesystems.html
-
Förstå diskpartitionering: https://www.howtogeek.com/184659/beginner-geek-how-linux-filesystems-work/
28. 16 - Nätverk
När det gäller nätverk finns det förmodligen ingenting som inte går att göra med Linux. Linux används för att bygga alla möjliga slags nätverkssystem och nätverksapparater, däribland brandväggar, routrar, namnservrar, nätverksansluten lagring (NAS) och mycket mer.
Liksom ämnet nätverk är omfattande, är också antalet kommandon som kan användas för att konfigurera och styra det stort. Vi ska koncentrera oss på några av de mest använda. De kommandon vi tittar på omfattar både sådana som används för att övervaka nätverk och sådana som används för filöverföring. Dessutom ska vi utforska programmet ssh, som används för fjärrinloggning. Detta kapitel tar upp följande kommandon:
-
ping- skicka en ICMP ECHO_REQUEST till nätverksvärdar. -
traceroute- skriv ut den väg paket tar till en nätverksvärd. -
ip- visa och hantera routning, enheter, policybaserad routning och tunnlar. -
netstat- skriv ut nätverksanslutningar, routingtabeller, gränssnittsstatistik, masquerade-anslutningar och multicast-medlemskap. -
ftp- internetprogram för filöverföring. -
curl- överför en URL. -
wget- icke-interaktiv nätverkshämtare. -
ssh- OpenSSH SSH-klient (program för fjärrinloggning).
Vissa av dessa kommandon kan kräva ytterligare paket eller superanvändarbehörighet, beroende på din distribution.
28.1. Undersöka och övervaka ett nätverk
Även om du inte är systemadministratör är det ofta hjälpsamt att undersöka ett nätverks prestanda och funktion.
28.1.1. ping
Det mest grundläggande nätverkskommandot är ping. Kommandot ping skickar ett särskilt nätverkspaket som kallas ICMP ECHO_REQUEST till en angiven värd. De flesta nätverksenheter som tar emot detta paket svarar på det, vilket gör det möjligt att verifiera nätverksanslutningen.
[me@linuxbox ~]$ ping linuxcommand.org
PING linuxcommand.org (66.35.250.210) 56(84) bytes of data.
64 bytes from vhost.sourceforge.net (66.35.250.210): icmp_seq=1 ttl=43 time=107 ms
64 bytes from vhost.sourceforge.net (66.35.250.210): icmp_seq=2 ttl=43 time=108 ms
Obs: Det går att konfigurera de flesta nätverksenheter (inklusive Linuxvärdar) så att de ignorerar dessa paket. Det görs vanligen av säkerhetsskäl, för att delvis dölja en värd för en möjlig angripare. Det är också vanligt att brandväggar konfigureras för att blockera ICMP-trafik.
28.1.2. traceroute
Programmet traceroute (på vissa system används det liknande programmet tracepath i stället) listar alla de "hopp" som nätverkstrafiken tar från det lokala systemet till en angiven värd. För att till exempel se vilken väg som tas till slashdot.org gör vi så här:
[me@linuxbox ~]$ traceroute slashdot.org
Utdata ser ut så här:
28.1.3. ip
Programmet ip är ett mångsidigt verktyg för nätverkskonfiguration som utnyttjar hela det utbud av nätverksfunktioner som finns i moderna Linuxkärnor. Det ersätter det äldre och numera avvecklade programmet ifconfig. Programmet ip används för att undersöka olika nätverksinställningar och statistik. Med dess många flaggor kan vi titta på många olika delar av vår nätverkskonfiguration. Med ip kan vi undersöka ett systems nätverksgränssnitt och routingtabell. Först gränssnitten:
I exemplet ovan ser vi att vårt testsystem har tre nätverksgränssnitt. Det första, lo, är loopback-gränssnittet, ett virtuellt gränssnitt som systemet använder för att "prata med sig självt". Det andra, enp1s0, är ett Ethernet-gränssnitt (en = Ethernet), och det tredje, wlp2s0, är ett trådlöst gränssnitt (wl = wireless).
[me@linuxbox ~]$ ip address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
3: wlp2s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500
Vid enkel nätverksfelsökning är det viktigaste att titta efter frasen state UP på första raden för gränssnittet, vilket visar att det är aktiverat, samt förekomsten av en giltig IP-adress i fältet inet på tredje raden. På system som använder DHCP (Dynamic Host Configuration Protocol) bekräftar en giltig IP-adress i detta fält att DHCP fungerar.
Därefter routingtabellen:
[me@linuxbox ~]$ ip route show
default via 192.168.1.1 dev enp1s0 proto dhcp src 192.168.1.223
169.254.0.0/16 dev enp1s0 scope link metric 1000
192.168.1.0/24 dev enp1s0 proto kernel scope link src 192.168.1.223 metric 100
I detta enkla exempel ser vi en typisk routingtabell för en klientmaskin i ett lokalt nätverk (LAN) bakom en brandvägg/router. IP-adresser som slutar på noll syftar på nätverk snarare än enskilda värdar, så denna destination betyder att vilken värd som helst på det lokala nätverket kan nås direkt. Vi ser två nätverk i listan: 169.254.0.0 och 192.168.1.0.
ip [-options] object [command]
Nätverket 192.168.1.0 är vårt lokala nätverk, men vad är då 169.254.0.0? Jo, det är ett nätverkstrick som kallas APIPA (Automatic Private IP Addressing). Det används för att automatiskt tilldela en IP-adress när ingen DHCP-server finns tillgänglig.
28.2. Överföra filer över nätverk
28.2.1. ftp
ftp har fått sitt namn från File Transfer Protocol. Historiskt sett var det ett av de främsta sätten att överföra filer över internet, men det är inte säkert eftersom användarnamn och lösenord skickas i klartext.
En exempelsession med en anonym FTP-server kan se ut så här:
[me@linuxbox ~]$ ftp fileserver
Connected to fileserver.localdomain.
Name (fileserver:me): anonymous
Password:
ftp> cd pub/cd_images/Ubuntu-24.04
ftp> ls
ftp> lcd Desktop
ftp> get ubuntu-24.04-desktop-amd64.iso
ftp> bye
| Kommando | Beskrivning |
|---|---|
|
Anslut till FTP-servern |
|
Anonymt inloggningsnamn. |
|
Byt till en fjärrkatalog. |
|
Lista fjärrkatalogen. |
|
Ändra den lokala arbetskatalogen till |
|
Hämta den angivna filen. |
|
Avsluta sessionen. |
28.2.2. lftp - en bättre ftp
lftp är ett förbättrat FTP-klientprogram.
28.2.3. curl - överför en URL
Ett annat populärt program för filöverföring är curl. Den mest grundläggande användningen ser ut så här:
[me@linuxbox ~]$ curl https://linuxcommand.org
Vi anger en URL och curl hämtar den första sidan från URL:en och skriver ut den till standardutdata. Flera URL:er kan anges.
| Flagga | Beskrivning |
|---|---|
|
Skicka utdata till angiven fil. |
|
Använd fjärrfilnamnet lokalt. |
|
Dölj förloppsindikatorn och felmeddelanden. |
|
Ange kombination av användarnamn och lösenord. |
|
Visa utförliga meddelanden. |
28.2.4. wget - icke-interaktiv nätverkshämtare
Ett annat kommandoradsprogram för filhämtning är wget. Det är användbart för att hämta innehåll från både webb- och FTP-platser. Enstaka filer, flera filer och till och med hela webbplatser kan hämtas. För att hämta den första sidan från linuxcommand.org kan vi göra så här:
[me@linuxbox ~]$ wget http://linuxcommand.org/index.php
Programmets många flaggor gör det möjligt för wget att hämta rekursivt, hämta filer i bakgrunden (så att du kan logga ut men ändå fortsätta hämtningen) och slutföra hämtningen av en fil som bara delvis laddats ned. Dessa funktioner är väl dokumenterade i dess ovanligt bra manualsida.
28.3. Säker kommunikation med fjärrvärdar
I många år har Unix-liknande operativsystem kunnat administreras på distans via nätverk. Förr i tiden, innan internet blev allmänt spritt, fanns det ett par populära program för att logga in på fjärrvärdar. Dessa var rlogin och telnet. Dessa program lider dock av samma ödesdigra brist som ftp: de överför all kommunikation (inklusive inloggningsnamn och lösenord) i klartext. Därför är de helt olämpliga att använda i interneteran.
28.3.1. ssh
För att lösa detta problem utvecklades ett nytt protokoll som kallas Secure Shell (SSH). SSH löser två grundläggande problem vid säker kommunikation med en fjärrvärd.
-
Det verifierar att fjärrvärden verkligen är den den utger sig för att vara (och förhindrar därmed så kallade man-in-the-middle-attacker).
-
Det krypterar all kommunikation mellan den lokala och den fjärranslutna värden.
SSH består av två delar. En SSH-server körs på fjärrvärden och lyssnar efter inkommande anslutningar, som standard på port 22, medan en SSH-klient används på det lokala systemet för att kommunicera med fjärrservern.
[me@linuxbox ~]$ ssh remote-sys
The authenticity of host 'remote-sys (192.168.1.4)' can't be established.
Are you sure you want to continue connecting (yes/no)?
De flesta Linuxdistributioner levereras med en SSH-implementation som heter OpenSSH från OpenBSD-projektet. Vissa distributioner inkluderar både klient- och serverpaketen som standard, medan andra bara levererar klienten. För att ett system ska kunna ta emot fjärranslutningar måste det ha paketet OpenSSH-server installerat, konfigurerat och i drift, och om systemet kör bakom eller utgör en brandvägg måste det tillåta inkommande nätverksanslutningar på TCP-port 22.
[me@linuxbox ~]$ ssh bob@remote-sys
bob@remote-sys's password:
[bob@remote-sys ~]$
Tips: Om du inte har något fjärrsystem att ansluta till men ändå vill prova exemplen, se till att paketet OpenSSH-server är installerat på ditt system och använd localhost som namn på fjärrvärden. Då skapar din maskin nätverksanslutningar till sig själv.
SSH-klientprogrammet som används för att ansluta till fjärr-SSH-servrar heter, passande nog, ssh. För att ansluta till en fjärrvärd med namnet remote-sys använder vi klientprogrammet ssh så här:
[me@linuxbox ~]$ ssh remote-sys free
[me@linuxbox ~]$ ssh remote-sys 'ls *' > dirlist.txt
[me@linuxbox ~]$ ssh remote-sys 'ls * > dirlist.txt'
Första gången anslutningen försöks visas ett meddelande om att fjärrvärdens äkthet inte kan fastställas. Det beror på att klientprogrammet aldrig tidigare har sett denna fjärrvärd. För att godta fjärrvärdens autentiseringsuppgifter skriver vi yes när vi får frågan. När anslutningen väl har upprättats ombeds användaren att ange lösenord:
När lösenordet har angetts korrekt får vi skalprompten från fjärrsystemet. Den fjärranslutna skalsessionen fortsätter tills användaren skriver kommandot exit vid fjärrskalets prompt, vilket stänger fjärranslutningen. Därefter återupptas den lokala skalsessionen och den lokala prompten visas igen.
28.3.2. scp och sftp
Paketet OpenSSH innehåller också två program som kan använda en SSH-krypterad tunnel för att kopiera filer över nätverket. Det första, scp (secure copy), används ungefär som det välbekanta programmet cp för att kopiera filer. Den mest påtagliga skillnaden är att sökvägen till källan eller målet kan föregås av namnet på en fjärrvärd, följt av ett kolon. Om vi till exempel vill kopiera ett dokument med namnet document.txt från vår hemkatalog på fjärrsystemet remote-sys till den aktuella arbetskatalogen på vårt lokala system, kan vi göra så här:
[me@linuxbox ~]$ scp remote-sys:document.txt .
[me@linuxbox ~]$ scp bob@remote-sys:document.txt .
Programmet sftp fungerar på ett liknande sätt, förutom att det är interaktivt och tillhandahåller ett kommandointerface som liknar det gamla ftp-programmet.
28.3.3. Tunneling med SSH
SSH kan också tunnla andra typer av trafik, inklusive X Window System-trafik med ssh -X eller ssh -Y.
28.3.4. En SSH-klient för Windows?
Anta att du sitter vid en Windowsdator men behöver logga in på din Linuxserver och få lite riktigt arbete gjort; vad gör du? Skaffar ett SSH-klientprogram till din Windowsdator, förstås! Det finns ett antal sådana. Det populäraste är förmodligen PuTTY av Simon Tatham och hans team. PuTTY visar ett terminalfönster och låter en Windowsanvändare öppna en SSH-session (eller telnetsession) mot en fjärrvärd. Programmet tillhandahåller också motsvarigheter till scp och sftp.
PuTTY finns på http://www.chiark.greenend.org.uk/~sgtatham/putty/
28.4. Sammanfattning
I detta kapitel har vi gått igenom några av de nätverksverktyg som finns på de flesta Linuxsystem. Eftersom Linux används så brett i servrar och nätverksapparater finns det många fler som kan läggas till genom installation av ytterligare programvara. Men även med denna grunduppsättning verktyg går det att utföra många nyttiga nätverksrelaterade uppgifter.
28.5. Vidare läsning
-
Linux Network Administrator’s Guide:
-
Nätverksgrunder på Wikipedia:
-
curl-handledning: -
Förklaring av APIPA:
29. 17 - Söka efter filer
När vi har vandrat runt i vårt Linuxsystem har en sak blivit smärtsamt tydlig: ett typiskt Linuxsystem har väldigt många filer! Det väcker frågan: "Hur hittar vi saker?" Vi vet redan att Linuxfilsystemet är välorganiserat enligt konventioner som har förts vidare från en generation Unix-liknande system till nästa, men det stora antalet filer kan ändå vara ett skrämmande problem.
I det här kapitlet ska vi titta på två verktyg som används för att hitta filer i ett system.
-
locate- hitta filer efter namn. -
find- sök efter filer i en kataloghierarki. -
xargs- bygg och kör kommandorader från standardindata. -
touch- ändra filtider. -
stat- visa fil- eller filsystemstatus.
Vi ska också titta på ett kommando som ofta används tillsammans med filsökningskommandon för att bearbeta den resulterande fillistan.
29.1. locate - hitta filer på det enkla sättet
Programmet locate gör en snabb databassökning i sökvägar och skriver sedan ut varje namn som matchar en given delsträng. Anta till exempel att vi vill hitta alla program vars namn börjar med zip. Eftersom vi letar efter program kan vi anta att namnet på katalogen som innehåller programmen slutar med bin/. Vi kan därför prova att använda locate så här för att hitta våra filer:
[me@linuxbox ~]$ locate bin/zip
/usr/bin/zip
/usr/bin/zipcloak
/usr/bin/zipgrep
/usr/bin/zipinfo
/usr/bin/zipnote
/usr/bin/zipsplit
locate söker i sin databas med sökvägar och skriver ut alla som innehåller strängen bin/zip.
[me@linuxbox ~]$ locate zip | grep bin
/bin/bunzip2
/bin/bzip2
/usr/bin/unzip
/usr/bin/zip
Om sökkravet inte är så enkelt kan vi kombinera locate med andra verktyg, till exempel grep, för att skapa mer intressanta sökningar.
29.1.1. Var kommer locate-databasen ifrån?
Locate-databasen uppdateras vanligtvis dagligen via ett cron-jobb.
29.2. find - hitta filer på det svåra sättet
Medan programmet locate kan hitta en fil enbart utifrån dess namn söker programmet find i en given katalog (och dess underkataloger) efter filer baserat på en rad olika attribut. Vi ska tillbringa mycket tid med find, eftersom det har många intressanta funktioner som vi kommer att se om och om igen när vi senare börjar gå igenom programmeringsbegrepp.
I sin enklaste användning får find ett eller flera katalognamn att söka i. För att till exempel skriva ut en lista över vår hemkatalog kan vi använda detta:
[me@linuxbox ~]$ find ~
[me@linuxbox ~]$ find ~ | wc -l
47068
På de flesta aktiva användarkonton ger detta en lång lista. Eftersom listan skickas till standardutdata kan vi leda den vidare till andra program. Låt oss använda wc för att räkna antalet filer.
29.2.1. Tester
Anta att vi vill ha en lista över kataloger från vår sökning. För att göra det kan vi lägga till följande test:
[me@linuxbox ~]$ find ~ -type d | wc -l
1695
Genom att lägga till testet -type d begränsas sökningen till kataloger. På motsvarande sätt hade vi kunnat begränsa sökningen till vanliga filer med detta test:
[me@linuxbox ~]$ find ~ -type f | wc -l
38737
| Filtyp | Beskrivning |
|---|---|
|
Blockspecial enhetsfil. |
|
Teckenspecial enhetsfil. |
|
Katalog. |
|
Vanlig fil. |
|
Symbolisk länk. |
[me@linuxbox ~]$ find ~ -type f -name "*.JPG" -size +1M | wc -l
840
| Tecken | Enhet |
|---|---|
|
512-byte-block. |
|
Byte. |
|
2-byte-ord. |
|
Kilobyte. |
|
Megabyte. |
|
Gigabyte. |
Vi kan också söka efter filstorlek och filnamn genom att lägga till några fler tester. Låt oss leta efter alla vanliga filer som matchar jokerteckensmönstret *.JPG och som är större än 1 MB.
| Test | Beskrivning |
|---|---|
|
Matchar filer eller kataloger vars innehåll eller attribut ändrades för |
|
Matchar objekt som ändrats mer nyligen än |
|
Matchar objekt vars innehåll eller attribut ändrades för |
|
Matchar tomma filer och kataloger. |
|
Matchar filer eller kataloger som tillhör en grupp. |
|
Som |
|
Matchar filer med inodnummer |
|
Matchar filer eller kataloger vars innehåll ändrades för |
|
Matchar filer eller kataloger vars innehåll ändrades för |
|
Matchar filer och kataloger med det angivna jokerteckensmönstret. |
|
Matchar filer och kataloger som är nyare än |
|
Matchar filer och kataloger utan giltig ägare. |
|
Matchar filer och kataloger utan giltig grupp. |
|
Matchar filer eller kataloger med de angivna rättigheterna. |
|
Matchar filer som delar samma inod som |
|
Matchar filer med storlek |
|
Matchar filer av typ |
|
Matchar filer eller kataloger som tillhör en användare. |
29.2.2. Operatorer
Även med alla tester som find erbjuder kan vi ändå behöva ett bättre sätt att beskriva de logiska relationerna mellan testerna. Tänk till exempel om vi behöver avgöra om alla filer och underkataloger i en katalog har säkra rättigheter. Då skulle vi behöva leta efter alla filer med rättigheter som inte är 0600 och alla kataloger med rättigheter som inte är 0700. Som tur är erbjuder find ett sätt att kombinera tester med logiska operatorer för att skapa mer komplexa logiska relationer. För att uttrycka detta test kan vi göra så här:
[me@linuxbox ~]$ find ~ \( -type f -not -perm 0600 \) -or \( -type d -not -perm 0700 \)
| Operator | Beskrivning |
|---|---|
|
Matchar om testerna på båda sidor är sanna. |
|
Matchar om ett test på någon av sidorna är sant. |
|
Matchar om det efterföljande testet är falskt. |
|
Grupperar tester och operatorer tillsammans. |
| Resultat av expr1 | Operator | expr2 utförs… |
|---|---|---|
True |
|
Alltid. |
False |
|
Aldrig. |
True |
|
Aldrig. |
False |
|
Alltid. |
29.2.3. Fördefinierade åtgärder
Nu ska vi få något gjort! Att ha en lista över resultat från vårt find-kommando är nyttigt, men det vi egentligen vill är att göra något med objekten i listan. Som tur är tillåter find att åtgärder utförs utifrån sökresultaten. Det finns en uppsättning fördefinierade åtgärder och flera sätt att tillämpa användardefinierade åtgärder. Vi börjar med några av de fördefinierade åtgärder som listas i tabell 17-6.
| Åtgärd | Beskrivning |
|---|---|
|
Tar bort den matchande filen. |
|
Utför motsvarigheten till |
|
Skriver ut den fullständiga sökvägen till den matchande filen. |
|
Avslutar när en matchning har hittats. |
[me@linuxbox ~]$ find ~ -type f -name '*.bak' -delete
Det borde kanske vara självklart att du ska vara extremt försiktig när du använder åtgärden -delete. Testa alltid kommandot först genom att byta ut -delete mot -print för att bekräfta sökresultaten.
29.2.4. Användardefinierade åtgärder
Förutom de fördefinierade åtgärderna kan vi också anropa godtyckliga kommandon. Det traditionella sättet att göra detta är med åtgärden -exec. Den fungerar så här:
-exec command {} ;
Här är command namnet på ett kommando, {} är en symbolisk representation av den aktuella sökvägen, och semikolonet är en nödvändig avgränsare som markerar slutet på kommandot. Här är ett exempel på hur -exec kan användas för att fungera ungefär som åtgärden -delete vi nyss diskuterade:
[me@linuxbox ~]$ find ~ -type f -name 'foo*' -ok ls -l '{}' ';'
Återigen gäller att eftersom klammerparenteserna och semikolonet har särskild betydelse för skalet måste de citeras eller maskeras.
29.2.5. Förbättra effektiviteten
När åtgärden -exec används startas en ny instans av det angivna kommandot varje gång en matchande fil hittas. Ibland kan det vara bättre att samla alla sökresultat och starta en enda instans av kommandot. I stället för att köra kommandona så här:
[me@linuxbox ~]$ find ~ -type f -name 'foo*' -exec ls -l '{}' +
29.2.6. Hantera filnamn med specialtecken
När filnamn innehåller specialtecken som blanksteg eller andra märkliga tecken, måste de hanteras försiktigt.
29.2.7. xargs
Kommandot xargs utför en intressant uppgift. Det tar emot indata från standardindata och omvandlar dem till en argumentlista för ett angivet kommando. I vårt exempel skulle vi använda det så här:
[me@linuxbox ~]$ find ~ -type f -name 'foo*' -print | xargs ls -l
Här ser vi hur utdata från kommandot find skickas vidare till xargs, som i sin tur bygger en argumentlista åt kommandot ls och sedan kör det.
[me@linuxbox ~]$ find ~ -iname '*.jpg' -print0 | xargs --null ls -l
29.3. Tillbaka till lekplatsen
Det är dags att använda find till något som är (nästan) praktiskt. Vi ska skapa en lekplats och prova en del av det vi har lärt oss.
[me@linuxbox ~]$ mkdir -p playground/dir-{001..100}
[me@linuxbox ~]$ touch playground/dir-{001..100}/file-{A..Z}
Först skapar vi en lekplats med många underkataloger och filer.
[me@linuxbox ~]$ find playground -type f -name 'file-A'
[me@linuxbox ~]$ find playground -type f -name 'file-A' | wc -l
100
Beundra kommandoradens kraft! Med dessa två rader skapade vi en katalog med namnet playground som innehåller 100 underkataloger, och i varje underkatalog finns 26 tomma filer. Försök göra det med ett grafiskt gränssnitt!
[me@linuxbox ~]$ touch playground/timestamp
[me@linuxbox ~]$ stat playground/timestamp
Metoden vi använde för att åstadkomma denna magi involverade ett välbekant kommando (mkdir), en exotisk skalexpansion (klammerparenteser) och ett nytt kommando, touch. Genom att kombinera mkdir med flaggan -p (som får mkdir att skapa överordnade kataloger för de angivna sökvägarna) och klammerexpansion kunde vi skapa 100 underkataloger.
[me@linuxbox ~]$ find playground -type f -name 'file-B' -exec touch '{}' ';'
[me@linuxbox ~]$ find playground -type f -newer playground/timestamp
Kommandot touch används vanligtvis för att sätta eller uppdatera filers åtkomst-, ändrings- och modifieringstider. Om filnamnsargumentet däremot avser en fil som inte finns, skapas en tom fil.
29.3.1. Flaggor
Till sist har vi flaggorna. Flaggor används för att styra omfattningen av en find-sökning. De kan användas tillsammans med andra tester och åtgärder när man bygger find-uttryck. Tabell 17-7 listar de vanligaste find-flaggorna.
| Flagga | Beskrivning |
|---|---|
|
Behandlar en katalogs filer före själva katalogen. |
|
Anger det maximala antalet nivåer som |
|
Anger det minsta antalet nivåer innan tester och åtgärder tillämpas. |
|
Korsar inte kataloger monterade på andra filsystem. |
|
Inaktiverar antaganden baserade på Unix-liknande filsystemsbeteende. |
29.4. Sammanfattning
Det är lätt att se att locate är lika enkelt som find är komplicerat. Båda har sina användningsområden. Ta dig tid att utforska de många funktionerna i find. Vid regelbunden användning kan det förbättra din förståelse för hur Linuxfilsystem fungerar.
29.5. Vidare läsning
-
Programmen
locate,updatedb,findochxargsingår alla i GNU-projektets paketfindutils. GNU-projektet tillhandahåller en webbplats med omfattande dokumentation på nätet, som är mycket bra och bör läsas om du använder dessa program i miljöer med höga säkerhetskrav. Observera dock att de flesta moderna Linuxdistributioner tar bortlocateochupdatedbfrån paketetfindutilsoch ersätter dem med nyare och snabbare versioner somplocate, vilka vanligtvis ingår i ett separat paket:
30. 18 - Arkivering och säkerhetskopiering
En av systemadministratörens viktigaste uppgifter är att hålla systemets data säkra. Ett sätt att göra detta är att ta säkerhetskopior av systemets filer i rätt tid. Även om vi inte är systemadministratörer är det ofta nyttigt att kopiera saker och flytta stora samlingar filer från plats till plats och från enhet till enhet.
I det här kapitlet ska vi titta på flera vanliga program som används för att hantera filsamlingar. Det här är filkomprimeringsprogrammen:
-
gzip- komprimera eller expandera filer. -
bzip2- en filkomprimerare med blocksortering. -
Det här är arkiveringsprogrammen:
-
tar- verktyg för bandarkivering. -
zip- paketera och komprimera filer.
Och det här är filsynkroniseringsprogrammet:
-
rsync- fjärrsynkronisering av filer och kataloger.
30.1. Komprimera filer
Genom hela datorhistorien har det pågått en kamp för att få in så mycket data som möjligt på så liten yta som möjligt, vare sig denna yta har varit minne, lagringsenheter eller nätverksbandbredd. Många av de datatjänster vi i dag tar för givna, såsom mobiltelefoni, högupplöst tv eller bredbandsinternet, har sin existens att tacka effektiva tekniker för datakomprimering.
Datakomprimering är processen att ta bort överflödig information ur data. Låt oss tänka oss ett påhittat exempel. Anta att vi hade en helt svart bildfil med måtten 100 bildpunkter gånger 100 bildpunkter. I termer av datalagring (om vi antar 24 bitar, alltså 3 byte per bildpunkt) skulle bilden uppta 30 000 byte lagringsutrymme.
En bild som bara består av en enda färg innehåller helt redundant data. Om vi var smarta skulle vi kunna koda datat på ett sådant sätt att vi helt enkelt beskrev att vi har ett block med 10 000 svarta bildpunkter. Så i stället för att lagra ett datablock som innehåller 30 000 nollor (svart representeras vanligen som noll i bildfiler) skulle vi kunna komprimera datat till talet 10 000, följt av en nolla för att representera vårt data. En sådan datakomprimeringsmetod kallas run-length encoding och är en av de mest grundläggande komprimeringsteknikerna. Dagens tekniker är mycket mer avancerade och komplexa, men grundmålet är detsamma - att bli av med redundant data.
Komprimeringsalgoritmer (de matematiska tekniker som används för att utföra komprimeringen) faller i två huvudkategorier.
30.1.1. Var inte kompressivt tvångsmässigt
Komprimering är användbart men inte alltid nödvändigt.
30.1.2. gzip
Programmet gzip används för att komprimera en eller flera filer. När det körs ersätter det originalfilen med en komprimerad version av originalet. Det motsvarande programmet gunzip används för att återställa komprimerade filer till deras ursprungliga, okomprimerade form. Här är ett exempel:
[me@linuxbox ~]$ ls -l /etc > foo.txt
[me@linuxbox ~]$ ls -l foo.*
-rw-r--r-- 1 me me 15738 2025-10-14 07:15 foo.txt
[me@linuxbox ~]$ gzip foo.txt
[me@linuxbox ~]$ ls -l foo.*
-rw-r--r-- 1 me me 3230 2025-10-14 07:15 foo.txt.gz
[me@linuxbox ~]$ gunzip foo.txt
[me@linuxbox ~]$ ls -l foo.*
-rw-r--r-- 1 me me 15738 2025-10-14 07:15 foo.txt
| Flagga | Lång flagga | Beskrivning |
|---|---|---|
|
|
Skriv utdata till standardutdata och behåll originalfilerna. |
|
|
Packa upp. |
|
|
Tvinga komprimering även om en komprimerad version redan finns. |
|
|
Visa användningsinformation. |
|
|
Visa komprimeringsstatistik för varje fil. |
|
|
Komprimera filer i kataloger rekursivt. |
|
|
Testa integriteten hos en komprimerad fil. |
|
|
Visa detaljerade meddelanden under komprimering. |
|
Ställ in komprimeringsnivå från |
I detta exempel skapar vi en textfil med namnet foo.txt från en kataloglistning. Därefter kör vi gzip, som ersätter originalfilen med en komprimerad version med namnet foo.txt.gz. I kataloglistningen av foo.* ser vi att originalfilen har ersatts med den komprimerade versionen och att den komprimerade versionen är ungefär en femtedel så stor som originalet. Vi kan också se att den komprimerade filen har samma rättigheter och tidsstämpel som originalet.
[me@linuxbox ~]$ gzip foo.txt
[me@linuxbox ~]$ gzip -tv foo.txt.gz
foo.txt.gz:
OK
[me@linuxbox ~]$ gzip -d foo.txt.gz
Därefter kör vi programmet gunzip för att packa upp filen. Efteråt ser vi att den komprimerade versionen av filen har ersatts av originalet, återigen med rättigheter och tidsstämpel bevarade.
[me@linuxbox ~]$ ls -l /etc | gzip > foo.txt.gz
[me@linuxbox ~]$ gunzip -c foo.txt | less
[me@linuxbox ~]$ zcat foo.txt.gz | less
30.1.3. bzip2
Programmet bzip2, av Julian Seward, liknar gzip men använder en annan komprimeringsalgoritm som ger högre komprimeringsgrad på bekostnad av komprimeringshastighet. I de flesta avseenden fungerar det på samma sätt som gzip. En fil som komprimerats med bzip2 känns igen på ändelsen .bz2.
[me@linuxbox ~]$ ls -l /etc > foo.txt
[me@linuxbox ~]$ bzip2 foo.txt
[me@linuxbox ~]$ ls -l foo.txt.bz2
[me@linuxbox ~]$ bunzip2 foo.txt.bz2
Som vi ser kan bzip2 användas på samma sätt som gzip. Alla flaggor (utom r) som vi tog upp för gzip stöds också i bzip2. Observera dock att flaggan för komprimeringsnivå (-number) har en något annan betydelse i bzip2.
bzip2 levereras med bunzip2 och bzcat för uppackning av filer. bzip2 levereras också med programmet bzip2recover, som försöker återställa skadade .bz2-filer.
30.2. Arkivera filer
En vanlig filhanteringsuppgift som ofta används tillsammans med komprimering är arkivering. Arkivering är processen att samla ihop många filer och bunta samman dem till en enda stor fil. Arkivering görs ofta som en del av systemsäkerhetskopiering. Det används också när gamla data flyttas från ett system till någon form av långtidslagring.
30.2.1. tar
I den Unix-liknande programvaruvärlden är programmet tar det klassiska verktyget för att arkivera filer. Namnet, en förkortning av "tape archive", avslöjar dess rötter som ett verktyg för att skapa säkerhetskopior på band. Även om det fortfarande används för denna traditionella uppgift är det lika skickligt på andra lagringsenheter. Vi ser ofta filnamn som slutar med ändelsen .tar eller .tgz, vilket anger ett "rent" tar-arkiv respektive ett gzip-komprimerat arkiv. Ett tar-arkiv kan bestå av en grupp separata filer, en eller flera kataloghierarkier eller en blandning av båda. Kommandosyntaxen ser ut så här:
tar mode[options] pathname...
Här är mode ett av följande driftlägen som listas i tabell 18-2 (här visas bara en delmängd; se manualsidan för tar för en fullständig lista).
| Läge | Beskrivning |
|---|---|
|
Skapa ett arkiv. |
|
Extrahera ett arkiv. |
|
Lägg till angivna sökvägar i slutet av ett arkiv. Om arkivet inte finns skapas det. |
|
Lista innehållet i ett arkiv. |
tar använder ett lite märkligt sätt att uttrycka flaggor, så vi behöver några exempel för att visa hur det fungerar. Låt oss först återskapa vår lekplats från föregående kapitel.
Därefter skapar vi ett tar-arkiv av hela playground.
[me@linuxbox ~]$ mkdir -p playground/dir-{001..100}
[me@linuxbox ~]$ touch playground/dir-{001..100}/file-{A..Z}
[me@linuxbox ~]$ tar cf playground.tar playground
[me@linuxbox ~]$ tar tf playground.tar
[me@linuxbox ~]$ tar tvf playground.tar
Detta kommando skapar ett tar-arkiv med namnet playground.tar som innehåller hela kataloghierarkin playground. Vi kan se att läget och flaggan f, som används för att ange namnet på tar-arkivet, kan skrivas ihop och inte behöver något inledande bindestreck. Observera dock att läget alltid måste anges först, före alla flaggor.
För att visa innehållet i arkivet kan vi göra så här:
För en mer detaljerad lista kan vi lägga till flaggan v (verbose).
Nu packar vi upp playground på en ny plats. Det gör vi genom att skapa en ny katalog med namnet foo, byta till den katalogen och packa upp tar-arkivet.
[me@linuxbox ~]$ mkdir foo
[me@linuxbox ~]$ cd foo
[me@linuxbox foo]$ tar xf ../playground.tar
[me@linuxbox foo]$ ls
playground
tar xf archive.tar pathname
[me@linuxbox ~]$ find playground -name 'file-A' -exec tar rf playground.tar '{}' '+'
[me@linuxbox ~]$ find playground -name 'file-A' | tar cf - --files-from=- | gzip > playground.tgz
[me@linuxbox ~]$ find playground -name 'file-A' | tar czf playground.tgz -T -
[me@linuxbox ~]$ find playground -name 'file-A' | tar cjf playground.tbz -T -
[me@linuxbox ~]$ mkdir remote-stuff
[me@linuxbox ~]$ cd remote-stuff
[me@linuxbox remote-stuff]$ ssh remote-sys 'tar cf - Documents' | tar xf -
[me@linuxbox remote-stuff]$ ls
Documents
30.2.2. zip
Programmet zip är både ett komprimeringsverktyg och ett arkiveringsprogram. Filformatet som programmet använder är välkänt för Windowsanvändare, eftersom det läser och skriver .zip-filer. I Linux är däremot gzip det dominerande komprimeringsprogrammet, med bzip2 som en stark tvåa.
zip options zipfile file...
I sin mest grundläggande form används zip så här:
[me@linuxbox ~]$ zip -r playground.zip playground
[me@linuxbox ~]$ cd foo
[me@linuxbox foo]$ unzip ../playground.zip
För att till exempel skapa ett zip-arkiv av vår lekplats skulle vi göra så här:
Om vi inte inkluderar flaggan -r för rekursion lagras bara katalogen playground (men inget av dess innehåll). Även om ändelsen .zip läggs till automatiskt tar vi med den här för tydlighetens skull.
[me@linuxbox ~]$ unzip -l playground.zip playground/dir-087/file-Z
[me@linuxbox foo]$ unzip ../playground.zip playground/dir-087/file-Z
När zip skapar arkivet visar det normalt en serie meddelanden som ser ut ungefär så här:
[me@linuxbox ~]$ find playground -name "file-A" | zip -@ file-A.zip
[me@linuxbox ~]$ ls -l /etc/ | zip ls-etc.zip
[me@linuxbox ~]$ unzip -p ls-etc.zip | less
Dessa meddelanden visar status för varje fil som läggs till i arkivet. zip lägger till filer i arkivet med en av två lagringsmetoder: antingen "lagrar" det filen utan komprimering, som visas här, eller så "deflaterar" det filen och komprimerar den därmed. Det numeriska värde som visas efter lagringsmetoden anger hur stor komprimering som uppnåtts. Eftersom vår playground bara innehåller tomma filer sker ingen komprimering av dess innehåll.
30.3. Synkronisera filer och kataloger
En vanlig strategi för att upprätthålla en säkerhetskopia av ett system är att hålla en eller flera kataloger synkroniserade med en annan katalog (eller andra kataloger) som finns antingen på det lokala systemet (vanligen på någon typ av flyttbar lagringsenhet) eller på ett fjärrsystem. Vi kan till exempel ha en lokal kopia av en webbplats under utveckling och då och då synkronisera den med den "skarpa" kopian på en fjärransluten webbserver.
Det föredragna Unix-liknande verktyget för detta ändamål är rsync.
rsync options source destination
Där source och destination kan vara lokala sökvägar eller fjärrplatser i formen [user@]host:path.
Exempel på lokal synkronisering:
[me@linuxbox ~]$ rm -rf foo/*
[me@linuxbox ~]$ rsync -av playground foo
[me@linuxbox ~]$ touch playground/dir-099/file-Z
[me@linuxbox ~]$ rsync -av playground foo
rsync kopierar bara skillnaderna mellan källa och destination.
Ett avslutande snedstreck på källsökvägen ändrar beteendet:
[me@linuxbox ~]$ rsync source destination
[me@linuxbox ~]$ rsync source/ destination
Det första kommandot kopierar själva katalogen; det andra kopierar bara dess innehåll.
Praktiskt exempel på säkerhetskopiering:
[me@linuxbox ~]$ mkdir /media/BigDisk/backup
[me@linuxbox ~]$ sudo rsync -av --delete /etc /home /usr/local /media/BigDisk/backup
Vi skulle till och med kunna skapa ett alias:
alias backup='sudo rsync -av --delete /etc /home /usr/local /media/BigDisk/backup'
30.3.1. Använda rsync över ett nätverk
rsync kan också arbeta över ett nätverk med hjälp av ssh:
[me@linuxbox ~]$ sudo rsync -av --delete --rsh=ssh /etc /home /usr/local remote-sys:/backup
Det kan också synkronisera mot en rsync-daemon:
[me@linuxbox ~]$ mkdir fedora-devel
[me@linuxbox ~]$ rsync -av --delete rsync://archive.linux.duke.edu/fedora/linux/development/rawhide/Everything/x86_64/os/ fedora-devel
30.4. Sammanfattning
Vi har tittat på de vanliga komprimerings- och arkiveringsprogram som används i Linux och andra Unix-liknande operativsystem. För arkivering av filer är kombinationen tar/gzip den föredragna metoden på Unix-liknande system, medan zip/unzip används för samverkan med Windowssystem. Slutligen tittade vi på programmet rsync (en personlig favorit), som är mycket praktiskt för effektiv synkronisering av filer och kataloger mellan system.
30.5. Vidare läsning
-
Manualsidorna för alla kommandon som diskuteras här är ganska tydliga och innehåller användbara exempel. Dessutom har GNU-projektet en bra handledning på nätet för sin version av
tar. Den finns här:
31. 19 - Reguljära uttryck
I de närmaste kapitlen ska vi titta på verktyg som används för att manipulera text. Som vi har sett spelar textdata en viktig roll i alla Unix-liknande system, såsom Linux. Men innan vi fullt ut kan uppskatta alla funktioner som dessa verktyg erbjuder måste vi först undersöka en teknik som ofta förknippas med de mest avancerade användningarna av dem - reguljära uttryck.
31.1. Vad är reguljära uttryck?
Enkelt uttryckt är reguljära uttryck symboliska notationer som används för att identifiera mönster i text. På vissa sätt liknar de skalets metod att matcha fil- och sökvägsnamn med jokertecken, men i mycket större skala. Reguljära uttryck stöds av många kommandoradsverktyg och av de flesta programspråk för att underlätta lösningen av problem med textmanipulering. För att göra det hela ännu mer förvirrande är dock inte alla reguljära uttryck likadana; de varierar något från verktyg till verktyg och från programspråk till programspråk. I vår genomgång begränsar vi oss till reguljära uttryck så som de beskrivs i POSIX-standarden (vilket täcker de flesta kommandoradsverktyg), i motsats till många programspråk (framför allt Perl), som använder något större och rikare uppsättningar notationer.
Det huvudsakliga program vi ska använda för att arbeta med reguljära uttryck är vår gamle vän grep. Namnet "grep" kommer faktiskt från uttrycket "global regular expression print", så vi kan se att grep har något med reguljära uttryck att göra. I grunden söker grep i textfiler efter förekomster av text som matchar ett angivet reguljärt uttryck och skriver ut varje rad som innehåller en träff till standardutdata.
31.2. grep
Hittills har vi använt grep med fasta strängar, så här:
[me@linuxbox ~]$ ls /usr/bin | grep zip
Detta listar alla filer i katalogen /usr/bin vars namn innehåller delsträngen zip.
grep [options] regex [file...]
| Flagga | Lång flagga | Beskrivning |
|---|---|---|
|
|
Ignorera skiftläge. |
|
|
Skriv ut rader som inte innehåller en träff. |
|
|
Skriv ut antalet matchande rader. |
|
|
Skriv bara ut namnen på filer som innehåller träffar. |
|
|
Skriv bara ut namnen på filer som inte innehåller träffar. |
|
|
Sätt radnummer framför varje matchande rad. |
|
|
Undertryck filnamn vid sökning i flera filer. |
|
|
Undertryck all utdata. |
Programmet grep tar emot flaggor och argument på detta sätt, där regex är ett reguljärt uttryck:
[me@linuxbox ~]$ ls /bin > dirlist-bin.txt
[me@linuxbox ~]$ ls /usr/bin > dirlist-usr-bin.txt
[me@linuxbox ~]$ ls /sbin > dirlist-sbin.txt
[me@linuxbox ~]$ ls /usr/sbin > dirlist-usr-sbin.txt
[me@linuxbox ~]$ grep bzip dirlist*.txt
dirlist-bin.txt:bzip2
dirlist-bin.txt:bzip2recover
[me@linuxbox ~]$ grep -l bzip dirlist*.txt
dirlist-bin.txt
[me@linuxbox ~]$ grep -L bzip dirlist*.txt
dirlist-sbin.txt
dirlist-usr-bin.txt
dirlist-usr-sbin.txt
31.3. Metatecken och bokstavliga tecken
Även om det kanske inte verkar uppenbart har våra grep-sökningar hela tiden använt reguljära uttryck, om än mycket enkla sådana. Det reguljära uttrycket bzip tolkas som att en träff bara uppstår om raden i filen innehåller minst fyra tecken och att tecknen b, z, i och p någonstans på raden förekommer i just den ordningen, utan andra tecken emellan. Tecknen i strängen bzip är alla bokstavliga tecken, i betydelsen att de matchar sig själva. Förutom bokstavliga tecken kan reguljära uttryck också innehålla metatecken som används för att ange mer komplexa träffar. Metatecken i reguljära uttryck består av följande:
^ $ . [ ] { } - ? * + ( ) | \
Alla andra tecken betraktas som bokstavliga, även om tecknet omvänt snedstreck i några fall används för att skapa metasekvenser, liksom för att maskera metatecken så att de behandlas som bokstavliga tecken i stället för att tolkas som metatecken.
31.4. Vilket tecken som helst
Det första metatecken vi ska titta på är punkttecknet, som används för att matcha vilket tecken som helst. Om vi tar med det i ett reguljärt uttryck matchar det vilket tecken som helst i den teckenpositionen. Här är ett exempel:
[me@linuxbox ~]$ grep -h '.zip' dirlist*.txt
bunzip2
bzip2
gunzip
gzip
funzip
gpg-zip
preunzip
prezip
prezip-bin
unzip
unzipsfx
Vi sökte efter varje rad i våra filer som matchar det reguljära uttrycket .zip. Det finns ett par intressanta saker att lägga märke till i resultatet. Observera att programmet zip inte hittades. Det beror på att metatecknet punkt i vårt reguljära uttryck ökade den nödvändiga träfflängden till fyra tecken, där ett tecken måste komma före z. Om några filer i våra listor dessutom hade innehållit filändelsen .zip skulle även de ha matchats, eftersom punkttecknet i filändelsen också hade matchats av "vilket tecken som helst".
31.5. Ankare
Tecknen cirkumflex (^) och dollartecken ($) behandlas som ankare i reguljära uttryck. Det betyder att de gör att en träff bara sker om det reguljära uttrycket hittas i början av raden (^) eller i slutet av raden ($).
[me@linuxbox ~]$ grep -h '^zip' dirlist*.txt
zip
zipcloak
zipgrep
zipinfo
zipnote
zipsplit
[me@linuxbox ~]$ grep -h 'zip$' dirlist*.txt
gunzip
gzip
funzip
gpg-zip
preunzip
prezip
unzip
zip
[me@linuxbox ~]$ grep -h '^zip$' dirlist*.txt
zip
Här sökte vi i fillistan efter strängen zip placerad i början av raden, i slutet av raden och på en rad där den både står i början och i slutet (det vill säga ensam på raden). Observera att det reguljära uttrycket ^$ (en början och ett slut utan någonting emellan) kommer att matcha tomma rader.
31.5.1. Korsordshjälp
31.6. Hakparentesuttryck och teckenklasser
Förutom att matcha vilket tecken som helst på en viss position i vårt reguljära uttryck kan vi också matcha ett enda tecken ur en angiven uppsättning tecken genom att använda hakparentesuttryck. Med hakparentesuttryck kan vi ange en uppsättning tecken (inklusive tecken som annars skulle tolkas som metatecken) som ska matchas. I detta exempel använder vi en uppsättning med två tecken för att matcha varje rad som innehåller strängen bzip eller gzip:
[me@linuxbox ~]$ grep -h '[bg]zip' dirlist*.txt
bzip2
bzip2recover
gzip
31.6.1. Negation
Om det första tecknet i ett hakparentesuttryck är en cirkumflex (^) tolkas de återstående tecknen som en uppsättning tecken som inte får förekomma i den givna teckenpositionen. Vi gör det genom att ändra vårt tidigare exempel så här:
[me@linuxbox ~]$ grep -h '[^bg]zip' dirlist*.txt
bunzip2
gunzip
funzip
gpg-zip
preunzip
prezip
prezip-bin
unzip
unzipsfx
31.6.2. Traditionella teckenintervall
Om vi vill konstruera ett reguljärt uttryck som hittar varje fil i våra listor som börjar med en versal skulle vi kunna göra så här:
[me@linuxbox ~]$ grep -h '^[A-Z]' dirlist*.txt
MAKEDEV
ControlPanel
GET
HEAD
POST
X
X11
Xorg
31.6.3. POSIX-teckenklasser
De traditionella teckenintervallen är ett lättbegripligt och effektivt sätt att hantera problemet med att snabbt ange uppsättningar av tecken. Tyvärr fungerar de inte alltid. Även om vi hittills inte har stött på några problem med vår användning av grep, kan vi råka ut för problem med andra program.
| Teckenklass | Beskrivning |
|---|---|
|
Alfanumeriska tecken. |
|
Som |
|
Alfabetiska tecken. |
|
Mellanslag och tabb. |
|
ASCII-kontrolltecken. |
|
Siffrorna |
|
Synliga tecken. |
|
Gemener. |
|
Skiljetecken. |
|
Utskrivbara tecken. |
|
Blanktecken. |
|
Versaler. |
|
Hexadecimala siffror. |
31.6.4. Återgå till traditionell sorteringsordning
Ibland behöver man återgå till traditionell sorteringsordning.
I kapitel 4 tittade vi på hur jokertecken används för att utföra sökvägsexpansion. Där sade vi att teckenintervall kunde användas på nästan samma sätt som i reguljära uttryck, men här finns problemet:
[me@linuxbox ~]$ ls /usr/sbin/[[:upper:]]*
(Beroende på Linuxdistribution får vi en annan fillista, möjligen en tom lista. Detta exempel kommer från Ubuntu.) Detta kommando ger det förväntade resultatet - en lista med endast de filer vars namn börjar med en versal - men med detta kommando får vi ett helt annat resultat (bara en del av resultatlistan visas):
[me@linuxbox ~]$ locale
[me@linuxbox ~]$ export LANG=POSIX
31.7. POSIX grundläggande kontra utökade reguljära uttryck
Just när vi trodde att det inte kunde bli mer förvirrande upptäcker vi att POSIX också delar upp implementationer av reguljära uttryck i två typer: grundläggande reguljära uttryck (BRE) och utökade reguljära uttryck (ERE). De funktioner vi har gått igenom hittills stöds av varje program som är POSIX-kompatibelt och implementerar BRE. Vårt program grep är ett sådant program.
-
Vad är skillnaden mellan BRE och ERE? Det handlar om metatecken. Med BRE känns följande metatecken igen:
-
Alla andra tecken betraktas som bokstavliga. Med ERE läggs följande metatecken (och deras tillhörande funktioner) till:
Men (och det är här det roliga börjar) behandlas tecknen (, ), { och } som metatecken i BRE om de föregås av omvänt snedstreck, medan det i ERE är så att om ett metatecken föregås av omvänt snedstreck behandlas det som ett bokstavligt tecken. Alla märkligheter som dyker upp tar vi upp i de följande diskussionerna.
^ $ . [ ] *
Eftersom de funktioner vi nu ska gå igenom hör till ERE behöver vi använda en annan sorts grep. Traditionellt har detta gjorts med programmet egrep, men GNU-versionen av grep stöder också utökade reguljära uttryck när flaggan -E används.
( ) { } ? + |
31.8. Alternation
Den första av funktionerna i utökade reguljära uttryck som vi ska diskutera kallas alternation, vilket är den möjlighet som gör att en träff kan ske utifrån en uppsättning uttryck. Precis som ett hakparentesuttryck gör det möjligt att matcha ett enda tecken ur en uppsättning angivna tecken gör alternation det möjligt att matcha ur en uppsättning strängar eller andra reguljära uttryck.
[me@linuxbox ~]$ echo "AAA" | grep -E 'AAA|BBB'
AAA
[me@linuxbox ~]$ echo "BBB" | grep -E 'AAA|BBB'
BBB
[me@linuxbox ~]$ echo "CCC" | grep -E 'AAA|BBB'
För att demonstrera detta använder vi grep tillsammans med echo. Först provar vi en helt vanlig strängmatchning.
[me@linuxbox ~]$ grep -Eh '^(bz|gz|zip)' dirlist*.txt
31.9. Kvantifierare
Utökade reguljära uttryck stöder flera sätt att ange hur många gånger ett element ska matchas, vilket beskrivs i avsnitten som följer.
31.9.1. ? - matcha ett element noll eller en gång
Denna kvantifierare betyder i praktiken: "Gör det föregående elementet valfritt." Anta att vi vill kontrollera om ett telefonnummer är giltigt och att vi anser att ett telefonnummer är giltigt om det matchar någon av dessa två former, där n är en siffra:
[me@linuxbox ~]$ echo "(555) 123-4567" | grep -E '^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$'
[me@linuxbox ~]$ echo "555 123-4567" | grep -E '^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$'
31.9.2. * - matcha ett element noll eller flera gånger
[me@linuxbox ~]$ echo "This works." | grep -E '^[[:upper:]][[:upper:][:lower:] ]*\.'
[me@linuxbox ~]$ echo "This Works." | grep -E '^[[:upper:]][[:upper:][:lower:] ]*\.'
31.9.3. + - matcha ett element en eller flera gånger
[me@linuxbox ~]$ echo "This that" | grep -E '^([[:alpha:]]+ ?)+$'
[me@linuxbox ~]$ echo "a b c" | grep -E '^([[:alpha:]]+ ?)+$'
31.9.4. { } - matcha ett element ett bestämt antal gånger
| Angivelse | Betydelse |
|---|---|
|
Matcha exakt |
|
Matcha minst |
|
Matcha |
|
Matcha inte fler än |
Metatecknen { och } används för att uttrycka minsta och största antal nödvändiga träffar. De kan anges på fyra olika sätt, som visas i tabell 19-3.
[me@linuxbox ~]$ echo "(555) 123-4567" | grep -E '^\(?[0-9]{3}\)? [0-9]{3}-[0-9]{4}$'
[me@linuxbox ~]$ echo "555 123-4567" | grep -E '^\(?[0-9]{3}\)? [0-9]{3}-[0-9]{4}$'
31.10. Använda reguljära uttryck i praktiken
31.10.1. Validera en telefonlista med grep
I vårt tidigare exempel tittade vi på enskilda telefonnummer och kontrollerade att de var korrekt formaterade. Ett mer realistiskt scenario är att i stället kontrollera en lista med nummer, så låt oss skapa en sådan. Vi gör det genom att recitera en magisk besvärjelse på kommandoraden. Den kommer att vara magisk eftersom vi ännu inte har gått igenom de flesta av de kommandon som ingår, men oroa dig inte - vi kommer dit i senare kapitel. Här är besvärjelsen:
[me@linuxbox ~]$ for i in {1..10}; do echo "(${RANDOM:0:3}) ${RANDOM:0:3}-${RANDOM:0:4}" >> phonelist.txt; done
[me@linuxbox ~]$ grep -Ev '^\([0-9]{3}\) [0-9]{3}-[0-9]{4}$' phonelist.txt
31.10.2. Hitta fula filnamn med find
Kommandot find stöder ett test baserat på ett reguljärt uttryck. Det finns en viktig sak att tänka på när man använder reguljära uttryck i find jämfört med grep. Medan grep skriver ut en rad när raden innehåller en sträng som matchar ett uttryck kräver find att sökvägen matchar det reguljära uttrycket exakt. I följande exempel använder vi find med ett reguljärt uttryck för att hitta varje sökväg som innehåller något tecken som inte är medlem i följande mängd:
[me@linuxbox ~]$ find . -regex '.*[^-_./0-9a-zA-Z].*'
31.10.3. Söka efter filer med locate
Programmet locate stöder både grundläggande (--regexp) och utökade (-regex) reguljära uttryck. Med det kan vi utföra många av samma operationer som vi tidigare gjorde med våra dirlist-filer.
[me@linuxbox ~]$ locate --regex 'bin/(bz|gz|zip)'
31.10.4. Söka efter text med less och vim
less och vim delar båda samma metod för textsökning. Genom att trycka på tangenten / följd av ett reguljärt uttryck utförs en sökning. Om vi använder less för att visa vår fil phonelist.txt, så här:
[me@linuxbox ~]$ less phonelist.txt
och söker efter vårt valideringsuttryck, så här:
/^\([0-9]{3}\) [0-9]{3}-[0-9]{4}$
kommer less att markera de strängar som matchar, så att de ogiltiga blir lätta att upptäcka.
/([0-9]\{3\}) [0-9]\{3\}-[0-9]\{4\}
:hlsearch
31.11. Sammanfattning
I detta kapitel såg vi några av de många användningarna av reguljära uttryck. Vi kan hitta ännu fler om vi använder reguljära uttryck för att söka efter fler program som använder dem. Det kan vi göra genom att söka i manualsidorna.
31.12. Vidare läsning
-
Det finns många resurser på nätet för att lära sig reguljära uttryck, däribland olika handledningar och snabböversikter.
-
Dessutom har Wikipedia bra artiklar om följande bakgrundsämnen:
32. 20 - Textbehandling
Alla Unix-liknande operativsystem förlitar sig i hög grad på textfiler för datalagring. Därför är det logiskt att det finns många verktyg för att manipulera text. I det här kapitlet ska vi titta på program som används för att "skära och tärna" text. I nästa kapitel ska vi titta på mer textbehandling, med fokus på program som används för att formatera text för utskrift och annan mänsklig konsumtion.
I detta kapitel återser vi några gamla vänner och bekantar oss med några nya:
-
cat— sammanfoga filer och skriv till standardutdata. -
sort— sortera rader i textfiler. -
uniq— rapportera eller utelämna upprepade rader. -
cut— ta bort delar från varje rad i filer. -
paste— slå samman rader från filer. -
join— slå samman rader ur två filer utifrån ett gemensamt fält. -
tac— sammanfoga och skriv ut filer i omvänd ordning. -
rev— vänd teckenordningen i rader. -
comm— jämför två sorterade filer rad för rad. -
diff— jämför filer rad för rad. -
patch— tillämpa endiff-fil på ett original. -
tr— översätt eller ta bort tecken. -
sed— strömredigerare för att filtrera och omvandla text. -
aspell— interaktiv stavningskontroll.
32.1. Vad text används till
Så vad mer används text till? Det visar sig: väldigt mycket.
32.1.1. Dokument
Många skriver dokument i vanliga textformat. Det är lätt att se hur en liten textfil kan vara användbar för enkla anteckningar, men det går också utmärkt att skriva stora dokument i textformat. Ett populärt arbetssätt är att skriva ett stort dokument i textform och sedan bädda in ett märkspråk som beskriver hur det färdiga dokumentet ska formateras. Många vetenskapliga artiklar skrivs på detta sätt, eftersom Unix-baserade textbehandlingssystem hörde till de första som stödde den avancerade typografiska utformning som behövdes av skribenter inom tekniska ämnen. På senare år har Markdown blivit ett populärt märkspråk som använder vanlig text för dokumentformatering.
32.1.2. Webbsidor
Den vanligaste typen av elektroniskt dokument i världen är förmodligen webbsidan. Webbsidor är textdokument som använder antingen Hypertext Markup Language (HTML) eller Extensible Markup Language (XML) som märkspråk för att beskriva dokumentets visuella format.
32.1.3. E-post
E-post är i grunden ett textbaserat medium. Även bilagor som inte är text omvandlas till en textrepresentation för överföring. Vi kan se detta själva genom att hämta ett e-postmeddelande och sedan visa det i less. Då ser vi att meddelandet börjar med ett sidhuvud som beskriver meddelandets ursprung och den behandling det fick under färden, följt av meddelandets brödtext med dess innehåll.
32.1.4. Skrivarutdata
På Unix-liknande system skickas utdata som ska till en skrivare som vanlig text eller, om sidan innehåller grafik, omvandlas den vanligen till ett textbaserat sidbeskrivningsspråk som kallas PostScript, vilket sedan skickas till ett program som genererar de grafiska punkter som ska skrivas ut.
32.1.5. Programkällkod
Många av kommandoradsprogrammen i Unix-liknande system skapades för att stödja systemadministration och programutveckling, och textbehandlingsprogram är inget undantag. Många av dem är utformade för att lösa problem inom programutveckling. Orsaken till att textbehandling är viktig för programutvecklare är att all programvara börjar som text. Källkod, alltså den del av programmet som programmeraren faktiskt skriver, är alltid i textformat.
32.2. Återbesök hos gamla vänner
32.2.1. cat
cat har ett antal flaggor som är användbara för att visualisera text. Ett exempel är -A, som visar icke-utskrivbara tecken:
[me@linuxbox ~]$ cat > foo.txt
The quick brown fox jumps over the lazy dog.
[me@linuxbox ~]$ cat -A foo.txt
^IThe quick brown fox jumps over the lazy dog.$
Här representerar ^I ett tabbtecken, och det avslutande $ markerar radens verkliga slut.
Ibland vill vi veta om styrtecken är inbäddade i annars synlig text. De vanligaste fallen är tabbtecken, vagnreturer från MS-DOS-filer och rader som innehåller efterföljande blanksteg.
I exemplet ovan skapar vi filen genom att köra cat med omdirigerad utdata och skriva texten direkt, och avslutar sedan inmatningen med kbd:[Ctrl-d]. Flaggan -A avslöjar både det inledande tabbtecknet och radens verkliga slut.
32.2.2. MS-DOS-text kontra Unix-text
En anledning att använda cat -A är att upptäcka dolda vagnreturer från MS-DOS/Windows-textfiler, där rader avslutas med vagnretur plus radmatning i stället för enbart en Unix-radmatning.
cat har även flaggor som -n för radnumrering och -s för att undertrycka upprepade blankrader:
[me@linuxbox ~]$ cat > foo.txt
The quick brown fox
jumps over the lazy dog.
[me@linuxbox ~]$ cat -ns foo.txt
1 The quick brown fox
2
3 jumps over the lazy dog.
I det här exemplet innehåller vår testfil två textrader separerade av flera blankrader. Efter behandling med cat -ns har den extra blankraden tagits bort och de återstående raderna har numrerats. Även om detta inte är en stor omvandling är det ändå textbehandling.
32.2.3. sort
Programmet sort sorterar innehållet i standardindata eller i en eller flera filer som anges på kommandoraden och skickar resultatet till standardutdata. Med samma teknik som vi använde med cat kan vi demonstrera behandling av standardindata direkt från tangentbordet så här:
[me@linuxbox ~]$ sort > foo.txt
c
b
a
[me@linuxbox ~]$ cat foo.txt
a
b
c
Efter att ha skrivit kommandot matar vi in bokstäverna c, b och a, och trycker sedan Ctrl-d för att markera filslut. Därefter visar vi den resulterande filen och ser att raderna nu står i sorterad ordning. Eftersom sort kan ta emot flera filer kan det också slå samman flera försorterade filer till ett större sorterat resultat.
Det kan också slå samman flera filer:
sort file1.txt file2.txt file3.txt > final_sorted_list.txt
| Flagga | Lång flagga | Beskrivning |
|---|---|---|
|
|
Ignorera inledande blanksteg. |
|
|
Gör sorteringen skiftlägesokänslig. |
|
|
Sortera efter numeriskt värde. |
|
|
Sortera i omvänd ordning. |
|
|
Sortera på ett nyckelfält. |
|
|
Slå samman försorterade filer. |
|
|
Skriv utdata till en fil. |
|
|
Ange fältavgränsaren. |
Numerisk och omvänd sortering gör det enkelt att identifiera de största användarna av diskutrymme:
[me@linuxbox ~]$ du -s /usr/share/* | sort -nr | head
Genom att använda flaggorna -n och -r tillsammans producerar vi en omvänd numerisk sortering, där de största värdena visas först.
Vi kan också sortera på specifika fält:
[me@linuxbox ~]$ sort --key=1,1 --key=2n distros.txt
[me@linuxbox ~]$ sort -k 3.7nbr -k 3.1nbr -k 3.4nbr distros.txt
[me@linuxbox ~]$ sort -t ':' -k 7 /etc/passwd | head
Många användningar av sort involverar tabelldata. Om vi tänker i databastermer är varje rad en post som består av fält. Flaggan -k låter oss välja ett eller flera nyckelfält att sortera på. I sort --key=1,1 --key=2n distros.txt begränsas den första nyckeln till enbart fält 1, medan den andra nyckeln sorterar fält 2 numeriskt.
Datumexemplet är särskilt intressant. Genom att ange -k 3.7nbr -k 3.1nbr -k 3.4nbr sorterar vi först på år, sedan månad och sedan dag. Flaggan b undertrycker inledande blanksteg, n gör jämförelsen numerisk och r vänder ordningen så att den senaste utgåvan visas först.
När en fil använder en annan fältavgränsare, som kolontecknen i /etc/passwd, talar flaggan -t om för sort vilken avgränsare som ska användas.
32.2.4. uniq
uniq tar bort duplicerade intilliggande rader från sorterad indata.
[me@linuxbox ~]$ sort foo.txt | uniq
a
b
c
uniq tar bara bort intilliggande dublettrader, vilket är anledningen till att det nästan alltid kombineras med sort. Om vi kör uniq direkt på en osorterad fil kommer dublettrader som inte ligger intill varandra att finnas kvar.
| Flagga | Lång flagga | Beskrivning |
|---|---|---|
|
|
Lägg till antal förekomster före varje rad. |
|
|
Skriv bara ut upprepade rader. |
|
|
Ignorera |
|
|
Ignorera skiftläge vid jämförelser. |
|
|
Hoppa över |
|
|
Skriv bara ut unika rader. |
Exempel:
[me@linuxbox ~]$ sort foo.txt | uniq -c
2 a
2 b
2 c
Flaggan -c prefixar varje rad med antalet förekomster. Detta är ett vanligt sätt att sammanfatta upprepade värden i textdata.
32.3. Skära och tärna
32.3.1. cut
cut extraherar delar av texten ur varje rad.
| Flagga | Lång flagga | Beskrivning |
|---|---|---|
|
|
Extrahera teckenpositioner. |
|
|
Extrahera fält. |
|
|
Ange fältavgränsare. |
|
Extrahera allt utom de valda delarna. |
Exempel:
[me@linuxbox ~]$ cut -f 3 distros.txt
[me@linuxbox ~]$ cut -f 3 distros.txt | cut -c 7-10
[me@linuxbox ~]$ cut -d ':' -f 1 /etc/passwd | head
cut är något oflexibelt och fungerar därför bäst på filer som genererats av andra program snarare än frihandstext skriven av människor. Vårt distros.txt-exempel fungerar bra eftersom det är tabbavgränsat och har en konsekvent struktur.
När vi använder cut -f 3 distros.txt extraherar vi datumfältet. Genom att köra cut en andra gång med cut -c 7-10 extraherar vi året ur dessa datum. Notationen 7-10 är ett exempel på ett teckenintervall.
32.3.2. Expandera tabbar
Programmet expand kan konvertera tabbar till blanksteg, och unexpand kan konvertera blanksteg tillbaka till tabbar.
Om vi bearbetar en tabbavgränsad fil med expand kan vi sedan använda cut -c för att arbeta med teckenpositioner enklare. Omvänt kan unexpand komprimera serier av blanksteg tillbaka till tabbar.
32.3.3. paste
paste gör motsatsen till cut: det slår samman textkolumner från flera filer.
[me@linuxbox ~]$ sort -k 3.7nbr -k 3.1nbr -k 3.4nbr distros.txt > distros-by-date.txt
[me@linuxbox ~]$ cut -f 1,2 distros-by-date.txt > distros-versions.txt
[me@linuxbox ~]$ cut -f 3 distros-by-date.txt > distros-dates.txt
[me@linuxbox ~]$ paste distros-dates.txt distros-versions.txt
Detta är ett bra exempel på komposition av textverktyg. Vi sorterar filen kronologiskt, delar upp den i separata kolumner och använder sedan paste för att sätta ihop kolumnerna i en ny ordning. Det skapar en kronologisk lista med datumet först i varje post.
32.3.4. join
join liknar paste, men det kombinerar rader från flera filer baserat på ett gemensamt nyckelfält.
[me@linuxbox ~]$ join distros-key-names.txt distros-key-vernums.txt | head
11/25/2008 Fedora 10
10/30/2008 Ubuntu 8.10
Indatafilerna måste vara sorterade på nyckelfältet för att join ska fungera korrekt.
join är egentligen en textversion av en relationsdatabas-join. I stället för att bara klistra ihop filer sida vid sida använder det ett gemensamt fält för att avgöra vilka rader som hör ihop. I vårt exempel fungerar utgivningsdatumet som den gemensamma nyckeln.
32.3.5. tac och rev
tac skriver ut filer i omvänd radordning, och rev vänder teckenordningen inom varje rad.
[me@linuxbox ~]$ tail /var/log/backup.log | tac
[me@linuxbox ~]$ echo "This is a test." | rev
.tset a si sihT
[me@linuxbox ~]$ tail /var/log/backup.log | rev | cut -c 2- | rev
tac är ofta användbart för att ordna om loggar så att de senaste händelserna visas först. rev kombineras ofta med cut när vi behöver ta bort tecken från slutet av en rad i stället för från början.
32.4. Jämföra text
32.4.1. comm
comm jämför två sorterade filer och visar rader som är unika för varje fil samt rader de har gemensamt.
[me@linuxbox ~]$ comm file1.txt file2.txt
[me@linuxbox ~]$ comm -12 file1.txt file2.txt
b
c
d
comm producerar tre kolumner: rader unika för den första filen, rader unika för den andra filen och rader som delas av båda. Flaggorna -1, -2 och -3 undertrycker enskilda kolumner. Kombinationen -12 lämnar bara de gemensamma raderna.
32.4.2. diff
diff upptäcker skillnader mellan filer och stöder många utdataformat.
[me@linuxbox ~]$ diff file1.txt file2.txt
[me@linuxbox ~]$ diff -c file1.txt file2.txt
[me@linuxbox ~]$ diff -u file1.txt file2.txt
diff är ett kraftfullare jämförelseverktyg än comm. Det används ofta av programutvecklare och systemadministratörer för att undersöka ändringar över tid och kan också jämföra hela katalogträd.
Standardutdata från diff är kortfattad och utformad främst för maskiner och erfarna användare. Kontextformatet (-c) och det enhetliga formatet (-u) är lättare att läsa för människor. Det enhetliga formatet är särskilt populärt eftersom det visar samma ändringar mer kompakt.
| Ändring | Beskrivning |
|---|---|
|
Lägg till rader från den andra filen. |
|
Ändra rader i den första filen så att de matchar den andra. |
|
Ta bort rader från den första filen. |
| Indikator | Betydelse |
|---|---|
blank |
Kontextrad. |
|
Borttagen rad. |
|
Tillagd rad. |
|
Ändrad rad. |
| Tecken | Betydelse |
|---|---|
blank |
Gemensam för båda filerna. |
|
Borttagen från den första filen. |
|
Tillagd i den första filen. |
32.4.3. patch
patch tillämpar ändringar som beskrivs av utdata från diff.
diff -Naur old_file new_file > diff_file
patch < diff_file
Exempel:
[me@linuxbox ~]$ diff -Naur file1.txt file2.txt > patchfile.txt
[me@linuxbox ~]$ patch < patchfile.txt
patching file file1.txt
Detta är det klassiska arbetsflödet som används för att distribuera källkodsändringar. I stället för att skicka ett helt nytt källträd kan vi skicka bara skillnaderna. Mottagaren använder sedan patch för att tillämpa ändringarna på en befintlig kopia.
32.5. Redigering i farten
32.5.1. tr
Programmet tr används för att translitterera tecken. Vi kan tänka på detta som en slags teckenbaserad sök-och-ersätt-operation. Translitterering är processen att ändra tecken från ett alfabet till ett annat. Att konvertera tecken från gemener till versaler är till exempel translitterering. Vi kan utföra en sådan konvertering med tr så här:
[me@linuxbox ~]$ echo "lowercase letters" | tr a-z A-Z
LOWERCASE LETTERS
[me@linuxbox ~]$ echo "lowercase letters" | tr [:lower:] A
AAAAAAAAA AAAAAAA
[me@linuxbox ~]$ tr -d '\r' < dos_file > unix_file
[me@linuxbox ~]$ echo "aaabbbccc" | tr -s ab
abccc
tr kan översätta tecken, ta bort dem eller komprimera upprepade tecken till en enda förekomst. Det används ofta som ett snabbt filter i pipelines.
32.5.2. ROT13: Den inte så hemliga avkodarringen
ROT13-exempel:
[me@linuxbox ~]$ echo "secret text" | tr a-zA-Z n-za-mN-ZA-M
frperg grkg
ROT13 är ett enkelt substitutionschiffer som roterar varje bokstav 13 positioner genom alfabetet. Det är inte riktig kryptering, men det är ett klassiskt exempel på teckenöversättning.
32.5.3. sed
Namnet sed är en förkortning av stream editor. Det utför textredigering på en textström, antingen en uppsättning angivna filer eller standardindata. sed är ett kraftfullt och något komplext program (det finns hela böcker om det), så vi kommer inte att täcka det fullständigt här.
[me@linuxbox ~]$ echo "front" | sed 's/front/back/'
back
[me@linuxbox ~]$ echo "front" | sed 's_front_back_'
back
I allmänhet fungerar sed så att det får antingen ett enda redigeringskommando (på kommandoraden) eller namnet på en skriptfil med flera kommandon, och därefter utför dessa kommandon på varje rad i textströmmen. Här är ett enkelt exempel på sed i arbete:
| Adress | Beskrivning |
|---|---|
|
Ett radnummer. |
|
Den sista raden. |
|
Rader som matchar ett reguljärt uttryck. |
|
Ett intervall av rader. |
|
Matcha en startrad och därefter var |
|
Matcha |
|
Matcha alla rader utom |
I detta exempel skapar vi en textström med ett ord med hjälp av echo och skickar den till sed. sed utför i sin tur instruktionen s/front/back/ på texten i strömmen och producerar back som resultat. Vi kan också känna igen detta kommando som liknande kommandot för substitution (sök och ersätt) i vi.
[me@linuxbox ~]$ sed -n '1,5p' distros.txt
[me@linuxbox ~]$ sed -n '/SUSE/p' distros.txt
[me@linuxbox ~]$ sed -n '/SUSE/!p' distros.txt
| Kommando | Beskrivning |
|---|---|
|
Skriv ut aktuellt radnummer. |
|
Lägg till text efter aktuell rad. |
|
Ta bort aktuell rad. |
|
Infoga text före aktuell rad. |
|
Skriv ut aktuell rad. |
|
Avsluta |
|
Avsluta |
|
Ersätt matchad text. |
|
Translitterera tecken. |
Vi kan formatera om datum med sed:
[me@linuxbox ~]$ sed 's/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3-\1-\2/' distros.txt
Och använda flaggan g för att ersätta alla matchningar på en rad:
[me@linuxbox ~]$ echo "aaabbbccc" | sed 's/b/B/'
aaaBbbccc
[me@linuxbox ~]$ echo "aaabbbccc" | sed 's/b/B/g'
aaaBBBccc
sed stöder även skript:
# sed script to produce Linux distributions report
1 i\
\
Linux Distributions Report\
s/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3-\1-\2/
y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
Kör med:
[me@linuxbox ~]$ sed -f distros.sed distros.txt
Även om sed ofta används för kompakta enradskommandon stöder det också skriptfiler för flerstegsomvandlingar. Det gör det användbart för repeterbara textbearbetningsuppgifter.
32.5.4. De som gillar sed gillar också…
sed är ett kapabelt program som kan utföra ganska komplexa redigeringsuppgifter på textströmmar. Det används oftast för korta enradskommandon snarare än långa skript. Många användare föredrar andra verktyg för större uppgifter, särskilt awk och perl.
Både awk och perl går bortom enkla textfilter och sträcker sig in i sfären av fullständiga programmeringsspråk. perl används ofta i stället för skalskript för administrationsarbete och webbutveckling, medan awk är särskilt starkt på att hantera tabelldata. De ligger utanför ramen för denna bok, men de är väl värda att lära sig.
32.5.5. aspell
Det sista verktyget vi ska titta på är aspell, en interaktiv stavningskontroll. aspell är efterföljaren till det äldre programmet ispell och kan ofta användas som en direkt ersättning. Även om det ofta används av andra program fungerar det utmärkt som ett fristående kommandoradsverktyg.
För att stavningskontrollera en vanlig textfil:
aspell check textfil
Som ett snabbt exempel skapar vi en fil med ett par felstavningar:
[me@linuxbox ~]$ cat > foo.txt
The quick brown fox jimps over the laxy dog.
Sedan kontrollerar vi den med aspell:
[me@linuxbox ~]$ aspell check foo.txt
aspell körs interaktivt och visar stavningsförslag och åtgärder som ignorera, ersätta, lägga till och avbryta. Efter att ha korrigerat orden ser filens innehåll ut så här:
[me@linuxbox ~]$ cat foo.txt
The quick brown fox jumps over the lazy dog.
I kontroll-läge visar aspell en numrerad lista med föreslagna ersättningar och en uppsättning åtgärder som ignorera, ersätta, lägga till och avbryta. Efter att ha valt ersättningar för jimps och laxy innehåller den korrigerade filen korrekt stavad text.
Om det inte avaktiveras med --dont-backup skapar aspell en säkerhetskopia av originalfilen med ändelsen .bak.
För att återanvända exemplet kan vi återinföra felstavningarna med sed:
[me@linuxbox ~]$ sed -i 's/lazy/laxy/; s/jumps/jimps/' foo.txt
aspell kan även hantera vissa strukturerade textformat. Om till exempel foo.txt innehåller HTML-märkning kommer aspell check foo.txt att försöka stavningskontrollera tagginnehållet också. I den situationen använder du HTML-läge:
[me@linuxbox ~]$ aspell -H check foo.txt
Detta gör att aspell ignorerar HTML-taggar och fokuserar på textinnehållet i stället.
Som standard ignorerar aspell URL:er och e-postadresser i text. Detta beteende kan ändras med kommandoradsflaggor, och det går också att styra vilka märkningstaggar som kontrolleras och vilka som hoppas över.
32.6. Sammanfattning
I detta kapitel tittade vi på några av de många kommandoradsverktyg som arbetar med text. Det kanske inte omedelbart är uppenbart hur eller varför du skulle använda vissa av dessa verktyg i vardagen, men tillsammans utgör de en viktig verktygslåda för att lösa praktiska problem, särskilt när vi kommer in på skalskript.
32.7. Vidare läsning
-
Från GNU Coreutils-paketet:
https://www.gnu.org/software/coreutils/manual/coreutils.html#Output-of-entire-files -
Fler Coreutils-ämnen om textbehandling:
https://www.gnu.org/software/coreutils/manual/coreutils.html#Operating-on-sorted-files -
Fler Coreutils-operationer på fält och tecken:
https://www.gnu.org/software/coreutils/manual/coreutils.html#Operating-on-fields -
diffochpatch:https://www.gnu.org/software/diffutils/manual/html_mono/diff.html -
Många rekommenderar också:
https://www.grymoire.com/Unix/Sed.html
33. 21 - Formatera utdata
I detta kapitel fortsätter vi vår genomgång av textrelaterade verktyg, med fokus på program som används för att formatera textutdata snarare än att ändra själva texten. Dessa verktyg används ofta för att förbereda text för senare utskrift, ett ämne som vi tar upp i nästa kapitel. I detta kapitel går vi igenom följande program:
-
nl- numrera rader. -
fold- radbryt varje rad till en angiven längd. -
fmt- ett enkelt textformatteringsprogram. -
pr- förbered text för utskrift. -
printf- formatera och skriv ut data. -
groff- ett dokumentformateringssystem.
33.1. Enkla formateringsverktyg
33.1.1. nl - numrera rader
Programmet nl numrerar rader. I sin enklaste användning liknar det cat -n.
[me@linuxbox ~]$ nl distros.txt | head
nl stöder logiska sidor, vilket gör det möjligt att återställa numreringen i en textström.
| Märkning | Betydelse |
|---|---|
|
Början på den logiska sidans sidhuvud. |
|
Början på den logiska sidans brödtext. |
|
Början på den logiska sidans sidfot. |
| Flagga | Betydelse |
|---|---|
|
Ange numreringsstil för brödtext. |
|
Ange numreringsstil för sidfot. |
|
Ange numreringsstil för sidhuvud. |
|
Ange steg för sidnumrering. |
|
Ange numreringsformat. |
|
Återställ inte numreringen vid varje logisk sida. |
|
Lägg till en avgränsarsträng efter radnumret. |
|
Ange första radnumret för varje logisk sida. |
|
Ange bredden på radnummerfältet. |
Exempel med sort, sed och nl tillsammans:
[me@linuxbox ~]$ sort -k 1,1 -k 2n distros.txt | sed -f distros-nl.sed | nl
Intressanta varianter:
nl -n rz
nl -w 3 -s ' '
33.1.2. fold - radbryt varje rad till en angiven längd
Radbrytning är processen att bryta textrader vid en angiven bredd. Liksom våra andra kommandon accepterar fold antingen en eller flera textfiler eller standardindata. Om vi skickar en enkel textström till fold kan vi se hur det fungerar.
[me@linuxbox ~]$ echo "The quick brown fox jumps over the lazy dog." | fold -w 12
The quick br
own fox jump
s over the l
azy dog.
[me@linuxbox ~]$ echo "The quick brown fox jumps over the lazy dog." | fold -w 12 -s
The quick
brown fox
jumps over
the lazy
dog.
Här ser vi fold i arbete. Texten som skickas av kommandot echo delas upp i segment som anges av flaggan -w. I detta exempel anger vi en radbredd på 12 tecken. Om ingen bredd anges är standard 80 tecken. Lägg märke till hur raderna bryts oavsett ordgränser. Om flaggan -s läggs till bryter fold raden vid det sista tillgängliga blanksteget innan radbredden nås.
33.1.3. fmt - ett enkelt textformatteringsprogram
Programmet fmt radbryter också text, men gör mycket mer. Det accepterar antingen filer eller standardindata och utför styckeformatering på textströmmen. I grunden fyller och sammanfogar det rader i text, samtidigt som tomma rader och indrag bevaras.
[me@linuxbox ~]$ fmt -w 50 fmt-info.txt
[me@linuxbox ~]$ fmt -cw 50 fmt-info.txt
| Flagga | Beskrivning |
|---|---|
|
Arbeta i kronmarginalläge. |
|
Formatera bara rader som börjar med |
|
Enbart uppdelningsläge. |
|
Utför enhetlig avståndsjustering. |
|
Ange utdatabredd. |
För att demonstrera detta behöver vi lite text. Vi lånar lite från infosidan för fmt.
[me@linuxbox ~]$ fmt -w 50 -p '# ' fmt-code.txt
33.1.4. pr - formatera text för utskrift
Programmet pr används för att paginera text. När man skriver ut text är det ofta önskvärt att separera utskriftens sidor med flera rader blanktecken, för att ge varje sida en övre och nedre marginal. Dessutom kan detta utrymme användas för att infoga sidhuvud och sidfot på varje sida.
[me@linuxbox ~]$ pr -l 15 -w 65 distros.txt
Vi demonstrerar pr genom att formatera vår fil distros.txt till en serie korta sidor (endast de två första sidorna visas).
33.1.5. printf - formatera och skriv ut data
Till skillnad från de andra kommandona i detta kapitel används inte kommandot printf i pipelines (det accepterar inte standardindata), och det används inte heller särskilt ofta direkt på kommandoraden (det används mest i skript). Så varför är det viktigt? Därför att det används så mycket.
printf [-v var] "format" arguments
printf (från uttrycket "print formatted") utvecklades ursprungligen för programspråket C och har implementerats i många programspråk, inklusive skalet. I bash är printf faktiskt inbyggt.
[me@linuxbox ~]$ printf "I formatted the string: %s\n" foo
I formatted the string: foo
[me@linuxbox ~]$ printf -v result "I formatted the string: %s\n" foo
[me@linuxbox ~]$ echo "$result"
I formatted the string: foo
| Specifikation | Beskrivning |
|---|---|
|
Heltal med tecken (decimal). |
|
Flyttal. |
|
Oktalt heltal. |
|
Sträng. |
|
Hexadecimalt heltal med små bokstäver. |
|
Hexadecimalt heltal med stora bokstäver. |
|
Litteralt procenttecken. |
[me@linuxbox ~]$ printf "%d, %f, %o, %s, %x, %X\n" 380 380 380 380 380 380
380, 380.000000, 574, 380, 17c, 17C
printf fungerar så här:
%[flags][width][.precision]conversion_specification
| Komponent | Beskrivning |
|---|---|
|
Formateringsflaggor som alternativt format, nollutfyllnad, vänsterjustering och explicit tecken. |
|
Minsta fältbredd. |
|
Antal decimaler för flyttal, eller teckengräns för strängar. |
| Argument | Format | Resultat | Anmärkning |
|---|---|---|---|
|
|
|
Enkel heltalsformatering. |
|
|
|
Hexadecimal med alternativt format. |
|
|
|
Heltal utfyllt med inledande nollor. |
|
|
|
Flyttal med precision. |
|
|
|
Bredare fält visar utfyllnad. |
|
|
|
Tvinga fram tecken på positivt tal. |
|
|
|
Sträng avkortad med precision. |
Kommandot får en sträng som innehåller en formatbeskrivning, vilken sedan tillämpas på en lista med argument. Det formaterade resultatet skickas till standardutdata om inte flaggan -v anges, i vilket fall det formaterade resultatet lagras i variabeln var. Här är ett trivialt exempel på printf i arbete:
[me@linuxbox ~]$ printf "%s\t%s\t%s\n" str1 str2 str3
[me@linuxbox ~]$ printf "Line: %05d %15.3f Result: %+15d\n" 1071 3.14156295 32589
[me@linuxbox ~]$ printf "<html>\n\t<head>\n\t\t<title>%s</title>\n\t</head>\n\t<body>\n\t\t<p>%s</p>\n\t</body>\n</html>\n" "Page Title" "Page Content"
33.2. Dokumentformateringssystem
Hittills har vi undersökt de enkla verktygen för textformatering. De är bra för små, enkla uppgifter, men hur är det med större jobb? En av anledningarna till att Unix blev ett populärt operativsystem bland tekniska och vetenskapliga användare (förutom att det erbjöd en kraftfull fleranvändar- och fleruppgiftsmiljö för alla slags programutveckling) var att det erbjöd verktyg som kunde användas för att producera många typer av dokument, särskilt vetenskapliga och akademiska publikationer. Faktum är att, som GNU-dokumentationen beskriver, var dokumentberedning avgörande för Unix utveckling.
-
Två huvudfamiljer av dokumentformatterare dominerar området: de som härstammar från det ursprungliga programmet
roff, däriblandnroffochtroff, och de som bygger på Donald Knuths sättningssystemTEX(uttalas "tek"). Och ja, det borttagnaE:et i mitten är en del av namnet. -
Namnet
roffkommer från uttrycket "run off", som i "I’ll run off a copy for you".
33.2.1. groff
groff är en programsvit som innehåller GNU:s implementation av troff. Den innehåller också ett skript som används för att emulera nroff och resten av roff-familjen.
Även om roff och dess avkomlingar används för att skapa formaterade dokument gör de det på ett sätt som är ganska främmande för moderna användare. De flesta dokument i dag produceras med ordbehandlare som kan utföra både innehållsskapande och layout i ett enda steg. Före de grafiska ordbehandlarnas tid framställdes dokument ofta i en tvåstegsprocess där man använde en textredigerare för att skriva innehållet och en processor, såsom troff, för att tillämpa formateringen. Instruktioner till formateringsprogrammet bäddades in i den skrivna texten genom användning av ett märkspråk. Den moderna motsvarigheten till en sådan process är webbsidan, som skrivs med någon form av textredigerare och sedan renderas av en webbläsare som använder HTML som märkspråk för att beskriva den slutliga sidlayouten.
[me@linuxbox ~]$ zcat /usr/share/man/man1/ls.1.gz | head
[me@linuxbox ~]$ man ls | head
Vi kommer inte att behandla groff i sin helhet, eftersom många delar av dess märkspråk handlar om ganska esoteriska typografiska detaljer. I stället ska vi koncentrera oss på ett av dess makropaket som fortfarande används brett. Dessa makropaket kondenserar många av dess låg-nivåkommandon till en mindre uppsättning hög-nivåkommandon som gör groff mycket enklare att använda.
[me@linuxbox ~]$ zcat /usr/share/man/man1/ls.1.gz | groff -mandoc -T ascii | head
Låt oss för ett ögonblick tänka på den anspråkslösa manualsidan. Den ligger i katalogen /usr/share/man som en gzip-komprimerad textfil. Om vi skulle titta på dess okomprimerade innehåll skulle vi se följande (manualsidan för ls i avsnitt 1 visas):
[me@linuxbox ~]$ zcat /usr/share/man/man1/ls.1.gz | groff -mandoc | head
%!PS-Adobe-3.0
Jämfört med manualsidan i sin normala presentation kan vi nu börja se ett samband mellan märkspråket och dess resultat.
[me@linuxbox ~]$ zcat /usr/share/man/man1/ls.1.gz | groff -mandoc > ~/Desktop/ls.ps
[me@linuxbox ~]$ ps2pdf ~/Desktop/foo.ps ~/Desktop/ls.pdf
Det intressanta här är att manualsidor renderas av groff med hjälp av makropaketet mandoc. Faktum är att vi kan simulera kommandot man med följande pipeline:
[me@linuxbox ~]$ sort -k 1,1 -k 2n distros.txt | sed -f distros-tbl.sed | groff -t -T ascii
[me@linuxbox ~]$ sort -k 1,1 -k 2n distros.txt | sed -f distros-tbl.sed | groff -t > ~/Desktop/foo.ps
33.3. Sammanfattning
Eftersom text är så central för Unix-liknande operativsystems karaktär är det logiskt att det finns många verktyg som används för att manipulera och formatera text. Och som vi har sett: det finns det verkligen! De enkla formateringsverktygen som fmt och pr kommer till användning i många skript som producerar korta dokument, medan groff (och dess vänner) kan användas för att skriva böcker. Vi kanske aldrig kommer att skriva en teknisk artikel med kommandoradsverktyg (även om många faktiskt gör det!), men det är bra att veta att vi skulle kunna.
33.4. Vidare läsning
-
groffUser’s Guide: -
Writing Papers With
nroffUsing-me: -
-meReference Manual: -
Wikipedia:
34. 22 - Utskrift
Efter att ha ägnat de senaste kapitlen åt att manipulera text är det dags att få ut den på papper. I detta kapitel ska vi titta på de kommandoradsverktyg som används för att skriva ut filer och styra skrivardrift. Vi kommer inte att gå in på hur man konfigurerar utskrift, eftersom det varierar från distribution till distribution och vanligen ställs in automatiskt under installationen. Observera att vi behöver en fungerande skrivarkonfiguration för att kunna utföra övningarna i detta kapitel.
Vi ska gå igenom följande kommandon:
-
pr- konvertera textfiler för utskrift. -
lp/lpr- skriv ut filer. -
a2ps- formatera filer för utskrift på en PostScript-skrivare. -
lpstat- visa statusinformation om skrivare. -
lpq- visa status för skrivarkön. -
lprm/cancel- avbryt utskriftsjobb.
34.1. En kort historia om utskrift
När skrivare var dyra och centraliserade, vilket de ofta var under Unix tidiga år, var det vanligt att många användare delade på en skrivare. För att identifiera utskriftsjobb som tillhörde en viss användare skrevs ofta en förstasida ut i början av varje utskriftsjobb med användarens namn. Datorsupportpersonalen lastade sedan en vagn med dagens utskriftsjobb och delade ut dem till de enskilda användarna.
34.1.1. Utskrift i den mörka forntiden
Skrivartekniken på 80-talet skilde sig från dagens på två sätt. För det första var skrivarna från denna tid nästan alltid slagskrivare. Slagskrivare använder en mekanisk anordning som slår ett färgband mot papperet för att skapa teckenavtryck på sidan. Två av de populära teknikerna vid denna tid var daisy wheel-utskrift och matrisutskrift.
34.1.2. Teckenbaserade skrivare
Det andra, och viktigare, kännetecknet hos tidiga skrivare var att de använde en fast uppsättning tecken som var inbyggda i själva enheten. En skrivare med daisy wheel kunde till exempel bara skriva ut de tecken som faktiskt var gjutna i kronbladet på skrivhjulet. Det gjorde skrivarna mycket lika snabba skrivmaskiner. Liksom de flesta skrivmaskiner skrev de med teckensnitt med fast bredd. Det betyder att varje tecken har samma bredd. Utskrift skedde på fasta positioner på sidan, och sidans utskrivbara område innehöll ett fast antal tecken. De flesta skrivare skrev ut 10 tecken per tum (CPI) horisontellt och sex rader per tum (LPI) vertikalt. Med detta schema är ett ark i amerikanskt letter-format 85 tecken brett och 66 rader högt. Med hänsyn till en liten marginal på vardera sidan ansågs 80 tecken vara den maximala bredden på en utskriftsrad. Det förklarar varför terminalvisningar (och våra terminalemulatorer) normalt är 80 tecken breda. Genom att använda ett teckensnitt med fast bredd och en terminal som är 80 tecken bred får man en What You See Is What You Get-vy (WYSIWYG) av utskriften.
Data skickas till en skrivmaskinsliknande skrivare i en enkel ström av byte som innehåller de tecken som ska skrivas ut. För att skriva ut ett a skickas till exempel ASCII-teckenkoden 97. Dessutom gav ASCII:s lågnumrerade styrkoder ett sätt att flytta skrivarvagn och papper, med koder för vagnretur, radmatning, form feed och så vidare. Med hjälp av styrkoderna gick det att uppnå vissa begränsade typsnittseffekter, som fetstil, genom att låta skrivaren skriva ut ett tecken, backa och skriva ut samma tecken igen för att få ett mörkare avtryck på sidan. Vi kan faktiskt se detta om vi använder nroff för att rendera en manualsida och undersöker utdatan med cat -A.
[me@linuxbox ~]$ zcat /usr/share/man/man1/ls.1.gz | nroff -P -c -man | cat -A | head
Tecknen ^H (Ctrl-h) är backsteg som används för att skapa den feta effekten. På samma sätt kan vi också se en sekvens med backsteg och understreck som används för att skapa understrykning.
34.1.3. Grafiska skrivare
Utvecklingen av grafiska gränssnitt ledde till stora förändringar i skrivartekniken. När datorer gick över till mer bildbaserade skärmar gick också utskriften från teckenbaserade till grafiska tekniker. Detta möjliggjordes av den billiga laserskrivaren, som i stället för att skriva fasta tecken kunde skriva ut små punkter var som helst i sidans utskrivbara område. Detta gjorde det möjligt att skriva ut proportionella teckensnitt (som de som används av sättmaskiner) och till och med fotografier och högkvalitativa diagram.
Detta krävde en smartare representation än råa bitmappar, vilket ledde till sidbeskrivningsspråket (PDL, Page Description Language). Det mest kända PDL:et är PostScript, som fortfarande används i stor utsträckning.
En PostScript-skrivare tolkar ett PostScript-program och omvandlar det till en bitmapp för utskrift. Detta omvandlingssteg kallas rasterbildsbearbetning (RIP, Raster Image Processing).
34.2. Utskrift i Linux
Moderna Linuxsystem använder vanligtvis två stora programvarukomponenter för utskrift:
-
CUPS (Common Unix Printing System), som hanterar skrivardrivrutiner, köer och utskriftsjobb.
-
Ghostscript, en PostScript-tolk som kan fungera som ett RIP.
34.3. Förbereda filer för utskrift
Som kommandoradsanvändare är vi mest intresserade av att skriva ut text.
34.3.1. pr - konvertera textfiler för utskrift
Vi såg pr i föregående kapitel. Här tittar vi på dess flaggor i ett utskriftssammanhang.
| Flagga | Beskrivning |
|---|---|
|
Skriv ut ett sidintervall. |
|
Ordna utdata i angivet antal kolumner. |
|
Lista flerkolumnsutdata horisontellt i stället för vertikalt. |
|
Dubbelspalta utdata. |
|
Formatera datumet som används i sidhuvuden. |
|
Använd sidmatning i stället för vagnretur mellan sidor. |
|
Använd |
|
Ange sidlängd. |
|
Numrera rader. |
|
Skapa en vänstermarginal med angiven bredd i tecken. |
|
Ange sidbredd. |
Som standard listas flerkolumnsutdata vertikalt. Genom att lägga till flaggan -a (across) listas innehållet horisontellt.
[me@linuxbox ~]$ ls /usr/bin | pr -3 -w 65 | head
34.4. Skicka ett utskriftsjobb till en skrivare
Utskriftssviten CUPS stöder två utskriftsmetoder som historiskt har använts i Unix-liknande system. Den ena metoden, kallad Berkeley eller LPD (använd i Unix-versionen Berkeley Software Distribution), använder programmet lpr, medan den andra metoden, kallad SysV (från Unix-versionen System V), använder programmet lp. Båda programmen gör i stort sett samma sak. Valet mellan dem är en fråga om personlig smak.
34.4.1. lpr - skriv ut filer (Berkeley-stil)
lpr skickar filer eller standardindata till skrivaren.
[me@linuxbox ~]$ ls /usr/bin | pr -3 | lpr
För att välja en annan skrivare:
lpr -P printer_name
För att se tillgängliga skrivare:
[me@linuxbox ~]$ lpstat -a
Många Linuxsystem kan definiera en virtuell PDF-skrivare, vilket är praktiskt för att experimentera med utskriftskommandon.
| Flagga | Beskrivning |
|---|---|
|
Ange antal kopior. |
|
Snyggutskrift med skuggade sidhuvuden. |
|
Ange skrivare. |
|
Ta bort filer efter utskrift. |
34.4.2. lp - skriv ut filer (System V-stil)
Liksom lpr accepterar lp antingen filer eller standardindata för utskrift. Det skiljer sig från lpr genom att det stöder en annan (och något mer sofistikerad) uppsättning flaggor. Tabell 22-3 beskriver de vanligaste.
| Flagga | Beskrivning |
|---|---|
|
Ange målskrivare. |
|
Ange antal kopior. |
|
Använd liggande orientering. |
|
Skala filen så att den passar sidan. |
|
Skala utdata i procent. |
|
Ange tecken per tum. |
|
Ange rader per tum. |
|
Ange sidlista eller sidintervall. |
Exempel med mindre teckensnitt och fler kolumner:
[me@linuxbox ~]$ ls /usr/bin | pr -4 -w 90 -l 88 | lp -o page-left=36 -o cpi=12 -o lpi=8
34.4.3. Ett annat alternativ: a2ps
Programmet a2ps (som finns i de flesta förråd) är intressant. Som vi kan sluta oss till av namnet är det ett formatkonverteringsprogram, men det är också mycket mer. Namnet betydde ursprungligen ASCII to PostScript, och det användes för att förbereda textfiler för utskrift på PostScript-skrivare. Med åren har dock programmets möjligheter vuxit, och nu betyder namnet Anything to PostScript. Även om namnet antyder ett formatkonverteringsprogram är det i själva verket ett utskriftsprogram. Det skickar sin standardutdata till systemets standardskrivare snarare än till standardutdata. Programmets standardbeteende är att vara en pretty printer, vilket betyder att det förbättrar utseendet på utdatan. Vi använder programmet för att skapa en PostScript-fil på vårt skrivbord.
[me@linuxbox ~]$ ls /usr/bin | pr -3 -t | a2ps -o ~/Desktop/ls.ps -L 66
[stdin (plain): 11 pages on 6 sheets]
Här filtrerar vi strömmen med pr, med flaggan -t (utelämna sidhuvuden och sidfötter), och sedan med a2ps, där vi anger en utfil (flaggan -o) och 66 rader per sida (flaggan -L) för att matcha pagineringen från pr. Om vi visar den resulterande filen med ett lämpligt visningsprogram ser vi utdatan i figur 7.
| Flagga | Beskrivning |
|---|---|
|
Ange centrerad sidtitel. |
|
Ordna sidor i kolumner. |
|
Ange sidfot. |
|
Rapportera filtyper för argumenten. |
|
Ange vänster sidfot. |
|
Ange vänster titel. |
|
Numrera utdatarader. |
|
Visa standardinställningar. |
|
Skriv ut angivna sidor. |
|
Ange höger sidfot. |
|
Ange höger titel. |
|
Ordna sidor i rader. |
|
Inga sidhuvuden. |
|
Ange sidhuvud. |
|
Ange teckenstorlek i punkter. |
|
Ange tecken per rad. |
|
Ange rader per sida. |
|
Ange medianamn. |
|
Skriv ut angivet antal kopior. |
|
Skicka utdata till fil. |
|
Använd angiven skrivare. |
|
Stående orientering. |
|
Liggande orientering. |
|
Ange tabulatorstopp. |
|
Lägg till en vattenstämpel. |
Ett annat formateringsverktyg värt att känna till är enscript, som utför liknande trick men bara accepterar textindata.
34.5. Övervaka och styra utskriftsjobb
Eftersom Unix-utskriftssystem är utformade för att hantera flera utskriftsjobb från flera användare är CUPS utformat för att göra detsamma. Varje skrivare får en utskriftskö, där jobb parkeras tills de kan spoolas till skrivaren. CUPS tillhandahåller flera kommandoradsprogram som används för att hantera skrivarstatus och utskriftsköer. Liksom programmen lpr och lp är dessa administrationsprogram modellerade efter motsvarande program i utskriftssystemen Berkeley och System V.
34.5.1. lpstat - visa status för utskriftssystemet
Programmet lpstat är användbart för att avgöra namnen på och tillgängligheten för skrivare i systemet. Om vi till exempel hade ett system med både en fysisk skrivare (med namnet printer) och en virtuell PDF-skrivare (med namnet PDF) skulle vi kunna kontrollera deras status så här:
[me@linuxbox ~]$ lpstat -a
PDF accepting requests since Mon 08 Dec 2024 03:05:59 PM EST
printer accepting requests since Tue 24 Feb 2025 08:43:22 AM EST
[me@linuxbox ~]$ lpstat -s
system default destination: printer
device for PDF: cups-pdf:/
device for printer: ipp://print-server:631/printers/printer
| Flagga | Beskrivning |
|---|---|
|
Visa om skrivarköer tar emot jobb. |
|
Visa standardskrivaren. |
|
Visa skrivarstatus. |
|
Visa utskriftsserverns status. |
|
Visa en sammanfattning. |
|
Visa en fullständig statusrapport. |
34.5.2. lpq - visa status för skrivarkön
För att se statusen för en skrivarkö används programmet lpq. Det gör att vi kan se statusen för kön och de utskriftsjobb den innehåller. Här är ett exempel på en tom kö för en systemskrivare med namnet printer:
[me@linuxbox ~]$ lpq
printer is ready
no entries
[me@linuxbox ~]$ ls *.txt | pr -3 | lp
request id is printer-603 (1 file(s))
[me@linuxbox ~]$ lpq
printer is ready and printing
34.5.3. lprm / cancel - avbryt utskriftsjobb
CUPS tillhandahåller två program som används för att avsluta utskriftsjobb och ta bort dem ur utskriftskön. Det ena är av Berkeley-typ (lprm) och det andra av System V-typ (cancel). De skiljer sig något i vilka flaggor de stöder, men gör i grunden samma sak. Med vårt tidigare utskriftsjobb som exempel skulle vi kunna stoppa jobbet och ta bort det så här:
[me@linuxbox ~]$ cancel 603
[me@linuxbox ~]$ lpq
printer is ready
no entries
34.6. Sammanfattning
I detta kapitel såg vi hur gårdagens skrivare påverkade utformningen av utskriftssystemen i Unix-liknande maskiner, och hur mycket kontroll som finns på kommandoraden för att styra inte bara schemaläggning och körning av utskriftsjobb utan också olika utdataalternativ.
34.7. Vidare läsning
-
PostScript:
-
CUPS:
-
Utskriftssystemen Berkeley och System V:
35. 23 - Kompilera program
I detta kapitel ska vi titta på hur man bygger program genom att kompilera källkod. Tillgången till källkod är den grundläggande frihet som gör Linux möjligt. Hela ekosystemet kring Linuxutveckling bygger på fritt utbyte mellan utvecklare. För många skrivbordsanvändare har kompilering blivit en bortglömd konst. Förr var det ganska vanligt, men i dag underhåller distributionsleverantörer enorma förråd med förkompilerade binärfiler, redo att hämtas och användas. När detta skrivs innehåller Debians förråd (ett av de största bland distributionerna) mer än 68 000 paket.
Så varför kompilera programvara? Det finns två skäl:
-
Tillgänglighet. Trots antalet förkompilerade program i distributionsförråd kanske vissa distributioner inte innehåller alla önskade program. I så fall är det enda sättet att få programmet att kompilera det från källkod.
-
Aktualitet. Vissa distributioner specialiserar sig på de allra senaste programversionerna, men många gör det inte. Det betyder att om man vill ha den senaste versionen av ett program är kompilering nödvändig.
Vi introducerar ett nytt kommando:
-
make— verktyg för att underhålla program.
35.1. Vad är kompilering?
Enkelt uttryckt är kompilering processen att översätta källkod (den människoläsbara beskrivningen av ett program som skrivs av en programmerare) till datorprocessorns modersmål.
Datorns processor (CPU) arbetar på en elementär nivå och kör program i det som kallas maskinspråk. Det är en numerisk kod som beskriver extremt små operationer, såsom "lägg till denna byte", "peka på denna plats i minnet" eller "kopiera denna byte". Var och en av dessa instruktioner uttrycks i binär form (ettor och nollor). De tidigaste datorprogrammen skrevs med denna numeriska kod, vilket kanske förklarar varför programmerare som skrev den sades röka mycket, dricka litervis med kaffe och bära tjocka glasögon.
Detta problem övervanns genom tillkomsten av assemblerspråk, som ersatte de numeriska koderna med (något) lättare teckenbaserade minnesregler som CPY (för copy) och MOV (för move). Program skrivna i assemblerspråk bearbetas till maskinspråk av ett program som kallas assembler. Assemblerspråk används fortfarande i dag för vissa specialiserade programmeringsuppgifter, såsom enhetsdrivrutiner och inbyggda system.
Därefter kommer det som kallas högnivåspråk. De kallas så eftersom de gör det möjligt för programmeraren att bry sig mindre om detaljerna i vad processorn gör och mer om att lösa det aktuella problemet. De tidiga språken (utvecklade under 1950-talet) omfattar FORTRAN (utformat för vetenskapliga och tekniska uppgifter) och COBOL (utformat för affärstillämpningar). Båda används fortfarande i begränsad utsträckning.
35.1.1. Är alla program kompilerade?
Nej. Som vi har sett finns det program som skalskript som inte kräver kompilering. De körs direkt. Dessa skrivs i det som kallas skriptspråk eller tolkade språk. Dessa språk har vuxit i popularitet under senare år och omfattar Perl, Python, PHP, Ruby och många andra.
35.2. Kompilera ett C-program
Låt oss kompilera något. Innan vi gör det behöver vi dock några verktyg, som kompilatorn, länkaren och make. Den C-kompilator som nästan universellt används i Linuxmiljön heter gcc (GNU C Compiler) och skrevs ursprungligen av Richard Stallman. De flesta distributioner installerar inte gcc som standard. Vi kan kontrollera om kompilatorn finns så här:
[me@linuxbox ~]$ which gcc
/usr/bin/gcc
Resultatet i detta exempel visar att kompilatorn är installerad.
35.2.1. Hämta källkoden
För vår kompilationsövning ska vi kompilera ett program från GNU-projektet som heter diction. Detta praktiska lilla program granskar textfiler med avseende på skrivkvalitet och stil. Som program betraktat är det ganska litet och lätt att bygga.
[me@linuxbox ~]$ mkdir src
[me@linuxbox ~]$ cd src
[me@linuxbox src]$ ftp ftp.gnu.org
ftp> cd gnu/diction
ftp> ls
ftp> get diction-1.11.tar.gz
ftp> bye
[me@linuxbox src]$ ls
diction-1.11.tar.gz
Enligt konvention skapar vi först en katalog för vår källkod med namnet src och hämtar sedan källkoden dit med ftp.
[me@linuxbox ~]$ curl -O https://ftp.gnu.org/gnu/diction/diction-1.11.tar.gz
Även om vi använde ftp i exemplet ovan, vilket är traditionellt, finns det andra sätt att hämta källkod. GNU-projektet stöder till exempel också hämtning via HTTPS. Vi kan hämta diction-källkoden med programmet curl.
Obs: Eftersom vi är "underhållare" av denna källkod medan vi kompilerar den kommer vi att hålla den i ~/src. Källkod som installerats av distributionen kommer att finnas i /usr/src, medan källkod som vi själva underhåller och som är avsedd att användas av flera användare vanligtvis placeras i /usr/local/src.
[me@linuxbox src]$ tar xzf diction-1.11.tar.gz
[me@linuxbox src]$ ls
diction-1.11
diction-1.11.tar.gz
Som vi ser levereras källkod vanligtvis i form av en komprimerad tar-fil. En sådan fil kallas ibland en tarball och innehåller källträdet, alltså hierarkin av kataloger och filer som utgör källkoden. När vi har kommit till FTP-platsen undersöker vi listan över tillgängliga tar-filer och väljer den nyaste versionen för hämtning. Med kommandot get inne i ftp kopierar vi filen från FTP-servern till den lokala maskinen.
tar tzvf tarfile | head
35.2.2. Undersöka källträdet
När tar-filen packas upp skapas en ny katalog med namnet diction-1.11. Denna katalog innehåller källträdet. Låt oss titta in i den.
[me@linuxbox src]$ cd diction-1.11
[me@linuxbox diction-1.11]$ ls
Här ser vi ett antal filer. Program som hör till GNU-projektet, liksom många andra, levererar dokumentationsfilerna README, INSTALL, NEWS och COPYING. Dessa filer innehåller en beskrivning av programmet, information om hur det byggs och installeras samt licensvillkoren. Det är alltid en god idé att noggrant läsa filerna README och INSTALL innan man försöker bygga programmet.
De andra intressanta filerna i denna katalog är de som slutar på .c och .h.
[me@linuxbox diction-1.11]$ ls *.c
diction.c getopt1.c getopt.c misc.c sentence.c style.c
[me@linuxbox diction-1.11]$ ls *.h
getopt.h getopt_int.h misc.h sentence.h
[me@linuxbox diction-1.11]$ less diction.c
Filerna .c innehåller de två C-program som paketet tillhandahåller (style och diction), uppdelade i moduler. Det är vanlig praxis att större program delas upp i mindre, lättare hanterbara delar. Källkodsfilerna är vanliga textfiler och kan undersökas med less.
#include "getopt.h"
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
Filerna .h kallas headerfiler. Även dessa är vanliga textfiler. Headerfiler innehåller beskrivningar av de rutiner som ingår i en källkodsfil eller ett bibliotek. För att kompilatorn ska kunna koppla samman modulerna måste den få en beskrivning av alla moduler som behövs för att färdigställa hela programmet. Nära början av filen diction.c ser vi denna rad:
35.2.3. Bygga programmet
De flesta program byggs med en enkel sekvens av två kommandon.
./configure
make
Programmet configure är ett skalskript som följer med källträdet. Dess uppgift är att analysera byggmiljön. Den mesta källkoden är utformad för att vara portabel. Det vill säga att den är avsedd att kunna byggas på mer än en sorts Unix-liknande system. Men för att kunna göra det kan källkoden behöva genomgå mindre justeringar under byggprocessen för att ta hänsyn till skillnader mellan systemen. configure kontrollerar också att nödvändiga externa verktyg och komponenter är installerade. Låt oss köra configure. Eftersom configure inte ligger där skalet normalt förväntar sig att program finns måste vi uttryckligen tala om dess plats för skalet genom att prefixa kommandot med ./, vilket anger att programmet ligger i den aktuella arbetskatalogen.
[me@linuxbox diction-1.11]$ ./configure
configure kommer att skriva ut en mängd meddelanden medan det testar och ställer in bygget. När det är klart ser det ut ungefär så här:
config.status: creating Makefile
config.status: creating diction.1
config.status: creating style.1
config.status: creating config.h
Det viktiga här är att det inte finns några felmeddelanden. Om det hade funnits det skulle konfigurationen ha misslyckats, och programmet kan då inte byggas förrän felen har rättats.
[me@linuxbox diction-1.11]$ less Makefile
Vi ser att configure skapade flera nya filer i vår källkodskatalog. Den viktigaste är makefile. makefile är en konfigurationsfil som talar om exakt för programmet make hur det ska bygga programmet. Utan den vägrar make att köra. makefile är en vanlig textfil, så vi kan titta på den:
CC = gcc
diction: diction.o sentence.o misc.o getopt.o getopt1.o
$(CC) -o $@ $(LDFLAGS) diction.o sentence.o misc.o \
getopt.o getopt1.o $(LIBS)
.c.o:
$(CC) -c $(CPPFLAGS) $(CFLAGS) $<
Programmet make tar en makefile som indata (som vanligtvis heter Makefile) och beskriver relationerna och beroendena mellan de komponenter som utgör det färdiga programmet.
[me@linuxbox diction-1.11]$ make
Den första delen av makefile definierar variabler som ersätts i senare avsnitt av makefile. Vi ser till exempel följande rad:
[me@linuxbox diction-1.11]$ ls
35.2.4. Varför make spelar roll
Det definierar att C-kompilatorn ska vara gcc. Senare i makefile ser vi ett exempel på hur den används.
[me@linuxbox diction-1.11]$ make
make: Nothing to be done for `all'.
Här utförs en substitution, och värdet $(CC) ersätts med gcc vid körning.
Merparten av makefile består av rader som definierar ett mål, i detta fall den körbara filen diction, och de filer som den är beroende av. De återstående raderna beskriver de kommandon som behövs för att skapa målet från dess komponenter. Vi ser i detta exempel att den körbara filen diction (en av slutprodukterna) beror på att diction.o, sentence.o, misc.o, getopt.o och getopt1.o finns. Senare i makefile ser vi definitioner av var och en av dessa som mål.
[me@linuxbox diction-1.11]$ rm getopt.o
[me@linuxbox diction-1.11]$ make
Vi ser dock inte att något kommando har angivits för dem. Det hanteras av ett generellt mål tidigare i filen, som beskriver det kommando som används för att kompilera vilken .c-fil som helst till en .o-fil.
[me@linuxbox diction-1.11]$ ls -l diction getopt.c
[me@linuxbox diction-1.11]$ touch getopt.c
[me@linuxbox diction-1.11]$ ls -l diction getopt.c
[me@linuxbox diction-1.11]$ make
[me@linuxbox diction-1.11]$ ls -l diction getopt.c
Allt detta verkar mycket komplicerat. Varför inte bara lista alla steg för att kompilera delarna och sedan vara klar? Svaret blir tydligt om en stund. Under tiden kör vi make och bygger våra program.
35.2.5. Installera programmet
Välpaketerad källkod innehåller ofta ett särskilt make-mål som heter install. Detta mål installerar slutprodukten i en systemkatalog för användning. Vanligtvis är denna katalog /usr/local/bin, den traditionella platsen för lokalt byggd programvara. Denna katalog är dock normalt inte skrivbar för vanliga användare, så vi måste bli superanvändare för att kunna utföra installationen.
[me@linuxbox diction-1.11]$ sudo make install
[me@linuxbox diction-1.11]$ which diction
/usr/local/bin/diction
[me@linuxbox diction-1.11]$ man diction
35.3. Sammanfattning
I detta kapitel såg vi hur tre enkla kommandon:
./configure
make
make install
kan användas för att bygga många källkodspaket. Vi såg också den viktiga roll som make spelar i underhållet av program. Programmet make kan användas för vilken uppgift som helst som behöver upprätthålla en relation mellan mål och beroenden, inte bara för att kompilera källkod.
35.4. Vidare läsning
-
Kompilator:
-
Make:
-
GNU Make-manual:
Del 4 - Skriva skalskript
36. 24 - Skriva ditt första skript
I de föregående kapitlen har vi samlat på oss en arsenal av kommandoradsverktyg. Även om dessa verktyg kan lösa många slags datorproblem är vi fortfarande begränsade till att använda dem ett i taget manuellt på kommandoraden. Vore det inte utmärkt om vi kunde få skalet att göra mer av arbetet? Det kan vi. Genom att sammanfoga våra verktyg till program av egen design kan skalet utföra komplicerade sekvenser av uppgifter helt på egen hand. Vi kan få det att göra detta genom att skriva skalskript.
36.1. Vad är skalskript?
Enkelt uttryckt är ett skalskript en fil som innehåller en serie kommandon. Skalet läser denna fil och utför kommandona som om de hade skrivits direkt på kommandoraden.
Skalet är lite speciellt genom att det både är ett kraftfullt kommandoradsgränssnitt till systemet och en tolk för ett skriptspråk. Som vi ska se kan det mesta som kan göras på kommandoraden också göras i skript, och det mesta som kan göras i skript kan också göras på kommandoraden.
36.2. Hur man skriver ett skalskript
För att framgångsrikt skapa och köra ett skalskript behöver vi göra tre saker.
-
Skriva ett skript. Skalskript är vanliga textfiler. Vi behöver alltså en textredigerare för att skriva dem. De bästa textredigerarna ger syntaxmarkering, så att vi kan se skriptets delar i olika färger. Syntaxmarkering hjälper oss att upptäcka vissa vanliga fel.
vim,gedit,kateoch många andra redigerare är bra kandidater för att skriva skript. -
Göra skriptet körbart. Systemet är ganska noga med att inte låta vilken textfil som helst behandlas som ett program, och det av goda skäl. Vi behöver ställa in skriptfilens rättigheter så att den får köras.
-
Placera skriptet där skalet kan hitta det. Skalet söker automatiskt i vissa kataloger efter körbara filer när ingen explicit sökväg anges. För maximal bekvämlighet placerar vi våra skript i en av dessa kataloger.
36.3. Skriptfilens format
I god programmeringstradition ska vi skapa ett Hello World-program för att demonstrera ett ytterst enkelt skript. Låt oss starta våra textredigerare och skriva in följande skript:
#!/bin/bash
# This is our first script.
echo 'Hello World!'
Den sista raden i vårt skript är ganska bekant; det är bara ett echo-kommando med ett strängargument. Den andra raden är också bekant. Den ser ut som en kommentar av det slag vi har sett användas i många av de konfigurationsfiler vi har granskat och redigerat. En sak att veta om kommentarer i skalskript är att de också kan förekomma i slutet av rader, förutsatt att de föregås av minst ett blanktecken.
Den första raden börjar med #!, en speciell konstruktion som kallas shebang. Den talar om för kärnan vilken tolk som ska köra skriptet. Varje skalskript bör ha den som sin första rad.
Kommentarer börjar med #, precis som i många andra konfigurations- och skriptfiler.
echo 'Hello World!' # This is a comment too
Allt från symbolen # och framåt på raden ignoreras.
Precis som så mycket annat fungerar detta även på kommandoraden:
[me@linuxbox ~]$ echo 'Hello World!' # This is a comment too
Hello World!
Låt oss spara vårt skript som hello_world.
36.4. Körbara rättigheter
Nästa sak vi måste göra är att göra vårt skript körbart. Det görs enkelt med chmod.
[me@linuxbox ~]$ ls -l hello_world
-rw-r--r-- 1 me me 63 2025-03-07 10:10 hello_world
[me@linuxbox ~]$ chmod 755 hello_world
[me@linuxbox ~]$ ls -l hello_world
-rwxr-xr-x 1 me me 63 2025-03-07 10:10 hello_world
Det finns två vanliga rättighetsinställningar för skript: 755 för skript som alla kan köra och 700 för skript som bara ägaren kan köra. Observera att skript måste vara läsbara för att kunna köras.
36.5. Skriptfilens plats
När rättigheterna väl är satta kan vi nu köra skriptet.
[me@linuxbox ~]$ ./hello_world
Hello World!
För att skriptet ska köras måste vi föregå skriptnamnet med en explicit sökväg. Om vi inte gör det får vi detta:
[me@linuxbox ~]$ hello_world
bash: hello_world: command not found
Varför blir det så? Vad skiljer vårt skript från andra program? Det visar sig: ingenting. Vårt skript är helt i sin ordning. Problemet är var det ligger. I kapitel 11 diskuterade vi miljövariabeln PATH och dess betydelse för hur systemet söker efter körbara program. För att sammanfatta: systemet söker i en lista med kataloger varje gång det behöver hitta ett körbart program, om ingen explicit sökväg anges. Det är så systemet vet att det ska köra /bin/ls när vi skriver ls på kommandoraden. Katalogen /bin är en av de kataloger som systemet söker i automatiskt. Listan över kataloger lagras i en miljövariabel med namnet PATH. Variabeln PATH innehåller en kolonseparerad lista över kataloger som ska genomsökas. Vi kan visa innehållet i PATH.
[me@linuxbox ~]$ echo $PATH
/home/me/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
Här ser vi vår lista över kataloger. Om vårt skript låg i någon av katalogerna i listan skulle problemet vara löst. Lägg märke till den första katalogen i listan, /home/me/bin. De flesta Linuxdistributioner konfigurerar variabeln PATH så att den innehåller en katalog bin i användarens hemkatalog, för att användare ska kunna köra sina egna program. Så om vi skapar katalogen bin och placerar vårt skript där bör det börja fungera som andra program.
[me@linuxbox ~]$ mkdir bin
[me@linuxbox ~]$ mv hello_world bin
[me@linuxbox ~]$ hello_world
Hello World!
Och det gör det också.
export PATH=~/bin:"$PATH"
Om variabeln PATH inte innehåller katalogen kan vi enkelt lägga till den genom att ta med denna rad i vår fil .bashrc:
[me@linuxbox ~]$ . .bashrc
När denna ändring väl är gjord kommer den att börja gälla i varje ny terminalsession. För att tillämpa ändringen i den aktuella terminalsessionen måste vi få skalet att läsa om filen .bashrc. Detta kan göras genom att sourca den.
Punktkommandot (.) är en synonym till kommandot source, ett inbyggt skalkommando som läser en angiven fil med skalkommandon och behandlar den som indata från tangentbordet.
Ubuntu och de flesta andra Debianbaserade system lägger automatiskt till ~/bin i PATH om katalogen finns när .bashrc körs.
36.5.1. Bra platser för skript
-
Katalogen
~/binär en bra plats för skript avsedda för personligt bruk. -
/usr/local/binär den traditionella platsen för skript som alla användare ska kunna köra. -
/usr/local/sbinanvänds ofta för administratörskript.
Lokalt tillhandahållen programvara bör placeras under /usr/local och inte i /bin eller /usr/bin, som är reserverade för filer som tillhandahålls och underhålls av Linuxdistributören.
36.6. Fler formateringstrick
Ett av huvudmålen med seriöst skriptskrivande är enkelhet i underhåll. Läsbara skript är lättare att ändra.
36.6.1. Långa namn på flaggor
Många kommandon stöder både korta och långa flaggor. På kommandoraden sparar korta flaggor tangenttryckningar, men i skript kan långa flaggor göra koden lättare att läsa:
[me@linuxbox ~]$ ls -ad
[me@linuxbox ~]$ ls --all --directory
36.6.2. Indrag och radfortsättning
Långa kommandon kan spridas ut över flera rader med radfortsättningar för att förbättra läsbarheten.
find playground \
\( \
-type f \
-not -perm 0600 \
-exec chmod 0600 '{}' ';' \
\) \
-or \
\( \
-type d \
-not -perm 0700 \
-exec chmod 0700 '{}' ';' \
\)
Genom att använda radfortsättningar (sekvenser med omvänt snedstreck följt av radmatning) och indrag beskrivs logiken i detta komplexa kommando tydligare för läsaren. Denna teknik fungerar även på kommandoraden, men används sällan där, eftersom den är omständlig att skriva och redigera. En skillnad mellan ett skript och kommandoraden är att skriptet kan använda tabbtecken för att åstadkomma indrag, medan kommandoraden inte kan det eftersom tabbar används för komplettering.
36.6.3. Konfigurera vim för skriptskrivande
Textredigeraren vim har väldigt, väldigt många inställningar. Flera vanliga alternativ kan underlätta när man skriver skript.
:syntax on
:set hlsearch
:set tabstop=4
:set autoindent
Dessa inställningar aktiverar syntaxmarkering, markerar sökresultat, ställer in tabulatorbredd och slår på automatisk indragning. De kan göras permanenta genom att lägga till dem i ~/.vimrc utan det inledande kolonet.
Syntaxmarkering är särskilt användbar för att snabbt upptäcka vissa typer av programmeringsfel och gör skript lättare att läsa under redigering.
36.7. Sammanfattning
I detta första kapitel om skriptskrivande tittade vi på hur skript skrivs och görs lättkörda på vårt system. Vi såg också hur vi kan använda olika formateringstekniker för att förbättra läsbarheten (och därmed underhållbarheten) hos våra skript. I kommande kapitel kommer enkelhet i underhåll att återkomma gång på gång som en central princip för att skriva bra skript.
36.8. Vidare läsning
-
Hello World-exempel: -
Shebang:
37. 25 - Starta ett projekt
Från och med detta kapitel börjar vi bygga ett program. Syftet med projektet är att se hur olika skalfunktioner används för att skapa program och, ännu viktigare, skapa bra program.
Programmet vi ska skriva är en rapportgenerator. Den kommer att presentera olika uppgifter om vårt system och dess status och producera denna rapport i HTML-format, så att vi kan visa den med en webbläsare som Firefox eller Chrome.
Program byggs vanligen upp i en serie steg, där varje steg lägger till funktioner och möjligheter. Det första steget i vårt program kommer att producera ett minimalt HTML-dokument som inte innehåller någon systeminformation. Det kommer senare.
37.1. Första steget: minimalt dokument
Det första vi behöver veta är hur ett välformat HTML-dokument ser ut. Det ser ut så här:
<html>
<head>
<title>Page Title</title>
</head>
<body>
Page body.
</body>
</html>
Om vi skriver in detta i vår textredigerare och sparar filen som foo.html kan vi använda följande URL i Firefox för att visa filen:
file:///home/username/foo.html
Det första steget i vårt program ska kunna skriva ut denna HTML-fil till standardutdata. Vi kan skriva ett program som gör detta ganska enkelt. Låt oss starta textredigeraren och skapa en ny fil med namnet ~/bin/sys_info_page.
#!/bin/bash
# Program to output a system information page
echo "<html>"
echo " <head>"
echo " <title>Page Title</title>"
echo " </head>"
echo " <body>"
echo " Page body."
echo " </body>"
echo "</html>"
Efter att ha sparat filen gör vi den körbar och kör den:
[me@linuxbox ~]$ chmod 755 ~/bin/sys_info_page
[me@linuxbox ~]$ sys_info_page
[me@linuxbox ~]$ sys_info_page > sys_info_page.html
[me@linuxbox ~]$ firefox sys_info_page.html
#!/bin/bash
# Program to output a system information page
echo "<html>
<head>
<title>Page Title</title>
</head>
<body>
Page body.
</body>
</html>"
När programmet körs bör vi se texten i HTML-dokumentet visas på skärmen, eftersom echo-kommandona i skriptet skickar sin utdata till standardutdata. Vi kör programmet igen och styr om programmets utdata till filen sys_info_page.html så att vi kan visa resultatet i en webbläsare.
När en citerad sträng sträcker sig över flera rader fortsätter skalet att läsa indata tills det hittar det avslutande citationstecknet. Skalets prompt > visas om vi skriver en flerradsciterad sträng direkt på kommandoraden. Den kommer från skalvariabeln PS2. Den här funktionen verkar lite obskyr just nu, men senare, när vi går igenom programsatser som sträcker sig över flera rader, kommer den att visa sig vara mycket praktisk.
37.2. Andra steget: lägga till lite data
Nu när vårt program kan generera ett minimalt dokument ska vi lägga in lite data i rapporten. För att göra detta gör vi följande ändringar:
#!/bin/bash
# Program to output a system information page
echo "<html>
<head>
<title>System Information Report</title>
</head>
<body>
<h1>System Information Report</h1>
</body>
</html>"
Vi lade till en sidtitel och en rubrik i rapportens brödtext.
37.3. Variabler och konstanter
Det finns dock ett problem med vårt skript. Lägg märke till hur strängen System Information Report upprepas. I vårt lilla skript är det inget problem, men tänk om skriptet vore riktigt långt och vi hade flera förekomster av denna sträng. Om vi då ville ändra titeln till något annat skulle vi behöva ändra den på flera ställen, vilket kunde innebära mycket arbete. Tänk om vi kunde ordna skriptet så att strängen bara förekom en gång och inte flera gånger? Det skulle göra framtida underhåll av skriptet mycket enklare. Så här skulle vi kunna göra:
#!/bin/bash
# Program to output a system information page
title="System Information Report"
echo "<html>
<head>
<title>$title</title>
</head>
<body>
<h1>$title</h1>
</body>
</html>"
Genom att skapa en variabel med namnet title och tilldela den värdet System Information Report kan vi dra nytta av parameterexpansion och placera strängen på flera ställen.
[me@linuxbox ~]$ foo="yes"
[me@linuxbox ~]$ echo $foo
yes
[me@linuxbox ~]$ echo $fool
[me@linuxbox ~]$
Så hur skapar vi då en variabel? Enkelt - vi använder den bara. När skalet stöter på en variabel skapar det den automatiskt. Detta skiljer sig från många programspråk där variabler måste deklareras eller definieras uttryckligen innan de används. Skalet är mycket tillåtande på denna punkt, vilket kan leda till vissa problem. Tänk till exempel på detta scenario på kommandoraden:
[me@linuxbox ~]$ foo=foo.txt
[me@linuxbox ~]$ foo1=foo1.txt
[me@linuxbox ~]$ cp $foo $fool
cp: missing destination file operand after `foo.txt'
Detta visar varför vi måste vara mycket noga med stavningen. Skalet är mycket tillåtande, och den tillåtligheten kan lätt dölja misstag.
Reglerna för namngivning av variabler är enkla:
-
Namn får innehålla bokstäver, siffror och understreck.
-
Det första tecknet måste vara en bokstav eller ett understreck.
-
Mellanslag och skiljetecken är inte tillåtna.
Skalet skiljer inte formellt mellan variabler och konstanter, men enligt konvention används versaler för konstanter och gemener för vanliga variabler:
#!/bin/bash
# Program to output a system information page
TITLE="System Information Report For $HOSTNAME"
echo "<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>$TITLE</h1>
</body>
</html>"
Skalet kan framtvinga skrivskyddade konstanter med declare -r, även om detta sällan används i vardagliga skript.
declare -r TITLE="Page Title"
37.3.1. Tilldela värden till variabler och konstanter
Det är här vår kunskap om expansion verkligen börjar löna sig. Som vi har sett tilldelas variabler värden så här, där variable är namnet på variabeln och value är en sträng:
variable=value
Till skillnad från vissa andra programspråk bryr sig inte skalet om vilken datatyp som tilldelas en variabel; det behandlar allt som strängar. Det går att tvinga skalet att begränsa tilldelningen till heltal genom att använda kommandot declare med flaggan -i, men liksom att göra variabler skrivskyddade sker detta sällan.
Observera att det i en tilldelning inte får finnas några blanksteg mellan variabelnamnet, likamedtecknet och värdet. Vad får då värdet bestå av? Det kan innehålla allt som vi kan expandera till en sträng.
a=z
b="a string"
c="a string and $b"
d="$(ls -l foo.txt)"
e=$((5 * 7))
f="\t\ta string\n"
a=5 b="a string"
Flera variabeltilldelningar kan göras på en enda rad.
[me@linuxbox ~]$ filename="myfile"
[me@linuxbox ~]$ touch "$filename"
[me@linuxbox ~]$ mv "$filename" "$filename1"
mv: missing destination file operand after `myfile'
[me@linuxbox ~]$ mv "$filename" "${filename}1"
Vid expansion kan variabelnamn omges av valfria klammerparenteser, {}. Detta är användbart i fall där ett variabelnamn blir tvetydigt på grund av sitt sammanhang. Här försöker vi ändra namnet på en fil från myfile till myfile1 med hjälp av en variabel:
Detta försök misslyckas eftersom skalet tolkar det andra argumentet till kommandot mv som en ny (och tom) variabel. Problemet kan lösas så här:
#!/bin/bash
# Program to output a system information page
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME="$(date +"%x %r %Z")"
TIMESTAMP="Generated $CURRENT_TIME, by $USER"
echo "<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>$TITLE</h1>
<p>$TIMESTAMP</p>
</body>
</html>"
37.4. Here documents
Vi har tittat på två olika sätt att skriva ut vår text, båda med kommandot echo. Det finns ett tredje sätt som kallas here document eller here script. Ett here document är en extra form av I/O-omstyrning där vi bäddar in en textkropp i vårt skript och matar den till standardindata för ett kommando. Det fungerar så här:
command << token
text
token
I detta exempel är command namnet på ett kommando som accepterar standardindata, och token är en sträng som används för att markera slutet på den inbäddade texten. Nu ändrar vi vårt skript så att det använder ett here document:
#!/bin/bash
# Program to output a system information page
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME="$(date +"%x %r %Z")"
TIMESTAMP="Generated $CURRENT_TIME, by $USER"
cat << _EOF_
<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>$TITLE</h1>
<p>$TIMESTAMP</p>
</body>
</html>
_EOF_
I stället för att använda echo använder vårt skript nu cat och ett here document. Strängen EOF (som betyder end of file, en vanlig konvention) valdes som token och markerar slutet på den inbäddade texten. Observera att token måste stå ensam och att det inte får finnas några efterföljande blanksteg på raden.
Så vad är fördelen med att använda ett here document? Det är i stort sett detsamma som echo, förutom att enkla och dubbla citationstecken inne i here documents som standard förlorar sin särskilda betydelse för skalet. Här är ett exempel från kommandoraden:
[me@linuxbox ~]$ foo="some text"
[me@linuxbox ~]$ cat << _EOF_
> $foo
> "$foo"
> '$foo'
> \$foo
> _EOF_
some text
"some text"
'some text'
$foo
Som vi ser bryr sig inte skalet om citationstecknen. Det behandlar dem som vanliga tecken. Detta gör att vi kan bädda in citationstecken fritt i ett here document. Det kan visa sig vara praktiskt i vårt rapportprogram.
[me@linuxbox ~]$ foo="some text"
[me@linuxbox ~]$ cat << '_EOF_'
> $foo
> "$foo"
> '$foo'
> \$foo
> _EOF_
$foo
"$foo"
'$foo'
\$foo
Texten inuti ett here document genomgår parameterexpansion, kommandosubstitution och aritmetisk expansion, och de bokstavliga tecknen $ och \ måste därför maskeras med \.
#!/bin/bash
# Script to retrieve a file via FTP
FTP_SERVER="ftp.nl.debian.org"
FTP_PATH="/debian/dists/bookworm/main/installer-amd64/current/images/cdrom"
REMOTE_FILE="debian-cd_info.tar.gz"
ftp -n << _EOF_
open $FTP_SERVER
user anonymous me@linuxbox
cd $FTP_PATH
hash
get $REMOTE_FILE
bye
_EOF_
ls -l "$REMOTE_FILE"
Om vi använder <← i stället för << ignoreras inledande tabbtecken i here-dokumentet, vilket gör att blocket kan indenteras för läsbarhet.
37.5. Sammanfattning
I detta kapitel började vi på ett projekt som ska följa oss genom processen att bygga ett fungerande skript. Vi introducerade begreppet variabler och konstanter och hur de kan användas. De är den första av många tillämpningar vi kommer att hitta för parameterexpansion. Vi tittade också på hur man producerar utdata från sitt skript och på olika metoder för att bädda in textblock.
37.6. Vidare läsning
-
HTML:
-
Manualsidan för
bashhar ett avsnitt omHere Documents.
38. 26 - Top-down-design
När program blir större och mer komplexa blir de också svårare att utforma, koda och underhålla. Som med alla stora projekt är det ofta klokt att bryta ned stora, komplicerade uppgifter i en serie små, enkla uppgifter. Denna process kallas top-down-design.
Låt oss föreställa oss att vi försöker beskriva en vanlig vardagsuppgift - att åka till affären för att köpa mat - för en person från Mars. Vi skulle kunna beskriva den övergripande processen som följande serie steg:
-
Sätt dig i bilen.
-
Kör till affären.
-
Parkera bilen.
-
Gå in i affären.
-
Köp mat.
-
Gå tillbaka till bilen.
-
Kör hem.
-
Parkera bilen.
-
Gå in i huset.
En person från Mars behöver dock sannolikt mer detaljer. Vi skulle kunna bryta ned deluppgiften Park car ytterligare i denna serie steg:
-
Hitta en parkeringsplats.
-
Kör in bilen på platsen.
-
Stäng av motorn.
-
Dra åt handbromsen.
-
Kliv ur bilen.
-
Lås bilen.
Deluppgiften Turn off motor skulle kunna brytas ned ytterligare i steg som att stänga av tändningen, ta ut nyckeln och så vidare, tills varje steg i hela processen har definierats fullständigt.
Denna stegvisa förfining är kärnan i top-down-design. Det är en vanlig metod för att utforma program och en som lämpar sig särskilt väl för skalprogrammering.
I detta kapitel ska vi använda top-down-design för att vidareutveckla vårt rapportgenererande skript.
38.1. Skalfunktioner
Vårt skript utför för närvarande följande steg för att generera HTML-dokumentet:
-
Öppna sidan.
-
Öppna sidrubriken.
-
Ange sidans titel.
-
Stäng sidrubriken.
-
Öppna sidans kropp.
-
Skriv ut sidrubriken.
-
Skriv ut tidsstämpeln.
-
Stäng sidans kropp.
-
Stäng sidan.
I nästa utvecklingssteg ska vi lägga till några uppgifter mellan steg 7 och 8. Dessa ska omfatta följande:
-
Systemets upptid och belastning. Det är den tid som har gått sedan senaste avstängning eller omstart och det genomsnittliga antalet uppgifter som för närvarande körs på processorn över flera tidsintervall.
-
Diskutrymme. Detta är den totala användningen av utrymme på systemets lagringsenheter.
-
Hemutrymme. Detta är mängden lagringsutrymme som används av varje användare.
Om vi hade ett kommando för var och en av dessa uppgifter skulle vi kunna lägga till dem i vårt skript helt enkelt genom kommandosubstitution:
#!/bin/bash
# Program to output a system information page
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME="$(date +"%x %r %Z")"
TIMESTAMP="Generated $CURRENT_TIME, by $USER"
cat << _EOF_
<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>$TITLE</h1>
<p>$TIMESTAMP</p>
$(report_uptime)
$(report_disk_space)
$(report_home_space)
</body>
</html>
_EOF_
Vi skulle kunna skapa dessa extra kommandon på två sätt. Vi skulle kunna skriva tre separata skript och lägga dem i en katalog som finns i vår PATH, eller så skulle vi kunna bädda in skripten i vårt program som skalfunktioner. Som vi tidigare har nämnt är skalfunktioner "miniskript" som ligger inuti andra skript och kan fungera som självständiga program. Skalfunktioner har två vanliga syntaktiska former:
function name {
commands
return
}
name () {
commands
return
}
Båda formerna är likvärdiga och kan användas omväxlande. Den enklare andra formen föredras i allmänhet.
Följande är ett litet skript som demonstrerar användningen av en skalfunktion:
#!/bin/bash
# Shell function demo
function step2 {
echo "Step 2"
return
}
# Main program starts here
echo "Step 1"
step2
echo "Step 3"
När skalet läser skriptet passerar det förbi funktionsdefinitionen, eftersom funktionsdefinitioner inte körs av sig själva. Körningen börjar i huvudprogrammet. När step2 anropas flyttas programstyrningen till funktionen och återkommer sedan till raden efter anropet.
Funktionsdefinitioner måste finnas i skriptet före de anropas, för att funktionsanrop ska kännas igen som skalfunktioner och inte tolkas som namn på externa program.
Vi lägger till minimala definitioner av skalfunktioner i vårt rapportskript:
#!/bin/bash
# Program to output a system information page
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME="$(date +"%x %r %Z")"
TIMESTAMP="Generated $CURRENT_TIME, by $USER"
report_uptime () {
return
}
report_disk_space () {
return
}
report_home_space () {
return
}
cat << _EOF_
<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>$TITLE</h1>
<p>$TIMESTAMP</p>
$(report_uptime)
$(report_disk_space)
$(report_home_space)
</body>
</html>
_EOF_
Namn på skalfunktioner följer samma regler som variabler. En funktion måste innehålla minst ett kommando. Kommandot return (som är valfritt) uppfyller detta krav.
38.2. Lokala variabler
I de skript vi har skrivit hittills har alla variabler (inklusive konstanter) varit globala variabler. Globala variabler fortsätter att existera genom hela programmet. Inuti skalfunktioner är det ofta önskvärt att ha lokala variabler. Lokala variabler är bara åtkomliga inom den skalfunktion där de definieras och i andra funktioner som den funktionen kan anropa. De upphör att existera när skalfunktionen avslutas.
Att ha lokala variabler gör att programmeraren kan använda variabelnamn som kanske redan finns, antingen globalt i skriptet eller i andra skalfunktioner, utan att behöva oroa sig för möjliga namnkonflikter.
Här är ett exempelskript som visar hur lokala variabler definieras och används:
#!/bin/bash
# local-vars: script to demonstrate local variables
foo=0
funct_1 () {
local foo
foo=1
echo "funct_1: foo = $foo"
}
funct_2 () {
local foo
foo=2
echo "funct_2: foo = $foo"
}
echo "global: foo = $foo"
funct_1
echo "global: foo = $foo"
funct_2
echo "global: foo = $foo"
Som vi ser definieras lokala variabler genom att man sätter ordet local framför variabelnamnet. Detta skapar en variabel som är lokal till den skalfunktion där den definieras. Resultatet visar att tilldelningen av värden till den lokala variabeln foo inuti båda skalfunktionerna inte påverkar värdet på foo som definieras utanför funktionerna.
Denna egenskap gör att skalfunktioner kan skrivas så att de förblir oberoende av varandra och av det skript där de förekommer. Det är värdefullt, eftersom det hjälper till att förhindra att en del av ett program stör en annan. Det gör också att skalfunktioner kan skrivas på ett portabelt sätt. Det vill säga att de kan klippas ut och klistras in från skript till skript efter behov.
38.3. Skalfunktioner och omstyrning
Om vi tittar närmare på hur skalfunktioner skrivs kanske vi märker att de liknar gruppkommandon, som vi berörde i kapitel 6, vad gäller omstyrning:
my_funct () {
command1
command2
command3
}
De tre kommandona inuti klammerparenteserna bildar en enda utström. När vi anropar funktionen kan vi styra den samlade utdata:
my_funct > my_directories.txt
my_funct | sort
my_var="$(my_funct)"
my_funct < input.txt
38.4. Håll skripten körbara
När vi utvecklar vårt program är det användbart att hålla programmet i körbart skick. Genom att göra detta och testa ofta kan vi upptäcka fel tidigt i utvecklingsprocessen. Det gör felsökning mycket enklare.
Ett sätt att göra detta är att skapa tomma funktioner, kallade stubs på programmerarspråk, som bevarar den avsedda programgången men ger synlig återkoppling.
Om vi till exempel tittar på utdatan från vårt skript ser vi att det finns några tomma rader i utdatan efter tidsstämpeln, men vi kan inte vara säkra på orsaken. Om vi ändrar funktionerna så att de ger lite återkoppling:
report_uptime () {
echo "Function report_uptime executed."
return
}
report_disk_space () {
echo "Function report_disk_space executed."
return
}
report_home_space () {
echo "Function report_home_space executed."
return
}
ser vi nu att våra tre funktioner faktiskt körs. Detta gör körningsflödet synligt i utdatan. Det är en enkel men effektiv felsökningsteknik medan ett skript fortfarande är under uppbyggnad.
Nu när vårt funktionsramverk är på plats och fungerar är det dags att fylla ut en del av funktionskoden. Först kommer funktionen report_uptime:
report_uptime () {
cat << _EOF_
<h2>System Uptime</h2>
<pre>$(uptime)</pre>
_EOF_
return
}
Det är ganska rakt på sak. Vi använder ett here document för att skriva ut en avsnittsrubrik och utdatan från kommandot uptime, omgiven av taggarna <pre> för att bevara kommandots formatering. Funktionen report_disk_space liknar denna:
report_disk_space () {
cat << _EOF_
<h2>Disk Space Utilization</h2>
<pre>$(df -h)</pre>
_EOF_
return
}
Denna funktion använder kommandot df -h för att avgöra mängden diskutrymme. Till sist bygger vi funktionen report_home_space:
report_home_space () {
cat << _EOF_
<h2>Home Space Utilization</h2>
<pre>$(du -sh /home/*)</pre>
_EOF_
return
}
Vi använder kommandot du med flaggorna -sh för att utföra denna uppgift. Detta är dock inte en fullständig lösning på problemet. Även om det fungerar på vissa system (Ubuntu till exempel) fungerar det inte på andra. Skälet är att många system sätter rättigheterna på hemkataloger så att de inte blir världsläsbara, vilket är en rimlig säkerhetsåtgärd. På sådana system fungerar funktionen report_home_space, sådan den är skriven, bara om vårt skript körs med superanvändarrättigheter. En bättre lösning vore att låta skriptet anpassa sitt beteende efter användarens rättigheter. Vi tar upp detta i nästa kapitel.
38.4.1. Skalfunktioner i din .bashrc
Skalfunktioner är utmärkta ersättare för alias och är faktiskt den föredragna metoden för att skapa små kommandon för personligt bruk. Alias är begränsade, medan skalfunktioner tillåter allt som kan skriptas.
Om vi till exempel gillar skalfunktionen report_disk_space som vi utvecklade för vårt skript, skulle vi kunna skapa en liknande funktion med namnet ds i vår fil .bashrc:
ds () {
echo "Disk Space Utilization For $HOSTNAME"
df -h
}
38.5. Sammanfattning
I detta kapitel introducerade vi en vanlig metod för programutformning som kallas top-down-design, och vi såg hur skalfunktioner används för att bygga den stegvisa förfining som den kräver. Vi såg också hur lokala variabler kan användas för att göra skalfunktioner oberoende av varandra och av det program där de placeras. Detta gör det möjligt att skriva skalfunktioner på ett portabelt sätt och återanvända dem genom att placera dem i flera program; det sparar mycket tid.
38.6. Vidare läsning
-
Top-down-design:
-
Funktion (datorprogrammering):
39. 27 - Flödesstyrning: förgrening med if
I föregående kapitel ställdes vi inför ett problem. Hur kan vi få vårt skript reportgenerator att anpassa sig efter rättigheterna hos den användare som kör skriptet? Lösningen på detta problem kräver att vi hittar ett sätt att "byta riktning" i skriptet utifrån resultatet av ett test. I programmeringstermer behöver programmet förgrena sig.
Låt oss betrakta ett enkelt exempel på logik uttryckt i pseudokod, en simulering av ett datorspråk avsett för människor.
X=5
If X = 5, then:
Say "X equals 5."
Otherwise:
Say "X does not equal 5."
Detta är ett exempel på en förgrening. Vi utvärderar värdet på X, och om X = 5 säger vi X equals 5, annars säger vi X does not equal 5.
39.1. if
Med skalet kan vi koda logiken ovan så här:
x=5
if [ "$x" -eq 5 ]; then
echo "x equals 5."
else
echo "x does not equal 5."
fi
Eller så kan vi skriva in den direkt på kommandoraden (något förkortad):
if commands; then
commands
[elif commands; then
commands...]
[else
commands]
fi
I detta exempel kör vi kommandot två gånger, först med värdet x satt till 5, vilket gör att strängen equals 5 skrivs ut, och sedan med värdet x satt till 0, vilket gör att strängen does not equal 5 skrivs ut.
39.2. Slutstatus
Kommandon (inklusive de skript och skalfunktioner vi själva skriver) lämnar ifrån sig ett värde till systemet när de avslutas, kallat slutstatus. Detta värde, som är ett heltal i intervallet 0 till 255, anger om kommandot lyckades eller misslyckades. Enligt konvention betyder värdet noll att kommandot lyckades, medan alla andra värden betyder fel.
Skalet tillhandahåller en parameter som vi kan använda för att undersöka ett kommandos slutstatus. Här ser vi den i bruk:
[me@linuxbox ~]$ ls -d /usr/bin
/usr/bin
[me@linuxbox ~]$ echo $?
0
[me@linuxbox ~]$ ls -d /bin/usr
ls: cannot access /bin/usr: No such file or directory
[me@linuxbox ~]$ echo $?
2
I detta exempel kör vi kommandot ls två gånger. Första gången körs kommandot utan problem. Om vi visar värdet på parametern $? ser vi att det är noll. Vi kör ls en andra gång (med en katalog som inte finns), vilket ger ett fel, och granskar sedan parametern $? igen. Den här gången innehåller den värdet 2, vilket anger att kommandot stötte på ett fel. Vissa kommandon använder olika slutstatusvärden för att ge diagnostik om fel, medan många kommandon helt enkelt avslutas med värdet 1 när de misslyckas. Manualsidor innehåller ofta ett avsnitt med rubriken Exit Status som beskriver vilka koder som används. Men noll betyder alltid framgång.
Skalet tillhandahåller två extremt enkla inbyggda kommandon som inte gör någonting alls utom att avslutas med antingen 0 eller 1 i slutstatus. Kommandot true lyckas alltid, och kommandot false misslyckas alltid:
[me@linuxbox ~]$ true
[me@linuxbox ~]$ echo $?
0
[me@linuxbox ~]$ false
[me@linuxbox ~]$ echo $?
1
Vi kan använda dessa kommandon för att se hur satsen if fungerar. Det if egentligen gör är att utvärdera om kommandon lyckas eller misslyckas:
[me@linuxbox ~]$ if true; then echo "It's true."; fi
It's true.
[me@linuxbox ~]$ if false; then echo "It's true."; fi
[me@linuxbox ~]$
Om en lista med kommandon följer efter if är det det sista kommandot i listan som utvärderas.
39.3. Använda test
Det kommando som överlägset oftast används tillsammans med if är test. Kommandot test utför en rad olika kontroller och jämförelser. Det har två likvärdiga former:
test expression
[ expression ]
Den andra, mer populära formen med hakparentes, är fortfarande bara ett kommando; det avslutande tecknet ] krävs som dess sista argument. expression är ett uttryck som utvärderas till antingen sant eller falskt. Kommandot test returnerar slutstatus 0 när uttrycket är sant och status 1 när uttrycket är falskt.
Det är intressant att notera att både test och [ faktiskt är kommandon. I bash är de inbyggda, men de finns också som program i /usr/bin för användning med andra skal.
39.3.1. Filuttryck
Tabell 27-1 listar de uttryck som används för att utvärdera filers status.
| Uttryck | Sant om… |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#!/bin/bash
# test-file: Evaluate the status of a file
FILE=~/.bashrc
if [ -e "$FILE" ]; then
if [ -f "$FILE" ]; then
echo "$FILE is a regular file."
fi
if [ -d "$FILE" ]; then
echo "$FILE is a directory."
fi
if [ -r "$FILE" ]; then
echo "$FILE is readable."
fi
if [ -w "$FILE" ]; then
echo "$FILE is writable."
fi
if [ -x "$FILE" ]; then
echo "$FILE is executable/searchable."
fi
else
echo "$FILE does not exist"
exit 1
fi
exit
test_file () {
FILE=~/.bashrc
if [ -e "$FILE" ]; then
if [ -f "$FILE" ]; then
echo "$FILE is a regular file."
fi
if [ -d "$FILE" ]; then
echo "$FILE is a directory."
fi
if [ -r "$FILE" ]; then
echo "$FILE is readable."
fi
if [ -w "$FILE" ]; then
echo "$FILE is writable."
fi
if [ -x "$FILE" ]; then
echo "$FILE is executable/searchable."
fi
else
echo "$FILE does not exist"
return 1
fi
}
39.3.2. Stränguttryck
| Uttryck | Sant om… |
|---|---|
|
|
|
Längden på |
|
Längden på |
|
De två strängarna är lika. |
|
De två strängarna är lika ( |
|
De två strängarna är olika. |
|
|
|
|
uttrycksoperatorerna > och < måste citeras (eller maskeras med omvänt snedstreck) när de används med test. Om de inte gör det tolkas de av skalet som omstyrningsoperatorer, med potentiellt destruktiva resultat. Lägg också märke till att även om dokumentationen för bash säger att sorteringsordningen följer kollationsordningen i den aktuella localen, så behöver den inte göra det. ASCII-ordning (POSIX) används i versioner av bash upp till och med 4.0. Detta problem rättades i version 4.1.
#!/bin/bash
# test-string: evaluate the value of a string
ANSWER=maybe
if [ -z "$ANSWER" ]; then
echo "There is no answer." >&2
exit 1
fi
if [ "$ANSWER" == "yes" ]; then
echo "The answer is YES."
elif [ "$ANSWER" == "no" ]; then
echo "The answer is NO."
elif [ "$ANSWER" == "maybe" ]; then
echo "The answer is MAYBE."
else
echo "The answer is UNKNOWN."
fi
39.3.3. Heltalsuttryck
| Uttryck | Sant om… |
|---|---|
|
Lika. |
|
Inte lika. |
|
Mindre än eller lika med. |
|
Mindre än. |
|
Större än eller lika med. |
|
Större än. |
För att jämföra värden som heltal i stället för som strängar kan vi använda de uttryck som listas i tabell 27-3.
#!/bin/bash
# test-integer: evaluate the value of an integer.
INT=-5
if [ -z "$INT" ]; then
echo "INT is empty." >&2
exit 1
fi
if [ "$INT" -eq 0 ]; then
echo "INT is zero."
else
if [ "$INT" -lt 0 ]; then
echo "INT is negative."
else
echo "INT is positive."
fi
if [ $((INT % 2)) -eq 0 ]; then
echo "INT is even."
else
echo "INT is odd."
fi
fi
39.4. En modernare version av test
Moderna versioner av bash innehåller ett sammansatt kommando som fungerar som en utökad ersättning för test. Det använder följande syntax där, liksom med test, expression är ett uttryck som utvärderas till antingen sant eller falskt:
[[ expression ]]
Kommandot [[ ]] liknar test (det stöder alla dess uttryck) men lägger till ett viktigt nytt stränguttryck:
string1 =~ regex
Detta returnerar sant om string1 matchas av det utökade reguljära uttrycket regex. Detta öppnar många möjligheter för att utföra uppgifter som datavalidering. I vårt tidigare exempel med heltalsuttryck skulle skriptet misslyckas om konstanten INT innehöll något annat än ett heltal. Skriptet behöver ett sätt att verifiera att konstanten innehåller ett heltal. Med [[ ]] och operatorn =~ för stränguttryck kan vi förbättra skriptet så här:
Genom att använda det reguljära uttrycket kan vi begränsa värdet på INT till endast strängar som börjar med ett valfritt minustecken, följt av en eller flera siffror. Detta uttryck eliminerar också möjligheten att få tomma värden.
#!/bin/bash
# test-integer2: evaluate the value of an integer.
INT=-5
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
if [ "$INT" -eq 0 ]; then
echo "INT is zero."
else
if [ "$INT" -lt 0 ]; then
echo "INT is negative."
else
echo "INT is positive."
fi
if [ $((INT % 2)) -eq 0 ]; then
echo "INT is even."
else
echo "INT is odd."
fi
fi
else
echo "INT is not an integer." >&2
exit 1
fi
En annan tillagd funktion i [[ ]] är att operatorn == stöder mönstermatchning på samma sätt som sökvägsexpansion gör. Här är ett exempel:
[me@linuxbox ~]$ FILE=foo.bar
[me@linuxbox ~]$ if [[ $FILE == foo.* ]]; then
> echo "$FILE matches pattern 'foo.*'"
> fi
foo.bar matches pattern 'foo.*'
39.5. - utformat för heltal
Förutom det sammansatta kommandot [[ ]] tillhandahåller bash också det sammansatta kommandot , som är användbart vid arbete med heltal. Det stöder en fullständig uppsättning aritmetiska utvärderingar, ett ämne som vi ska gå igenom ordentligt i kapitel 34.
[me@linuxbox ~]$ if ((1)); then echo "It is true."; fi
It is true.
[me@linuxbox ~]$ if ((0)); then echo "It is true."; fi
[me@linuxbox ~]$
används för att utföra aritmetiska sanningsprov. Ett sådant test blir sant om resultatet av den aritmetiska utvärderingen inte är noll.
#!/bin/bash
# test-integer2a: evaluate the value of an integer.
INT=-5
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
if ((INT == 0)); then
echo "INT is zero."
else
if ((INT < 0)); then
echo "INT is negative."
else
echo "INT is positive."
fi
if ((((INT % 2)) == 0)); then
echo "INT is even."
else
echo "INT is odd."
fi
fi
else
echo "INT is not an integer." >&2
exit 1
fi
Med kan vi förenkla skriptet test-integer2 något på detta sätt:
39.5.1. Portabilitet är de små sinnenas skräck
Om man pratar med "riktiga" Unixfolk upptäcker man snabbt att många av dem inte tycker särskilt bra om Linux. De betraktar det som orent och smutsigt. En trossats bland Unixanvändare är att allting bör vara portabelt, det vill säga att alla skript man skriver bör kunna köras oförändrade på vilket Unix-liknande system som helst.
Unixfolket har goda skäl att tro detta. De har sett vad proprietära tillägg till kommandon och skal ställde till med i Unixvärlden före POSIX, och de är naturligt vaksamma mot vilken effekt Linux kan ha på deras älskade operativsystem.
Men portabilitet har en allvarlig nackdel: den förhindrar framsteg. Den kräver att allting alltid görs med lägsta-gemensamma-nämnarens teknik. Inom skalprogrammering innebär det att allting ska vara kompatibelt med sh, det ursprungliga Bourne-skalet.
GNU-verktygen, som bash, har inga sådana begränsningar. De uppmuntrar portabilitet genom att stödja standarder och genom att vara brett tillgängliga. Du kan installera bash och de andra GNU-verktygen på nästan alla typer av system, till och med Windows, helt utan kostnad. Så känn dig fri att använda alla funktioner i bash. Det är faktiskt portabelt.
39.6. Kombinera uttryck
Det går också att kombinera uttryck för att skapa mer komplexa utvärderingar. Uttryck kombineras med logiska operatorer. Vi såg dessa i kapitel 17 när vi lärde oss om kommandot find. Det finns tre logiska operationer för test och [[ ]]: AND, OR och NOT. test och [[ ]] använder olika operatorer för att representera dessa operationer:
| Operation | test |
[[ ]] och |
|---|---|---|
AND |
|
|
OR |
|
` |
` |
NOT |
Här är ett exempel på en AND-operation. Följande skript avgör om ett heltal ligger inom ett värdeintervall:
#!/bin/bash
# test-integer3: determine if an integer is within a specified range of values.
MIN_VAL=1
MAX_VAL=100
INT=50
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
if [[ "$INT" -ge "$MIN_VAL" && "$INT" -le "$MAX_VAL" ]]; then
echo "$INT is within $MIN_VAL to $MAX_VAL."
else
echo "$INT is out of range."
fi
else
echo "INT is not an integer." >&2
exit 1
fi
I detta skript avgör vi om värdet på heltalet INT ligger mellan värdena MIN_VAL och MAX_VAL. Detta görs med en enda användning av [[ ]], som innehåller två uttryck åtskilda av operatorn &&. Vi hade också kunnat skriva detta med test:
if [ "$INT" -ge "$MIN_VAL" -a "$INT" -le "$MAX_VAL" ]; then
echo "$INT is within $MIN_VAL to $MAX_VAL."
else
echo "$INT is out of range."
fi
Negationsoperatorn ! vänder på resultatet av ett uttryck. Den returnerar sant om ett uttryck är falskt, och falskt om ett uttryck är sant. I följande skript ändrar vi logiken i vår utvärdering för att hitta värden på INT som ligger utanför det angivna intervallet:
#!/bin/bash
# test-integer4: determine if an integer is outside a specified range of values.
MIN_VAL=1
MAX_VAL=100
INT=50
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
if [[ !( "$INT" -ge "$MIN_VAL" && "$INT" -le "$MAX_VAL" ) ]]; then
echo "$INT is outside $MIN_VAL to $MAX_VAL."
else
echo "$INT is in range."
fi
else
echo "INT is not an integer." >&2
exit 1
fi
Vi tar också med parenteser runt uttrycket för gruppering. Om dessa inte fanns skulle negationen bara gälla det första uttrycket och inte kombinationen av de två. Att skriva detta med test skulle göras så här:
Eftersom alla uttryck och operatorer som används av test behandlas som kommandoargument av skalet (till skillnad från med [[ ]] och ), måste tecken som har särskild betydelse för bash, såsom <, >, ( och ), citeras eller maskeras.
39.7. Styroperatorer: ett annat sätt att förgrena
bash tillhandahåller två styroperatorer som kan utföra förgrening. Operatorerna && (AND) och || (OR) fungerar som de logiska operatorerna i det sammansatta kommandot [[ ]]. Här är syntaxen för &&:
command1 && command2
command1 || command2
Här är syntaxen för ||:
Det är viktigt att förstå hur dessa fungerar. Med operatorn && körs alltid command1, och command2 körs om, och endast om, command1 lyckas. Med operatorn || körs alltid command1, och command2 körs om, och endast om, command1 misslyckas.
[me@linuxbox ~]$ mkdir temp && cd temp
[me@linuxbox ~]$ [[ -d temp ]] || mkdir temp
I praktiken innebär detta att vi kan göra något i stil med detta:
[ -d temp ] || exit 1
{ true && echo "true"; } && { false || echo "false"; }
Gruppkommandon returnerar slutstatusen för det sista kommandot i gruppen.
39.8. Sammanfattning
Vi började detta kapitel med en fråga. Hur skulle vi kunna få vårt skript sys_info_page att upptäcka om användaren hade rätt att läsa alla hemkataloger? Med vår kunskap om if kan vi lösa problemet genom att lägga till denna kod i funktionen report_home_space:
report_home_space () {
if [[ "$(id -u)" -eq 0 ]]; then
cat << _EOF_
<h2>Home Space Utilization (All Users)</h2>
<pre>$(du -sh /home/*)</pre>
_EOF_
else
cat << _EOF_
<h2>Home Space Utilization ($USER)</h2>
<pre>$(du -sh $HOME)</pre>
_EOF_
fi
return
}
Vi utvärderar utdatan från kommandot id. Med flaggan -u skriver id ut det numeriska användar-ID-numret för den effektiva användaren. Superanvändaren har alltid ID noll, och varje annan användare har ett tal större än noll. Med den kunskapen kan vi konstruera två olika here documents, ett som drar nytta av superanvändarrättigheter och ett annat som är begränsat till användarens egen hemkatalog.
39.9. Vidare läsning
Det finns flera avsnitt i bash-manualsidan som ger ytterligare detaljer om de ämnen som tas upp i detta kapitel:
-
Lists(täcker styroperatorerna||och&&) -
Compound Commands(täcker[[ ]],ochif) -
CONDITIONAL EXPRESSIONS -
SHELL BUILTIN COMMANDS(täckertest)
Dessutom har Wikipedia en bra artikel om begreppet pseudokod:
40. 28 - Läsa tangentbordsindata
De skript vi har skrivit hittills saknar en egenskap som är vanlig i de flesta datorprogram - interaktivitet, alltså förmågan hos programmet att samspela med användaren. Även om många program inte behöver vara interaktiva finns det sådana som har nytta av att kunna ta emot indata direkt från användaren. Ta till exempel detta skript från föregående kapitel:
#!/bin/bash
# test-integer2: evaluate the value of an integer.
INT=-5
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
if [ "$INT" -eq 0 ]; then
echo "INT is zero."
else
if [ "$INT" -lt 0 ]; then
echo "INT is negative."
else
echo "INT is positive."
fi
if [ $((INT % 2)) -eq 0 ]; then
echo "INT is even."
else
echo "INT is odd."
fi
fi
else
echo "INT is not an integer." >&2
exit 1
fi
Varje gång vi vill ändra värdet på INT måste vi redigera skriptet. Det vore mycket mer användbart om skriptet kunde be användaren om ett värde. I detta kapitel börjar vi titta på hur vi kan lägga till interaktivitet i våra program.
40.1. read - läs värden från standardindata
Det inbyggda kommandot read används för att läsa en enda rad från standardindata. Detta kommando kan användas för att läsa tangentbordsindata eller, när omstyrning används, en rad data från en fil. Kommandot har följande syntax:
read [-options] [variable...]
här är options en eller flera av de tillgängliga flaggor som listas senare i tabell 28-1, och variable är namnet på en eller flera variabler som används för att lagra indatavärdet. Om inget variabelnamn anges kommer skalvariabeln REPLY att innehålla dataraden.
I grunden tilldelar read fält från standardindata till de angivna variablerna. Om vi ändrar vårt skript för heltalsutvärdering så att det använder read, kan det se ut så här:
#!/bin/bash
# read-integer: evaluate the value of an integer.
echo -n "Please enter an integer -> "
read int
if [[ "$int" =~ ^-?[0-9]+$ ]]; then
if [ "$int" -eq 0 ]; then
echo "$int is zero."
else
if [ "$int" -lt 0 ]; then
echo "$int is negative."
else
echo "$int is positive."
fi
if [ $((int % 2)) -eq 0 ]; then
echo "$int is even."
else
echo "$int is odd."
fi
fi
else
echo "Input value is not an integer." >&2
exit 1
fi
Vi använder echo med flaggan -n (som undertrycker avslutande nyrad i utdatan) för att visa en prompt, och sedan använder vi read för att läsa in ett värde till variabeln int. När detta skript körs får vi detta resultat:
[me@linuxbox ~]$ read-integer
Please enter an integer -> 5
5 is positive.
5 is odd.
40.2. Läsa flera värden
read kan tilldela indata till flera variabler, som visas i detta skript:
#!/bin/bash
# read-multiple: read multiple values from keyboard
echo -n "Enter one or more values > "
read var1 var2 var3 var4 var5
echo "var1 = '$var1'"
echo "var2 = '$var2'"
echo "var3 = '$var3'"
echo "var4 = '$var4'"
echo "var5 = '$var5'"
Om read får färre värden än väntat blir de extra variablerna tomma, medan ett överskott av indata gör att den sista variabeln innehåller all återstående indata.
Om inga variabler anges efter kommandot read kommer skalvariabeln REPLY att tilldelas all indata.
#!/bin/bash
# read-single: read multiple values into default variable
echo -n "Enter one or more values > "
read
echo "REPLY = '$REPLY'"
40.3. Flaggor
read stöder de flaggor som beskrivs i tabell 28-1.
| Flagga | Beskrivning |
|---|---|
|
Tilldela indatan till |
|
Använd det första tecknet i |
|
Använd Readline för redigering av indata. |
|
Använd |
|
Läs |
|
Visa |
|
Råläge. Tolka inte omvända snedstreck som escape-tecken. |
|
Tyst läge. Eka inte skrivna tecken. |
|
Tidsgräns efter |
|
Använd indata från filbeskrivare |
Exempel med prompt:
#!/bin/bash
# read-single: read multiple values into default variable
read -r -p "Enter one or more values > "
echo "REPLY = '$REPLY'"
Hemlig indata med tidsgräns:
#!/bin/bash
# read-secret: input a secret passphrase
if read -r -t 10 -sp "Enter secret passphrase > " secret_pass; then
echo -e "\nSecret passphrase = '$secret_pass'"
else
echo -e "\nInput timed out" >&2
exit 1
fi
Exempel med standardvärde:
#!/bin/bash
# read-default: supply a default value if user presses Enter key.
read -e -p "What is your user name? " -i $USER
echo "You answered: '$REPLY'"
40.4. IFS
Normalt utför skalet orduppdelning på den indata som ges till read. Som vi har sett betyder detta att flera ord som skiljs åt av ett eller flera blanksteg blir separata poster på indataraden och tilldelas olika variabler av read. Detta beteende styrs av en skalvariabel med namnet IFS (för Internal Field Separator). Standardvärdet i IFS innehåller ett blanksteg, en tabb och ett nyradstecken, och vart och ett av dessa skiljer poster från varandra.
Vi kan justera värdet på IFS för att styra hur fält separeras i indata till read. Filen /etc/passwd innehåller till exempel datarader som använder kolon som fältavgränsare. Genom att ändra värdet på IFS till ett enda kolon kan vi använda read för att läsa innehållet i /etc/passwd och framgångsrikt dela upp fält i olika variabler. Här är ett skript som gör just det:
#!/bin/bash
# read-ifs: read fields from a file
FILE=/etc/passwd
read -r -p "Enter a username > " user_name
file_info="$(grep "^$user_name:" $FILE)"
if [ -n "$file_info" ]; then
IFS=":" read -r user pw uid gid name home shell <<< "$file_info"
echo "User = '$user'"
echo "UID = '$uid'"
echo "GID = '$gid'"
echo "Full Name = '$name'"
echo "Home Dir. = '$home'"
echo "Shell = '$shell'"
else
echo "No such user '$user_name'" >&2
exit 1
fi
Detta skript ber användaren att skriva in användarnamnet på ett konto på systemet och visar sedan de olika fält som finns i användarens post i filen /etc/passwd. Skriptet innehåller två intressanta rader. Den första är denna:
IFS=":" read -r user pw uid gid name home shell <<< "$file_info"
Raden består av tre delar:
-
en variabeltilldelning som tillfälligt ändrar
IFStill ett kolon -
ett
read-kommando med en lista av variabelnamn som argument -
en here string (
<<<) som matar in data
Skalet tillåter att en eller flera variabeltilldelningar görs omedelbart före ett kommando. Dessa tilldelningar ändrar miljön för det kommando som följer. Effekten av tilldelningen är tillfällig och ändrar bara miljön under kommandots körning. I vårt fall ändras värdet på IFS till ett kolon. Vi skulle också ha kunnat spara värdet på IFS, tilldela ett nytt värde, köra kommandot read och sedan återställa IFS till sitt ursprungliga värde. Det är uppenbart att det är mer kortfattat att placera variabeltilldelningen framför kommandot för att göra samma sak.
40.4.1. Du kan inte pipea read
Även om kommandot read normalt tar emot indata från standardindata kan du inte göra så här:
echo "foo" | read
Man skulle kunna tro att detta borde fungera, men det gör det inte. Kommandot ser ut att lyckas, men variabeln REPLY kommer alltid att vara tom. Varför är det så?
Förklaringen har att göra med hur skalet hanterar pipelines. I bash (och andra skal som sh) skapar pipelines underskal. Dessa är kopior av skalet och dess miljö som används för att köra kommandot i pipelinen. I vårt tidigare exempel körs read i ett underskal.
40.5. Validera indata
Med vår nyvunna förmåga att ta emot tangentbordsindata kommer också en extra programmeringsutmaning: att validera indata. Ofta ligger skillnaden mellan ett välskrivet program och ett dåligt skrivet i programmets förmåga att hantera det oväntade. Ofta uppträder det oväntade i form av dåliga indata. Vi gjorde lite av detta redan med våra utvärderingsprogram i föregående kapitel, där vi kontrollerade heltalsvärden och filtrerade bort tomma värden och icke-numeriska tecken. Det är viktigt att utföra sådana programmeringskontroller varje gång ett program tar emot indata, för att skydda sig mot ogiltiga data.
Detta är särskilt viktigt för program som delas av flera användare. Att utelämna sådana skydd av bekvämlighetsskäl kan kanske ursäktas om ett program bara ska användas en enda gång och bara av författaren själv för en särskild uppgift. Men även då, om programmet utför farliga uppgifter som att radera filer, vore det klokt att ta med datavalidering - för säkerhets skull.
Här är ett exempelprogram som validerar olika slags indata:
#!/bin/bash
# read-validate: validate input
invalid_input () {
echo "Invalid input '$REPLY'" >&2
exit 1
}
read -r -p "Enter a single item > "
[[ -z "$REPLY" ]] && invalid_input
(( "$(echo "$REPLY" | wc -w)" > 1 )) && invalid_input
if [[ "$REPLY" =~ ^[-[:alnum:]\._]+$ ]]; then
echo "'$REPLY' is a valid filename."
if [[ -e "$REPLY" ]]; then
echo "And file '$REPLY' exists."
else
echo "However, file '$REPLY' does not exist."
fi
if [[ "$REPLY" =~ ^-?[[:digit:]]*\.[[:digit:]]+$ ]]; then
echo "'$REPLY' is a floating point number."
else
echo "'$REPLY' is not a floating point number."
fi
if [[ "$REPLY" =~ ^-?[[:digit:]]+$ ]]; then
echo "'$REPLY' is an integer."
else
echo "'$REPLY' is not an integer."
fi
else
echo "The string '$REPLY' is not a valid filename."
fi
40.6. Menyer
En vanlig typ av interaktivitet kallas menybaserad. I menybaserade program presenteras användaren för en lista över val och ombeds att välja ett. Vi skulle till exempel kunna tänka oss ett program som presenterar följande:
Please Select:
1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
Enter selection [0-3] >
Med hjälp av det vi lärde oss när vi skrev vårt program sys_info_page kan vi bygga ett menybaserat program som utför uppgifterna i menyn ovan.
#!/bin/bash
# read-menu: a menu driven system information program
clear
echo "
Please Select:
1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
"
read -r -p "Enter selection [0-3] > "
if [[ "$REPLY" =~ ^[0-3]$ ]]; then
if [[ "$REPLY" == 0 ]]; then
echo "Program terminated."
exit
fi
if [[ "$REPLY" == 1 ]]; then
echo "Hostname: $HOSTNAME"
uptime
exit
fi
if [[ "$REPLY" == 2 ]]; then
df -h
exit
fi
if [[ "$REPLY" == 3 ]]; then
if [[ "$(id -u)" -eq 0 ]]; then
echo "Home Space Utilization (All Users)"
du -sh /home/*
else
echo "Home Space Utilization ($USER)"
du -sh "$HOME"
fi
exit
fi
else
echo "Invalid entry." >&2
exit 1
fi
Detta skript är logiskt uppdelat i två delar. Den första delen visar menyn och läser in användarens svar. Den andra delen identifierar svaret och utför den valda åtgärden. Lägg märke till användningen av kommandot exit i detta skript. Det används här för att förhindra att skriptet kör onödig kod efter att en åtgärd har utförts. Förekomsten av flera utgångspunkter i ett program är i allmänhet ingen bra idé (det gör programlogiken svårare att förstå), men det fungerar i detta skript. I nästa kapitel ska vi bygga vidare på konceptet med menybaserade program och göra det ännu bättre.
40.7. Sammanfattning
I detta kapitel tog vi våra första steg mot interaktivitet och gjorde det möjligt för användare att mata in data i våra program via tangentbordet. Med de tekniker som vi gått igenom hittills går det att skriva många användbara program, såsom specialiserade beräkningsprogram och lättanvända gränssnitt till svårhanterliga kommandoradsverktyg.
40.8. Extrapoäng
Det är viktigt att noggrant studera programmen i detta kapitel och ha en fullständig förståelse för hur de är logiskt uppbyggda, eftersom de program som följer blir allt mer komplexa. Som övning kan du skriva om programmen i detta kapitel med kommandot test i stället för det sammansatta kommandot [[ ]]. Tips: använd grep för att utvärdera de reguljära uttrycken och bedöm slutstatus. Det blir bra övning.
40.9. Vidare läsning
-
The Bash Reference Manualinnehåller ett kapitel om inbyggda kommandon, där kommandotreadingår:
41. 29 - Flödesstyrning: slingor med while / until
I föregående kapitel utvecklade vi ett menybaserat program som producerar olika slags systeminformation. Programmet fungerar, men det har fortfarande ett betydande användbarhetsproblem. Det utför bara ett enda val och avslutas sedan. Ännu värre är att om ett ogiltigt val görs avslutas programmet med ett fel, utan att ge användaren en chans att försöka igen. Det vore bättre om vi på något sätt kunde bygga programmet så att det kunde upprepa menyvisningen och valet om och om igen, tills användaren väljer att avsluta programmet.
I detta kapitel ska vi titta på ett programmeringsbegrepp som kallas slingor, vilket kan användas för att få delar av program att upprepas. Skalet tillhandahåller tre sammansatta kommandon för slingor. Vi ska titta på två av dem i detta kapitel och det tredje i ett senare kapitel.
41.1. Slingor
Vardagslivet är fullt av upprepade aktiviteter. Att gå till jobbet varje dag, rasta hunden och skiva en morot är alla uppgifter som innebär att man upprepar en serie steg. Låt oss betrakta att skiva en morot. Om vi uttrycker denna aktivitet i pseudokod skulle det kunna se ut ungefär så här:
1. get cutting board
2. get knife
3. place carrot on cutting board
4. lift knife
5. advance carrot
6. slice carrot
7. if entire carrot sliced, then quit; else go to step 4
Steg 4 till 7 bildar en slinga. Åtgärderna i slingan upprepas tills villkoret entire carrot sliced har uppnåtts.
41.1.1. while
bash kan uttrycka en liknande idé. Anta att vi vill visa fem tal i ordningsföljd från 1 till 5. Ett bash-skript skulle kunna konstrueras så här:
#!/bin/bash
# while-count: display a series of numbers
count=1
while [[ "$count" -le 5 ]]; do
echo "$count"
count=$((count + 1))
done
echo "Finished."
När skriptet körs blir resultatet följande:
[me@linuxbox ~]$ while-count
1
2
3
4
5
Finished.
Syntaxen för kommandot while är följande:
while commands; do commands; done
Liksom if utvärderar while slutstatusen för en lista kommandon. Så länge slutstatusen är noll utför det kommandona inne i slingan. I skriptet ovan skapas variabeln count och tilldelas ett startvärde på 1. Kommandot while utvärderar slutstatusen för det sammansatta kommandot [[ ]]. Så länge [[ ]] returnerar slutstatus noll körs kommandona i slingan. Vid slutet av varje varv upprepas kommandot [[ ]]. Efter fem varv har värdet på count ökat till 6, [[ ]] returnerar inte längre slutstatus noll och slingan avslutas. Programmet fortsätter med nästa sats efter slingan.
41.2. Förbättra menyn med while
Vi kan använda en while-slinga för att förbättra programmet read-menu från föregående kapitel. Genom att lägga menyn inuti en while-slinga kan vi få programmet att upprepa menyvisningen efter varje val. Slingan fortsätter så länge REPLY inte är lika med 0, och menyn visas igen så att användaren får möjlighet att göra ytterligare ett val. I slutet av varje åtgärd körs kommandot sleep, så att programmet pausar i några sekunder och resultatet av valet hinner synas innan skärmen rensas och menyn visas igen. När REPLY blir lika med 0, vilket anger valet quit, avslutas slingan och körningen fortsätter med raden efter done.
#!/bin/bash
# while-menu: a menu driven system information program
DELAY=3 # Number of seconds to display results
while [[ "$REPLY" != 0 ]]; do
clear
cat << _EOF_
Please Select:
1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
_EOF_
read -r -p "Enter selection [0-3] > "
if [[ "$REPLY" =~ ^[0-3]$ ]]; then
if [[ $REPLY == 1 ]]; then
echo "Hostname: $HOSTNAME"
uptime
sleep "$DELAY"
fi
if [[ "$REPLY" == 2 ]]; then
df -h
sleep "$DELAY"
fi
if [[ "$REPLY" == 3 ]]; then
if [[ "$(id -u)" -eq 0 ]]; then
echo "Home Space Utilization (All Users)"
du -sh /home/*
else
echo "Home Space Utilization ($USER)"
du -sh "$HOME"
fi
sleep "$DELAY"
fi
else
echo "Invalid entry."
sleep "$DELAY"
fi
done
echo "Program terminated."
41.2.1. break och continue
bash tillhandahåller två inbyggda kommandon som kan användas för att styra programflödet inuti slingor. Kommandot break avslutar omedelbart en slinga, och programstyrningen fortsätter med nästa sats efter slingan. Kommandot continue gör att resten av slingan hoppas över, och programstyrningen fortsätter med nästa varv i slingan. Här ser vi en version av programmet while-menu som använder både break och continue:
#!/bin/bash
# while-menu2: a menu driven system information program
DELAY=3 # Number of seconds to display results
while true; do
clear
cat << _EOF_
Please Select:
1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
_EOF_
read -p "Enter selection [0-3] > "
if [[ "$REPLY" =~ ^[0-3]$ ]]; then
if [[ "$REPLY" == 1 ]]; then
echo "Hostname: $HOSTNAME"
uptime
sleep "$DELAY"
continue
fi
if [[ "$REPLY" == 2 ]]; then
df -h
sleep "$DELAY"
continue
fi
if [[ "$REPLY" == 3 ]]; then
if [[ "$(id -u)" -eq 0 ]]; then
echo "Home Space Utilization (All Users)"
du -sh /home/*
else
echo "Home Space Utilization ($USER)"
du -sh "$HOME"
fi
sleep "$DELAY"
continue
fi
if [[ "$REPLY" == 0 ]]; then
break
fi
else
echo "Invalid entry."
sleep "$DELAY"
fi
done
echo "Program terminated."
I denna version av skriptet sätter vi upp en ändlös slinga (en som aldrig avslutas av sig själv) genom att använda kommandot true för att ge while en slutstatus. Eftersom true alltid avslutas med slutstatus noll kommer slingan aldrig att ta slut. Detta är en förvånansvärt vanlig skriptteknik. Eftersom slingan aldrig avslutas av sig själv måste programmeraren själv tillhandahålla ett sätt att bryta sig ur slingan när tiden är inne. I detta skript används kommandot break för att lämna slingan när valet 0 görs. Kommandot continue har lagts till i slutet av de andra valen för att möjliggöra effektivare körning. Genom att använda continue hoppar skriptet över kod som inte behövs när ett val väl har identifierats. Om till exempel valet 1 görs och identifieras finns det ingen anledning att testa de övriga valen.
41.2.2. select
Det här är ett bra tillfälle att nämna det inbyggda skalkommandot select, som används för att skapa slingande menyer. Det har en syntax som ser ut så här, där var är en variabel och string är texten i ett menyval:
select var in [string... ;] do commands; done
När select körs visar det strängen/strängarna följda av innehållet i variabeln PS3 (prompt string 3) som prompt för användarens inmatning. När ett val har gjorts sätter det variabeln REPLY till användarens inmatning (precis som read) och returnerar strängen som hör till valet i variabeln var. När värdena väl är satta utförs kommandona, och prompten visas igen för ytterligare ett val. Det låter kanske lite förvirrande, men vi kan visa det med detta lilla skript:
#!/bin/bash
# select-demo: select builtin demo
PS3="
Your choice: "
select my_choice in First Second Third Fourth Quit; do
echo "REPLY= $REPLY my_choice= $my_choice"
[[ "$my_choice" == "Quit" ]] && break
done
Först sätter vi innehållet i variabeln PS3 till den promptsträng vi vill ha. Därefter kör vi select. I detta exempel har vi fem strängar, och även om vi har använt enstaka ord som våra strängar skulle vi kunna använda vilken citerad text som helst. Som kommandon ekar vi helt enkelt de tilldelningar som select gör. Vi testar också innehållet i variabeln my_choice för att se om användaren har valt alternativet Quit, och om så är fallet utför vi ett break för att lämna slingan.
När select körs första gången visar det var och en av våra strängar föregångna av ett nummer, följt av vår promptsträng. Användaren skriver därefter in numret som motsvarar det önskade valet. Kommandot select sätter då variabeln REPLY till det användaren skrev in och den motsvarande strängen, om någon.
Här ser vi att användaren skrev in 1, och echo-kommandot visar värdena i variablerna REPLY och my_choice. select fortsätter att visa promptsträngen tills användaren skriver in en 5. Om användaren matar in ett ogiltigt värde sätter select my_choice till en tom sträng. Om användaren bara trycker ENTER börjar select om och visar listan med menyval igen.
Slingan select fortsätter i det oändliga tills antingen ett kommando break påträffas eller användaren skriver Ctrl-d för att signalera filslut.
En intressant egenskap hos select är att det inte visar sina menyval eller sin promptsträng på standardutdata; i stället använder det standardfel. Det är faktiskt praktiskt, eftersom det gör att det verkliga arbetet som kommandona i slingan utför kan styras om. Här är ett exempel:
[me@linuxbox ~]$ select-demo > choices.txt
När vi gör denna omstyrning visas menyn och prompten fortfarande, men utdatan från kommandot echo styrs om.
Låt oss göra en alternativ version av vårt systeminformationsskript där vi ersätter vår tidigare while-slinga med select.
#!/bin/bash
# select-menu: a menu driven system information program
DELAY=3 # Number of seconds to display results
PS3="
Enter selection [1-4] > "
select str in \
"Display System Information" \
"Display Disk Space" \
"Display Home Space Utilization" \
"Quit"; do
if [[ "$REPLY" == "1" ]]; then
echo "Hostname: $HOSTNAME"
uptime
sleep "$DELAY"
continue
fi
if [[ "$REPLY" == "2" ]]; then
df -h
sleep "$DELAY"
continue
fi
if [[ "$REPLY" == "3" ]]; then
if [[ $(id -u) -eq 0 ]]; then
echo "Home Space Utilization (All Users)"
du -sh /home/* 2> /dev/null
else
echo "Home Space Utilization ($USER)"
du -sh "$HOME" 2> /dev/null
fi
sleep "$DELAY"
continue
fi
if [[ "$REPLY" == "4" ]]; then
break
fi
if [[ -z "$str" ]]; then
echo "Invalid entry."
sleep "$DELAY"
fi
done
echo "Program terminated."
I vårt alternativa skript sätter vi variabeln PS3 och anropar sedan select med fyra strängar. Även om vi därefter skulle kunna testa variabeln str som sätts av select, är det enklare att testa variabeln REPLY och agera därefter. I slutet av slingan kontrollerar vi om variabeln str har längden noll, vilket anger ett ogiltigt värde.
Så vilken metod ska vi använda för att konstruera en meny? Kommandot select är intressant, men bortsett från att det använder standardfel för menyvisningen sparar det oss inte särskilt mycket kodarbete, om ens något alls, och det begränsar utformningen av menyn ganska kraftigt.
41.2.3. until
Kommandot until liknar while, men i stället för att lämna en slinga när en slutstatus som inte är noll påträffas gör det motsatsen. En until-slinga fortsätter tills den får slutstatus noll. I vårt skript while-count fortsatte vi slingan så länge värdet i variabeln count var mindre än eller lika med 5. Vi kan få samma resultat genom att skriva skriptet med until.
#!/bin/bash
# until-count: display a series of numbers
count=1
until [[ "$count" -gt 5 ]]; do
echo "$count"
count=$((count + 1))
done
echo "Finished."
Genom att ändra testuttrycket till $count -gt 5 kommer until att avsluta slingan vid rätt tidpunkt. Beslutet om huruvida man ska använda while eller until handlar vanligen om att välja den som gör det möjligt att skriva det tydligaste testet.
41.3. Läsa filer med slingor
while och until kan bearbeta standardindata. Detta gör det möjligt att bearbeta filer med while- och until-slingor. I följande exempel ska vi visa innehållet i filen distros.txt som användes i tidigare kapitel:
#!/bin/bash
# while-read: read lines from a file
while read -r distro version release; do
printf "Distro: %s\tVersion: %s\tReleased: %s\n" \
"$distro" \
"$version" \
"$release"
done < distros.txt
För att styra om en fil till slingan placerar vi omstyrningsoperatorn efter satsen done. Slingan kommer att använda read för att läsa in fälten från den omstyrda filen. Kommandot read avslutas efter att varje rad har lästs, med slutstatus noll tills filslut nås. Då avslutas det med en slutstatus som inte är noll, vilket i sin tur avslutar slingan. Det går också att pipea standardindata till en slinga.
#!/bin/bash
# while-read2: read lines from a file
sort -k 1,1 -k 2n distros.txt | while read -r distro version release; do
printf "Distro: %s\tVersion: %s\tReleased: %s\n" \
"$distro" \
"$version" \
"$release"
done
Här tar vi utdatan från kommandot sort och visar textströmmen. Det är dock viktigt att komma ihåg att eftersom en pipe kommer att köra slingan i ett underskal kommer alla variabler som skapas eller tilldelas inuti slingan att gå förlorade när slingan avslutas.
41.4. Sammanfattning
Med introduktionen av slingor, tillsammans med förgreningar, subrutiner och sekvenser, har vi nu gått igenom de viktigaste typerna av flödeskontroll som används i program. bash har fler knep i rockärmen, men de är förfiningar av dessa grundläggande idéer.
41.5. Vidare läsning
-
Bash Guide for Beginners on
whileloops: -
Wikipedia on control flow and loops:
42. 30 - Felsökning
Nu när våra skript har blivit mer komplexa är det dags att titta på vad som händer när saker går fel. I detta kapitel ska vi titta på några vanliga typer av fel som uppstår i skript och undersöka några användbara tekniker som kan användas för att spåra upp och undanröja problem.
42.1. Syntaktiska fel
En allmän klass av fel är syntaktiska. Syntaktiska fel innebär att något element i skalsyntaxen skrivs fel. Skalet slutar att köra ett skript när det stöter på denna typ av fel.
I följande diskussioner använder vi detta skript för att demonstrera vanliga feltyper:
#!/bin/bash
# trouble: script to demonstrate common errors
number=1
if [ $number = 1 ]; then
echo "Number is equal to 1."
else
echo "Number is not equal to 1."
fi
Som det är skrivet körs detta skript utan problem.
[me@linuxbox ~]$ trouble
Number is equal to 1.
42.1.1. Saknade citationstecken
Låt oss redigera skriptet och ta bort det avslutande citationstecknet från argumentet efter det första echo-kommandot:
#!/bin/bash
# trouble: script to demonstrate common errors
number=1
if [ $number = 1 ]; then
echo "Number is equal to 1.
else
echo "Number is not equal to 1."
fi
Det ger två fel. Intressant nog pekar radnumren som anges i felmeddelandena inte på den plats där citationstecknet togs bort, utan mycket senare i programmet. Om vi följer programmet efter det saknade citationstecknet ser vi varför. bash fortsätter att leta efter det avslutande citationstecknet tills det hittar ett, vilket det gör omedelbart efter det andra echo-kommandot. Efter det blir bash mycket förvirrat. Syntaxen för det efterföljande if-kommandot går sönder, eftersom satsen fi nu ligger inuti en citerad (men fortfarande öppen) sträng.
[me@linuxbox ~]$ trouble
/home/me/bin/trouble: line 10: unexpected EOF while looking for matching `"'
/home/me/bin/trouble: line 13: syntax error: unexpected end of file
I långa skript kan denna typ av fel vara ganska svåra att hitta. Att använda en redigerare med syntaxmarkering hjälper, eftersom den i de flesta fall visar citerade strängar på ett tydligt annorlunda sätt än annan skalsyntax. Om en fullständig version av vim är installerad kan syntaxmarkering aktiveras genom att skriva detta kommando:
:syntax on
42.1.2. Saknade eller oväntade token
Ett annat vanligt misstag är att glömma att avsluta ett sammansatt kommando, till exempel if eller while. Låt oss se vad som händer om vi tar bort semikolonet efter test i if-kommandot:
#!/bin/bash
# trouble: script to demonstrate common errors
number=1
if [ $number = 1 ] then
echo "Number is equal to 1."
else
echo "Number is not equal to 1."
fi
Resultatet blir detta:
[me@linuxbox ~]$ trouble
/home/me/bin/trouble: line 9: syntax error near unexpected token `else'
/home/me/bin/trouble: line 9: `else'
Återigen pekar felmeddelandet på ett fel som uppstår senare än det egentliga problemet. Det som händer är faktiskt ganska intressant. Som vi minns accepterar if en lista med kommandon och utvärderar slutkoden för det sista kommandot i listan. I vårt program tänker vi oss att denna lista ska bestå av ett enda kommando, [, en synonym till test. Kommandot [ tar det som följer som en lista argument; i vårt fall är det fyra argument: $number, 1, = och ]. När semikolonet tas bort läggs ordet then till i argumentlistan, vilket är syntaktiskt giltigt. Det efterföljande kommandot echo är också giltigt. Det tolkas som ännu ett kommando i listan över kommandon vars slutkod if ska utvärdera. Därefter kommer else, men det ligger fel, eftersom skalet känner igen det som ett reserverat ord (ett ord som har särskild betydelse för skalet) och inte som namnet på ett kommando, vilket är orsaken till felmeddelandet.
42.1.3. Oväntade expansioner
Det är möjligt att få fel som bara uppstår ibland i ett skript. Ibland körs skriptet fint, och andra gånger misslyckas det på grund av resultatet av en expansion. Om vi återställer vårt saknade semikolon och ändrar värdet på number till en tom variabel som visas här kan vi demonstrera detta:
#!/bin/bash
# trouble: script to demonstrate common errors
number=
if [ $number = 1 ]; then
echo "Number is equal to 1."
else
echo "Number is not equal to 1."
fi
Om vi kör skriptet med denna ändring blir resultatet följande:
[me@linuxbox ~]$ trouble
/home/me/bin/trouble: line 7: [: =: unary operator expected
Number is not equal to 1.
Vi får detta ganska kryptiska felmeddelande, följt av utdatan från det andra echo-kommandot. Problemet är expansionen av variabeln number inuti kommandot test. När kommandot:
[ $number = 1 ]
genomgår expansion med number som tom, blir resultatet:
[ = 1 ]
vilket är ogiltigt, och felet uppstår. Operatorn = är en binär operator (den kräver ett värde på vardera sidan), men det första värdet saknas, så kommandot test förväntar sig i stället en unär operator (som -z). Vidare, eftersom testet misslyckades (på grund av felet), får kommandot if en slutkod som inte är noll och agerar därefter, och det andra echo-kommandot körs.
[ "$number" = 1 ]
[ "" = 1 ]
Detta problem kan rättas till genom att sätta citationstecken runt det första argumentet i test-kommandot.
42.2. Logiska fel
Till skillnad från syntaktiska fel hindrar logiska fel inte ett skript från att köras. Skriptet körs, men det ger inte det önskade resultatet, på grund av ett problem i logiken. Det finns oräkneliga möjliga logiska fel, men här är några av de vanligaste typerna i skript:
-
Felaktiga villkorsuttryck. Det är lätt att skriva fel i en
if/then/elseoch få fel logik utförd. Ibland blir logiken omvänd, eller också blir den ofullständig. -
Off by one-fel. När man kodar slingor som använder räknare är det möjligt att förbise att slingan kan kräva att räkningen börjar på noll snarare än ett för att räkningen ska sluta vid rätt punkt. Denna typ av fel gör att en slinga antingen "går över kanten" genom att räkna för långt eller missar sista varvet genom att avslutas ett varv för tidigt. -
Oväntade situationer, som filnamn som innehåller blanksteg.
42.3. Defensiv programmering
Det är viktigt att verifiera antaganden när man programmerar. Det innebär en noggrann utvärdering av slutstatusen för program och kommandon som används av ett skript. Här är ett exempel, baserat på en sann historia. En olycklig systemadministratör skrev ett skript för att utföra en underhållsuppgift på en viktig server. Skriptet innehöll följande två kodrader:
cd $dir_name
rm *
Det är inget inneboende fel på dessa två rader, så länge katalogen med namnet i variabeln dir_name finns. Men vad händer om den inte gör det? I så fall misslyckas kommandot cd, och skriptet fortsätter till nästa rad och tar bort filerna i den aktuella arbetskatalogen. Inte alls det önskade utfallet! Den otursförföljde administratören förstörde en viktig del av servern på grund av detta designbeslut.
Låt oss titta på några sätt att förbättra denna konstruktion. För det första vore det klokt att säkerställa att variabeln dir_name expanderar till bara ett ord genom att citera den, och att göra körningen av rm beroende av att cd lyckas.
cd "$dir_name" && rm *
På så sätt körs inte kommandot rm om cd misslyckas. Det är bättre, men det lämnar fortfarande öppet för möjligheten att variabeln dir_name inte är satt eller är tom, vilket skulle leda till att filerna i användarens hemkatalog raderas. Detta skulle också kunna undvikas genom att kontrollera att dir_name faktiskt innehåller namnet på en katalog som finns.
[[ -d "$dir_name" ]] && cd "$dir_name" && rm *
Ofta är det bäst att inkludera logik som avslutar skriptet och rapporterar ett fel när en situation som den ovan inträffar.
# Delete files in directory $dir_name
if [[ ! -d "$dir_name" ]]; then
echo "No such directory: '$dir_name'" >&2
exit 1
fi
if ! cd "$dir_name"; then
echo "Cannot cd to '$dir_name'" >&2
exit 1
fi
if ! rm *; then
echo "File deletion failed. Check results" >&2
exit 1
fi
42.3.1. set -e, set -u och set -o pipefail
En sak vi märker med bash är att när ett skript körs och ett kommando misslyckas (inte inräknat ett syntaxfel i själva skriptet), fortsätter skriptet glatt till nästa kommando. Ofta är detta inte önskvärt, och POSIX-standarden och därefter bash försöker hantera detta. bash erbjuder en inställning som försöker hantera fel automatiskt, vilket helt enkelt betyder att om denna inställning är aktiverad avslutas skriptet om något kommando (med några nödvändiga undantag) returnerar en slutstatus som inte är noll. För att aktivera denna inställning placerar vi kommandot set -e nära början av skriptet. Flera kodningsstandarder för bash insisterar på att använda denna funktion tillsammans med ett par relaterade inställningar: set -u, som avslutar ett skript om det finns en oinitierad variabel, och set -o pipefail, som får ett skript att avslutas om det sista ledet i en pipeline misslyckas.
set -e
set -u
set -o pipefail
Det rekommenderas inte att använda dessa funktioner. Det är bättre att utforma korrekt felhantering och inte förlita sig på set -e som ersättning för god kodningspraxis.
42.3.2. ShellCheck är din vän
Det finns ett program i de flesta distributionsförråd som heter shellcheck och som analyserar skalskript och hittar många slags fel och dåliga skriptvanor. Det är väl värt att använda det för att kontrollera kvaliteten på våra skript. För att använda det på ett skript som har en shebang gör vi helt enkelt så här:
shellcheck my_script
shellcheck -s bash my_library
ShellCheck upptäcker automatiskt vilken skaldialekt som ska användas utifrån shebang-raden. För att testa skriptkod som inte innehåller en shebang, till exempel funktionsbibliotek, kan vi använda ShellCheck på detta sätt:
https://www.shellcheck.net
42.3.3. Se upp med filnamn
Det finns ytterligare ett problem med detta skript för filradering som är mer obskyrt men skulle kunna vara mycket farligt. Unix (och Unix-liknande operativsystem) har, enligt många, en allvarlig designbrist när det gäller filnamn. Unix är extremt tillåtande när det gäller dem. Faktum är att det bara finns två tecken som inte får ingå i ett filnamn. Det första är tecknet /, eftersom det används för att skilja elementen i en sökväg åt, och det andra är nulltecknet (en nollbyte), som används internt för att markera slutet på strängar. Allt annat är tillåtet, inklusive blanksteg, tabbar, radmatningar, inledande bindestreck, vagnreturer och så vidare.
Särskilt problematiska är inledande bindestreck. Det är till exempel helt lagligt att ha en fil som heter -rf ~. Tänk en stund på vad som händer när det filnamnet skickas till rm. För att skydda oss mot detta problem vill vi ändra vårt rm-kommando i skriptet från
rm *
till:
rm ./*
Detta hindrar ett filnamn som börjar med bindestreck från att tolkas som en kommandoflagga. Som allmän regel bör man alltid föregå jokertecken (som * och ?) med ./ för att förhindra att kommandon misstolkar dem. Detta gäller till exempel även saker som *.pdf och ???.mp3.
42.3.4. Portabla filnamn
För att säkerställa att ett filnamn är portabelt mellan flera plattformar (det vill säga olika typer av datorer och operativsystem) måste man vara noga med att begränsa vilka tecken som ingår i filnamnet. Det finns en standard som kallas POSIX Portable Filename Character Set och som kan användas för att maximera chansen att ett filnamn fungerar på olika system. Standarden är ganska enkel. De enda tillåtna tecknen är versalerna A-Z, gemenerna a-z, siffrorna 0-9, punkt (.), bindestreck (-) och understreck (_). Standarden rekommenderar dessutom att filnamn inte ska börja med bindestreck.
42.4. Verifiera indata
En allmän regel för god programmering är att om ett program accepterar indata måste det kunna hantera vad det än får. Det innebär vanligtvis att indatan måste granskas noggrant för att säkerställa att bara giltig indata accepteras för vidare behandling. Vi såg ett exempel på detta i föregående kapitel när vi studerade kommandot read. Ett skript innehöll följande test för att verifiera ett menyval:
[[ $REPLY =~ ^[0-3]$ ]]
Detta test är mycket specifikt. Det returnerar slutstatus noll bara om strängen som användaren skrev in är en siffra i intervallet noll till tre. Inget annat kommer att accepteras. Ibland kan sådana tester vara utmanande att skriva, men ansträngningen är nödvändig för att skapa ett skript av hög kvalitet.
42.4.1. Utformning är en funktion av tid
När jag var högskolestudent och läste industridesign sade en klok professor att mängden design i ett projekt bestämdes av hur mycket tid designern fick. Om du fick fem minuter på dig att utforma en apparat som dödar flugor, ritade du en flugsmälla. Om du fick fem månader kanske du i stället tog fram ett laserstyrt anti-flug-system.
Samma princip gäller för programmering. Ibland duger ett quick-and-dirty-skript om det bara ska användas en enda gång och bara av programmeraren själv. Den typen av skript är vanliga och bör utvecklas snabbt för att arbetet ska vara ekonomiskt. Sådana skript behöver inte särskilt många kommentarer eller defensiva kontroller. Om ett skript däremot är avsett för produktion, det vill säga ett skript som ska användas om och om igen för en viktig uppgift eller av flera användare, kräver det mycket mer noggrann utveckling.
42.5. Testning
Testning är ett viktigt steg i all slags programutveckling, även för skript. Det finns ett talesätt i öppen källkodsvärlden: release early, release often, vilket speglar detta faktum. Genom att släppa tidigt och ofta får programvara större exponering för användning och testning. Erfarenheten har visat att fel är mycket lättare att hitta och mycket billigare att rätta om de hittas tidigt i utvecklingscykeln.
I kapitel 26 såg vi hur stubs kan användas för att verifiera programflödet. Från de tidigaste stadierna i utvecklingen av ett skript är de en värdefull teknik för att kontrollera hur arbetet fortskrider.
Låt oss titta på problemet med filradering som visades tidigare och se hur det skulle kunna kodas för enkel testning. Att testa den ursprungliga kodsnutten vore farligt eftersom dess syfte är att radera filer, men vi kan ändra koden så att testet blir säkert.
if [[ -d $dir_name ]]; then
if cd $dir_name; then
echo rm * # TESTING
else
echo "cannot cd to '$dir_name'" >&2
exit 1
fi
else
echo "no such directory: '$dir_name'" >&2
exit 1
fi
exit # TESTING
Eftersom feltillstånden redan skriver ut användbara meddelanden behöver vi inte lägga till några fler. Den viktigaste ändringen är att placera ett echo-kommando direkt före kommandot rm, så att kommandot och dess expanderade argumentlista visas i stället för att kommandot faktiskt körs. Denna ändring möjliggör säker körning av koden. I slutet av kodfragmentet placerar vi ett exit-kommando för att avsluta testet och hindra att någon annan del av skriptet körs. Behovet av detta varierar beroende på skriptets utformning.
42.5.1. Testfall
För att utföra meningsfull testning är det viktigt att utveckla och tillämpa bra testfall. Detta görs genom att noggrant välja indata eller driftsförhållanden som speglar gräns- och hörnfall. I vår kodsnutt (som är enkel) vill vi veta hur koden fungerar under tre specifika förhållanden:
-
dir_nameinnehåller namnet på en katalog som finns. -
dir_nameinnehåller namnet på en katalog som inte finns. -
dir_nameär tom.
42.6. Felsökning
Om testning avslöjar ett problem i ett skript är nästa steg felsökning. Ett problem betyder vanligtvis att skriptet på något sätt inte beter sig som programmeraren förväntade sig. Om så är fallet måste vi noggrant avgöra exakt vad skriptet faktiskt gör och varför. Att hitta fel kan ibland innebära mycket detektivarbete.
42.6.1. Hitta problemområdet
I vissa skript, särskilt långa sådana, är det ibland användbart att isolera det område i skriptet som hänger samman med problemet. Det behöver inte alltid vara själva felet, men isolering ger ofta insikter om den verkliga orsaken. En teknik som kan användas för att isolera kod är att kommentera bort delar av ett skript. Vår kodsnutt för filradering kan till exempel ändras för att avgöra om den borttagna delen hängde samman med ett fel.
if [[ -d $dir_name ]]; then
if cd $dir_name; then
rm *
else
echo "cannot cd to '$dir_name'" >&2
exit 1
fi
# else
#
# echo "no such directory: '$dir_name'" >&2
#
# exit 1
fi
42.6.2. Spårning
Fel är ofta fall av oväntat logiskt flöde i ett skript. Det vill säga att delar av skriptet antingen aldrig körs eller körs i fel ordning eller vid fel tidpunkt. För att se programmets verkliga flöde använder vi en teknik som kallas spårning. En metod för spårning innebär att man placerar informativa meddelanden i ett skript som visar var körningen befinner sig. Vi kan lägga till sådana meddelanden i vår kodsnutt.
echo "preparing to delete files" >&2
if [[ -d $dir_name ]]; then
if cd $dir_name; then
echo "deleting files" >&2
rm *
else
echo "cannot cd to '$dir_name'" >&2
exit 1
fi
else
echo "no such directory: '$dir_name'" >&2
exit 1
fi
echo "file deletion complete" >&2
Vi skickar meddelandena till standardfel för att skilja dem från normal utdata. Vi drar inte heller in raderna som innehåller meddelandena, så att de blir lättare att hitta när det är dags att ta bort dem.
Nu när skriptet körs går det att se att filraderingen har utförts.
#!/bin/bash -x
# trouble: script to demonstrate common errors
number=1
if [ $number = 1 ]; then
echo "Number is equal to 1."
else
echo "Number is not equal to 1."
fi
bash tillhandahåller också en metod för spårning, implementerad genom flaggan -x och kommandot set med flaggan -x. Med vårt tidigare skript trouble kan vi aktivera spårning för hela skriptet genom att lägga till flaggan -x på första raden.
[me@linuxbox ~]$ trouble
+ number=1
+ '[' 1 = 1 ']'
+ echo 'Number is equal to 1.'
Number is equal to 1.
När det körs ser resultatet ut så här:
[me@linuxbox ~]$ export PS4='$LINENO + '
[me@linuxbox ~]$ trouble
5 + number=1
7 + '[' 1 = 1 ']'
8 + echo 'Number is equal to 1.'
Number is equal to 1.
Med spårning aktiverad ser vi de kommandon som körs med expansionerna redan tillämpade. De inledande plustecknen visar att detta är spårutskrift och skiljer dem från vanliga utdatarader. Plustecknet är standardtecknet för spårutskrift. Det ligger i skalvariabeln PS4 (prompt string 4). Innehållet i denna variabel kan justeras för att göra prompten mer användbar. Här ändrar vi variabelns innehåll så att det inkluderar det aktuella radnumret i skriptet där spårningen utförs. Lägg märke till att enkla citationstecken krävs för att förhindra expansion tills prompten faktiskt används.
#!/bin/bash
# trouble: script to demonstrate common errors
number=1
set -x # Turn on tracing
if [ $number = 1 ]; then
echo "Number is equal to 1."
else
echo "Number is not equal to 1."
fi
set +x # Turn off tracing
42.6.3. Undersöka värden under körning
Det är ofta användbart att, tillsammans med spårning, visa innehållet i variabler för att se de inre funktionerna i ett skript medan det körs. Att lägga till extra echo-satser brukar fungera bra.
#!/bin/bash
# trouble: script to demonstrate common errors
number=1
echo "number=$number" # DEBUG
set -x # Turn on tracing
if [ $number = 1 ]; then
echo "Number is equal to 1."
else
echo "Number is not equal to 1."
fi
set +x # Turn off tracing
I detta triviala exempel visar vi helt enkelt värdet på variabeln number och markerar den tillagda raden med en kommentar för att göra det lättare att senare identifiera och ta bort den. Denna teknik är särskilt användbar när man följer beteendet hos slingor och aritmetik i skript.
42.7. Sammanfattning
I detta kapitel tittade vi på bara några av de problem som kan uppstå under utveckling av skript. Det finns naturligtvis många fler. De tekniker som beskrivs här gör det möjligt att hitta de flesta vanliga fel. Felsökning är en fin konst som utvecklas genom erfarenhet, både när det gäller att undvika fel (genom att testa ständigt under utvecklingen) och att hitta fel (genom effektiv användning av spårning).
42.8. Vidare läsning
-
Syntaxfel:
-
Logiskt fel:
-
Bash-fallgropar och gotchas:
-
Filnamn i skalskript:
-
Bash Debugger:
43. 31 - Flödesstyrning: förgrening med case
I detta kapitel fortsätter vi vår genomgång av flödesstyrning. I kapitel 28 byggde vi några enkla menyer och den logik som användes för att agera utifrån användarens val. För att göra detta använde vi en serie if-kommandon för att identifiera vilket av de möjliga valen som hade gjorts. Denna typ av logisk konstruktion förekommer ofta i program, så ofta att många programspråk (inklusive skalet) tillhandahåller en särskild mekanism för flödesstyrning vid flervalsbeslut.
43.1. case
I bash kallas det sammansatta kommandot för flerval case. Det har följande syntax:
case word in
[pattern [| pattern]...) commands ;;]...
esac
Om vi tittar på programmet read-menu från kapitel 28 ser vi den logik som användes för att agera på användarens val.
#!/bin/bash
# read-menu: a menu driven system information program
clear
echo "
Please Select:
1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
"
read -r -p "Enter selection [0-3] > "
if [[ "$REPLY" =~ ^[0-3]$ ]]; then
if [[ "$REPLY" == 0 ]]; then
echo "Program terminated."
exit
fi
if [[ "$REPLY" == 1 ]]; then
echo "Hostname: $HOSTNAME"
uptime
exit
fi
if [[ "$REPLY" == 2 ]]; then
df -h
exit
fi
if [[ "$REPLY" == 3 ]]; then
if [[ "$(id -u)" -eq 0 ]]; then
echo "Home Space Utilization (All Users)"
du -sh /home/*
else
echo "Home Space Utilization ($USER)"
du -sh "$HOME"
fi
exit
fi
else
echo "Invalid entry." >&2
exit 1
fi
Med case kan vi ersätta denna logik med något enklare.
#!/bin/bash
# case-menu: a menu driven system information program
clear
echo "
Please Select:
1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
"
read -r -p "Enter selection [0-3] > "
case "$REPLY" in
0)
echo "Program terminated."
exit
;;
1)
echo "Hostname: $HOSTNAME"
uptime
;;
2)
df -h
;;
3)
if [[ "$(id -u)" -eq 0 ]]; then
echo "Home Space Utilization (All Users)"
du -sh /home/*
else
echo "Home Space Utilization ($USER)"
du -sh "$HOME"
fi
;;
*)
echo "Invalid entry" >&2
exit 1
;;
esac
Kommandot case tittar på värdet av word, som i vårt exempel är värdet i variabeln REPLY, och försöker sedan matcha det mot ett av de angivna mönstren. När en träff hittas körs de kommandon som hör till det angivna mönstret. Efter att en träff har hittats görs inga ytterligare försök att matcha.
43.1.1. Mönster
De mönster som används av case är desamma som de som används vid sökvägsexpansion. Mönster avslutas med ett )-tecken. Tabell 31-1 listar exempel på giltiga mönster.
| Mönster | Beskrivning |
|---|---|
|
Matchar om |
[[:alpha:]]) |
Matchar om |
|
Matchar om |
|
Matchar om |
|
Matchar alla värden av |
Exempel:
#!/bin/bash
read -r -p "Enter word > "
case "$REPLY" in
[[:alpha:]]) echo "is a single alphabetic character." ;;
[ABC][0-9]) echo "is A, B, or C followed by a digit." ;;
???) echo "is three characters long." ;;
*.txt) echo "is a word ending in '.txt'" ;;
*) echo "is something else." ;;
esac
Det går också att kombinera flera mönster genom att använda tecknet lodstreck som avgränsare. Detta skapar ett or-villkorligt mönster. Det är användbart för sådant som att hantera både versaler och gemener. Här är ett exempel:
#!/bin/bash
# case-menu: a menu driven system information program
clear
echo "
Please Select:
A. Display System Information
B. Display Disk Space
C. Display Home Space Utilization
Q. Quit
"
read -r -p "Enter selection [A, B, C or Q] > "
case "$REPLY" in
q|Q)
echo "Program terminated."
exit
;;
a|A)
echo "Hostname: $HOSTNAME"
uptime
;;
b|B)
df -h
;;
c|C)
if [[ "$(id -u)" -eq 0 ]]; then
echo "Home Space Utilization (All Users)"
du -sh /home/*
else
echo "Home Space Utilization ($USER)"
du -sh "$HOME"
fi
;;
*)
echo "Invalid entry" >&2
exit 1
;;
esac
43.1.2. Utföra flera åtgärder
I versioner av bash före 4.0 tillät case bara att en enda åtgärd utfördes vid en lyckad träff. Efter en lyckad träff avslutades kommandot. Här ser vi ett skript som testar ett tecken:
#!/bin/bash
# case4-1: test a character
read -r -n 1 -p "Type a character > "
echo
case "$REPLY" in
[[:upper:]]) echo "'$REPLY' is upper case." ;;
[[:lower:]]) echo "'$REPLY' is lower case." ;;
[[:alpha:]]) echo "'$REPLY' is alphabetic." ;;
[[:digit:]]) echo "'$REPLY' is a digit." ;;
[[:graph:]]) echo "'$REPLY' is a visible character." ;;
[[:punct:]]) echo "'$REPLY' is a punctuation symbol." ;;
[[:space:]]) echo "'$REPLY' is a whitespace character." ;;
[[:xdigit:]]) echo "'$REPLY' is a hexadecimal digit." ;;
esac
Skriptet fungerar för det mesta, men misslyckas om ett tecken matchar mer än en av POSIX-teckenklasserna. Tecknet a är till exempel både gemen och alfabetisk, samt en hexadecimal siffra. I versioner av bash före 4.0 fanns det inget sätt för case att matcha mer än ett test. Moderna versioner av bash lägger till notationen ;;& för att avsluta varje åtgärd, så nu kan vi göra så här:
#!/bin/bash
# case4-2: test a character
read -r -n 1 -p "Type a character > "
echo
case "$REPLY" in
[[:upper:]]) echo "'$REPLY' is upper case." ;;&
[[:lower:]]) echo "'$REPLY' is lower case." ;;&
[[:alpha:]]) echo "'$REPLY' is alphabetic." ;;&
[[:digit:]]) echo "'$REPLY' is a digit." ;;&
[[:graph:]]) echo "'$REPLY' is a visible character." ;;&
[[:punct:]]) echo "'$REPLY' is a punctuation symbol." ;;&
[[:space:]]) echo "'$REPLY' is a whitespace character." ;;&
[[:xdigit:]]) echo "'$REPLY' is a hexadecimal digit." ;;&
esac
När vi kör detta skript får vi detta:
[me@linuxbox ~]$ case4-2
Type a character > a
'a' is lower case.
'a' is alphabetic.
'a' is a visible character.
'a' is a hexadecimal digit.
Tillägget av syntaxen ;;& gör att case kan fortsätta till nästa test i stället för att bara avslutas.
Detta gör det möjligt att behandla case lite som en klassificerare, där en inmatning kan tillhöra flera kategorier samtidigt.
43.2. Sammanfattning
Kommandot case är ett praktiskt tillskott i vår samling programmeringstrick. Det är ofta tydligare och lättare att underhålla än långa kedjor av if/elif-tester, särskilt vid hantering av menyer eller annan flervalsinmatning.
43.3. Vidare läsning
-
Avsnittet om
Conditional ConstructsiThe Bash Reference Manualbeskriver kommandotcasei detalj: -
The Advanced Bash-Scripting Guideger fler exempel på användning avcase:
44. 32 - Positionsparametrar
En funktion som hittills har saknats i våra program är förmågan att ta emot och behandla kommandoradsflaggor och argument. I detta kapitel ska vi undersöka de skalfunktioner som ger våra program tillgång till kommandoradens innehåll.
44.1. Komma åt kommandoraden
Skalet tillhandahåller en uppsättning variabler som kallas positionsparametrar och som innehåller de enskilda orden på kommandoraden. Variablerna heter 0 till 9. De kan demonstreras på följande sätt:
#!/bin/bash
# posit-param: script to view command line parameters
echo "
\$0 = $0
\$1 = $1
\$2 = $2
\$3 = $3
\$4 = $4
\$5 = $5
\$6 = $6
\$7 = $7
\$8 = $8
\$9 = $9
"
Detta är ett enkelt skript som visar värdena i variablerna $0 till $9. När det körs utan några kommandoradsargument blir resultatet detta:
[me@linuxbox ~]$ posit-param a b c d
$0 = /home/me/bin/posit-param
$1 = a
$2 = b
$3 = c
$4 = d
$5 =
Det går att komma åt parametrar bortom nio med klammerparenteser, till exempel ${10}, ${11} och så vidare.
44.1.1. Avgöra antalet argument
Skalet tillhandahåller också en variabel, $#, som innehåller antalet argument på kommandoraden.
#!/bin/bash
# posit-param: script to view command line parameters
echo "
Number of arguments: $#
\$0 = $0
\$1 = $1
\$2 = $2
\$3 = $3
\$4 = $4
\$5 = $5
\$6 = $6
\$7 = $7
\$8 = $8
\$9 = $9
"
44.1.2. shift - få tillgång till många argument
Men vad händer när vi ger programmet ett stort antal argument, som i följande exempel?
#!/bin/bash
# posit-param2: script to display all arguments
count=1
while [[ $# -gt 0 ]]; do
echo "Argument $count = $1"
count=$((count + 1))
shift
done
På detta exempelsystem expanderar jokertecknet * till 82 argument. Hur kan vi behandla så många? Skalet tillhandahåller en metod, om än lite klumpig, för att göra detta. Kommandot shift gör att alla parametrar flyttas ned ett steg varje gång det körs. Faktum är att det med hjälp av shift går att klara sig med bara en parameter (utöver $0, som aldrig ändras).
44.1.3. Enkla tillämpningar
Även utan shift går det att skriva användbara program med positionsparametrar. Som exempel har vi här ett enkelt program för filinformation:
#!/bin/bash
# file-info: simple file information program
PROGNAME="$(basename "$0")"
if [[ -e "$1" ]]; then
echo -e "\nFile Type:"
file "$1"
echo -e "\nFile Status:"
stat "$1"
else
echo "$PROGNAME: usage: $PROGNAME file" >&2
exit 1
fi
Detta program visar filtypen (bestämd av kommandot file) och filstatusen (från kommandot stat) för en angiven fil. En intressant egenskap hos detta program är variabeln PROGNAME. Den får det värde som blir resultatet av kommandot basename "$0". Kommandot basename tar bort den inledande delen av en sökväg och lämnar bara filens basnamn. I vårt exempel tar basename bort den inledande delen av sökvägen som finns i parametern $0, det fullständiga sökvägsnamnet till vårt exempelprogram. Detta värde är användbart när man skapar meddelanden som användningsmeddelandet i slutet av programmet. Genom att skriva koden på detta sätt kan skriptet byta namn, och meddelandet anpassar sig automatiskt så att det innehåller programmets namn.
44.1.4. Använda positionsparametrar med skalfunktioner
Precis som positionsparametrar används för att skicka argument till skalskript kan de också användas för att skicka argument till skalfunktioner. För att demonstrera detta omvandlar vi skriptet file_info till en skalfunktion.
file_info () {
# file_info: function to display file information
if [[ -e "$1" ]]; then
echo -e "\nFile Type:"
file "$1"
echo -e "\nFile Status:"
stat "$1"
else
echo "$FUNCNAME: usage: $FUNCNAME file" >&2
return 1
fi
}
Om ett skript som innehåller skalfunktionen file_info anropar funktionen med ett filnamnsargument kommer argumentet nu att skickas till funktionen.
44.2. Hantera positionsparametrar i grupp
Det är ibland nyttigt att hantera alla positionsparametrar som en grupp. Vi kanske till exempel vill skriva ett wrapper-skript runt ett annat program. Det betyder att vi skapar ett skript eller en skalfunktion som förenklar anropet till ett annat program. Wrappern tillhandahåller i detta fall en lista med svårbegripliga kommandoradsflaggor och skickar sedan vidare en lista med argument till programmet på lägre nivå.
| Parameter | Beskrivning |
|---|---|
|
Expanderar till alla positionsparametrar. Inom citationstecken blir det en enda sträng separerad av det första tecknet i |
|
Expanderar till alla positionsparametrar. Inom citationstecken förblir varje parameter ett separat ord. |
Exempel:
#!/bin/bash
# posit-params3: script to demonstrate $* and $@
print_params () {
echo "\$1 = $1"
echo "\$2 = $2"
echo "\$3 = $3"
echo "\$4 = $4"
}
pass_params () {
echo -e "\n" '$* :'
print_params $*
echo -e "\n" '"$*" :'
print_params "$*"
echo -e "\n" '$@ :'
print_params $@
echo -e "\n" '"$@" :'
print_params "$@"
}
pass_params "word" "words with spaces"
Det viktiga att lära sig är att "$@" nästan alltid är den säkraste och mest användbara formen, eftersom den bevarar integriteten hos varje positionsparameter. Även om skalet ger oss fyra besläktade former är det denna som oftast stämmer överens med vad vi faktiskt vill uppnå.
44.3. En mer fullständig tillämpning
Efter ett långt uppehåll ska vi nu återuppta arbetet med vårt program sys_info_page, senast sett i kapitel 27. Vårt nästa tillägg ska lägga till flera kommandoradsflaggor till programmet enligt följande:
-
Utfil. Vi ska lägga till en flagga för att ange namnet på en fil som ska innehålla programmets utdata. Den ska anges som antingen
-f fileeller--file file. -
Interaktivt läge. Denna flagga ska be användaren om ett utfilnamn och avgöra om den angivna filen redan finns. Om den gör det ska användaren få en fråga innan den befintliga filen skrivs över. Denna flagga ska anges med antingen
-ieller--interactive. -
Hjälp. Antingen
-heller--helpkan anges för att få programmet att skriva ut ett informativt användningsmeddelande.
Här är den kod som behövs för att implementera behandlingen av kommandoraden:
usage () {
echo "$PROGNAME: usage: $PROGNAME [-f file | -i]"
return
}
# process command line options
interactive=
filename=
while [[ -n "$1" ]]; do
case "$1" in
-f | --file)
shift
filename="$1"
;;
-i | --interactive)
interactive=1
;;
-h | --help)
usage
exit
;;
*)
usage >&2
exit 1
;;
esac
shift
done
Först lägger vi till en skalfunktion som heter usage för att visa ett meddelande när hjälpflaggan används eller när en okänd flagga provas.
44.3.1. Flaggan getopts
Koden för tolkning av positionsparametrar som visades ovan är en bra lösning på uppgiften, men det är inte den enda metod vi kan använda. Det finns ett inbyggt skalkommando som heter getopts (att inte förväxla med det externt installerade kommandot getopt med liknande namn) som kan göra en del av arbetet åt oss. Varje gång getopts anropas (vanligen i en slinga) returnerar det det aktuella argumentet i en angiven variabel och ökar räknaren OPTIND så att den pekar på nästa positionsparameter. Om en flagga kräver ett argument returneras argumentet i variabeln OPTARG.
getopts optstring var [arg ...]
Syntaxen för getopts ser ut så här:
Argumentet optstring är en sträng som består av flaggnamnen i form av enstaka tecken. getopts stöder inte (åtminstone inte enkelt) flaggor i långt format. Om ett flaggnamn dessutom följs av ett kolon betyder det att flaggan kräver ett argument.
#!/bin/bash
# getopts-test: process command line options using getopts
PROGNAME="$(basename "$0")"
interactive=
filename=
usage () {
echo "$PROGNAME: usage: $PROGNAME [-f file | -i]"
return
}
while getopts :f:ih opt; do
case "$opt" in
f) filename="$OPTARG" ;;
i) interactive=1 ;;
h) usage ;;
\?) echo "option '$OPTARG' invalid" ;;
:) echo "option '$OPTARG' missing argument" ;;
esac
done
echo "interactive = '$interactive' filename = '$filename'"
Även om getopts bara stöder enstaka tecken som flaggnamn tillåter det att flera flaggor skrivs ihop utan mellanliggande bindestreck, vilket vi har sett hos många kommandon som ls.
44.3.2. Interaktivt läge
När koden för positionsparametrar nu är på plats ska vi implementera det interaktiva läget.
# interactive mode
if [[ -n "$interactive" ]]; then
while true; do
read -r -p "Enter name of output file: " filename
if [[ -e "$filename" ]]; then
read -r -p "'$filename' exists. Overwrite? [y/n/q] > "
case "$REPLY" in
Y|y) break ;;
Q|q) echo "Program terminated."; exit ;;
*) continue ;;
esac
elif [[ -z "$filename" ]]; then
continue
else
break
fi
done
fi
44.3.3. Filutdata
För att implementera funktionen för utfilnamn måste vi först omvandla den befintliga koden som skriver sidan till en skalfunktion, av skäl som snart blir tydliga.
write_html_page () {
cat << _EOF_
<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>$TITLE</h1>
<p>$TIMESTAMP</p>
$(report_uptime)
$(report_disk_space)
$(report_home_space)
</body>
</html>
_EOF_
return
}
# output html page
if [[ -n "$filename" ]]; then
if touch "$filename" && [[ -f "$filename" ]]; then
write_html_page > "$filename"
else
echo "$PROGNAME: Cannot write file '$filename'" >&2
exit 1
fi
else
write_html_page
fi
Koden som hanterar logiken för flaggan -f syns i slutet av listningen ovan. Där testar vi om ett filnamn finns, och om så är fallet görs ett test för att se om filen verkligen är skrivbar. För att göra detta utförs först ett touch, följt av ett test som avgör om den resulterande filen är en vanlig fil. Dessa två tester hanterar situationer där en ogiltig sökväg matas in (touch kommer då att misslyckas) och, om filen redan finns, att den är en vanlig fil.
44.4. Sammanfattning
Med tillägget av positionsparametrar kan vi nu skriva ganska funktionsrika skript. För enkla, repetitiva uppgifter gör positionsparametrar det möjligt att skriva användbara skalfunktioner som kan placeras i användarens fil .bashrc.
44.5. Vidare läsning
-
The Bash Reference Manualhar en artikel om de särskilda parametrarna, inklusive$*och$@:
45. 33 - Flödesstyrning: slingor med for
I detta sista kapitel om flödesstyrning ska vi titta på ytterligare en av skalets slingkonstruktioner. for-slingan skiljer sig från while- och until-slingorna genom att den tillhandahåller ett sätt att bearbeta sekvenser under en slinga. Det visar sig vara användbart vid programmering. Därför är for-slingan en populär konstruktion i bash-skript.
En for-slinga implementeras, ganska naturligt, med det sammansatta kommandot for. I bash finns for i två former.
45.1. for: traditionell skalform
Syntaxen för det ursprungliga kommandot for är följande:
for variable [in words]; do
commands
done
Här är variable namnet på en variabel som kommer att öka under slingans körning, words är en valfri lista över poster som i tur och ordning kommer att tilldelas variable, och commands är de kommandon som ska utföras vid varje varv i slingan.
Kommandot for är användbart på kommandoraden. Vi kan enkelt demonstrera hur det fungerar.
[me@linuxbox ~]$ for i in A B C D; do echo $i; done
A
B
C
D
I detta exempel får for en lista med fyra ord: A, B, C och D. Med en lista på fyra ord körs slingan fyra gånger. Varje gång slingan körs tilldelas ett ord till variabeln i. Inuti slingan har vi ett kommando echo som visar värdet på i för att visa tilldelningen. Precis som med while- och until-slingor avslutar nyckelordet done slingan.
Den verkligt kraftfulla egenskapen hos for är antalet intressanta sätt vi kan skapa listan med ord på. Vi kan till exempel göra det genom klammerexpansion, så här:
[me@linuxbox ~]$ for i in {A..D}; do echo $i; done
A
B
C
D
eller så kan vi använda sökvägsexpansion, så här:
[me@linuxbox ~]$ for i in distros*.txt; do echo "$i"; done
distros-by-date.txt
distros-dates.txt
distros-key-names.txt
distros-key-vernums.txt
distros-names.txt
distros.txt
distros-vernums.txt
distros-versions.txt
Sökvägsexpansion ger en fin och ren lista över sökvägar som kan bearbetas i slingan. Den enda försiktighetsåtgärd som behövs är att kontrollera att expansionen faktiskt matchade något. Som standard kommer jokertecknen själva (distros*.txt i exemplet ovan) att returneras om expansionen inte matchar några filer. För att skydda oss mot detta skulle vi skriva exemplet ovan i ett skript så här:
for i in distros*.txt; do
if [[ -e "$i" ]]; then
echo "$i"
fi
done
Genom att lägga till ett test för filens existens ignorerar vi en misslyckad expansion.
#!/bin/bash
# longest-word: find longest string in a file
while [[ -n "$1" ]]; do
if [[ -r "$1" ]]; then
max_word=
max_len=0
for i in $(strings "$1"); do
len="$(echo -n "$i" | wc -c)"
if (( len > max_len )); then
max_len="$len"
max_word="$i"
fi
done
echo "$1: '$max_word' ($max_len characters)"
fi
shift
done
En annan vanlig metod för att skapa ord är kommandosubstitution.
I detta exempel letar vi efter den längsta sträng som finns i en fil. När programmet får ett eller flera filnamn på kommandoraden använder det programmet strings (som ingår i GNU-paketet binutils) för att generera en lista med läsbara text`ord` i varje fil. for-slingan bearbetar varje ord i tur och ordning och avgör om det aktuella ordet är det längsta som hittats hittills. När slingan är klar visas det längsta ordet.
#!/bin/bash
# longest-word2: find longest string in a file
for i; do
if [[ -r "$i" ]]; then
max_word=
max_len=0
for j in $(strings "$i"); do
len="$(echo -n "$j" | wc -c)"
if (( len > max_len )); then
max_len="$len"
max_word="$j"
fi
done
echo "$i: '$max_word' ($max_len characters)"
fi
done
En sak att notera här är att vi, i motsats till vår vanliga praxis, inte omger kommandosubstitutionen $(strings "$1") med dubbla citationstecken. Det beror på att vi faktiskt vill att orduppdelning ska ske för att ge oss vår lista. Om vi hade omgett kommandosubstitutionen med citationstecken skulle den bara ge ett enda ord som innehåller alla strängar i filen. Det är inte riktigt det vi är ute efter.
Om den valfria delen in words i kommandot for utelämnas, använder for i stället positionsparametrarna. Vi ändrar vårt skript longest-word så att det använder denna metod:
Som vi ser har vi ändrat den yttersta slingan till att använda for i stället för while. Genom att utelämna ordlistan i kommandot for används positionsparametrarna i stället. Inuti slingan har tidigare förekomster av variabeln i ändrats till variabeln j. Användningen av shift har också tagits bort.
45.1.1. Varför i?
De traditionella loopvariablerna i, j och k härstammar från programmeringskonvention och går åtminstone tillbaka till FORTRAN, där dessa namn automatiskt behandlades som heltal.
Denna historia inspirerade också det gamla skämtet: "GUD är reell, såvida han inte deklareras som heltal."
45.2. for: form i C-språkets stil
Nyare versioner av bash har lagt till en andra form av kommandot for, en som liknar formen i programspråket C. Många andra språk stöder också denna form.
for (( expression1; expression2; expression3 )); do
commands
done
Här är expression1, expression2 och expression3 aritmetiska uttryck, och commands är de kommandon som ska utföras under varje varv i slingan.
(( expression1 ))
while (( expression2 )); do
commands
(( expression3 ))
done
Beteendemässigt motsvarar denna form följande konstruktion:
expression1 används för att initiera villkoren för slingan, expression2 används för att avgöra när slingan är färdig och expression3 utförs i slutet av varje varv i slingan.
#!/bin/bash
# simple_counter: demo of C style for command
for (( i=0; i<5; i=i+1 )); do
echo $i
done
Här är en typisk användning:
[me@linuxbox ~]$ simple_counter
0
1
2
3
4
C-formen är särskilt användbar när en numerisk sekvens behövs. Den känns bekant för programmerare som kommer från C, C++, Java och liknande språk, och är ofta det tydligaste valet för räkneslingor.
45.3. Sammanfattning
Med vår kunskap om kommandot for ska vi nu göra de sista förbättringarna av vårt skript sys_info_page. För närvarande ser funktionen report_home_space ut så här:
report_home_space () {
if [[ "$(id -u)" -eq 0 ]]; then
cat << _EOF_
<h2>Home Space Utilization (All Users)</h2>
<pre>$(du -sh /home/*)</pre>
_EOF_
else
cat << _EOF_
<h2>Home Space Utilization ($USER)</h2>
<pre>$(du -sh "$HOME")</pre>
_EOF_
fi
return
}
Därefter ska vi skriva om den så att den ger mer detaljer för varje användares hemkatalog och inkluderar det totala antalet filer och underkataloger i varje.
report_home_space () {
local format="%8s%10s%10s\n"
local i dir_list total_files total_dirs total_size user_name
if [[ "$(id -u)" -eq 0 ]]; then
dir_list=/home/*
user_name="All Users"
else
dir_list="$HOME"
user_name="$USER"
fi
echo "<h2>Home Space Utilization ($user_name)</h2>"
for i in $dir_list; do
total_files="$(find "$i" -type f | wc -l)"
total_dirs="$(find "$i" -type d | wc -l)"
total_size="$(du -sh "$i" | cut -f 1)"
echo "<H3>$i</H3>"
echo "<pre>"
printf "$format" "Dirs" "Files" "Size"
printf "$format" "----" "-----" "----"
printf "$format" "$total_dirs" "$total_files" "$total_size"
echo "</pre>"
done
return
}
Denna omskrivning tillämpar mycket av det vi har lärt oss hittills. Vi testar fortfarande om användaren är superanvändare, men i stället för att utföra hela uppsättningen åtgärder som en del av if sätter vi några variabler som senare används i en for-slinga. Vi har lagt till flera lokala variabler i funktionen och använt printf för att formatera en del av utdatan.
45.4. Vidare läsning
-
Advanced Bash-Scripting Guide, kapitel om slingor:
-
Bash Reference Manual om slingkonstruktioner:
46. 34 - Strängar och tal
Datorprogram handlar helt om att arbeta med data. I tidigare kapitel har vi fokuserat på att bearbeta data på filnivå. Många programmeringsproblem behöver dock lösas med mindre dataenheter, såsom strängar och tal.
I detta kapitel ska vi titta på flera skalfunktioner som används för att manipulera strängar och tal. Skalet tillhandahåller en mängd parameterexpansioner som utför strängoperationer. Utöver aritmetisk expansion (som vi snuddade vid i kapitel 7) finns det också ett välkänt kommandoradsprogram som heter bc, vilket utför matematik på högre nivå.
46.1. Parameterexpansion
Även om parameterexpansion dök upp i kapitel 7 gick vi inte igenom det i detalj, eftersom de flesta parameterexpansioner används i skript snarare än på kommandoraden. Vi har redan arbetat med vissa former av parameterexpansion, till exempel skalvariabler. Skalet erbjuder många fler.
Obs: Det är alltid god praxis att omsluta parameterexpansioner med dubbla citationstecken för att förhindra oönskad orduppdelning, om det inte finns ett särskilt skäl att låta bli. Detta gäller särskilt när man arbetar med filnamn, eftersom de ofta kan innehålla inbäddade blanksteg och annan blandad otrevlighet.
46.1.1. Grundläggande parametrar
Den enklaste formen av parameterexpansion speglas i den vanliga användningen av variabler. Här är ett exempel:
$a
${a}
När detta expanderas blir det till det som variabeln a innehåller. Enkla parametrar kan också omges av klammerparenteser.
[me@linuxbox ~]$ a="foo"
[me@linuxbox ~]$ echo "$a_file"
[me@linuxbox ~]$ echo "${a}_file"
foo_file
Detta påverkar inte expansionen, men krävs om variabeln står intill annan text som skulle kunna förvirra skalet. I detta exempel försöker vi skapa ett filnamn genom att lägga till strängen _file till innehållet i variabeln a.
${11}
46.1.2. Expansioner för att hantera tomma variabler
Flera parameterexpansioner är avsedda att hantera icke-existerande och tomma variabler. Dessa expansioner är praktiska för att hantera saknade positionsparametrar och tilldela standardvärden till parametrar. Den första av dem ger ett standardvärde för en parameter som inte är satt.
Om parameter inte är satt (det vill säga inte finns) eller är tom, resulterar denna expansion i värdet av word. Om parameter inte är tom resulterar expansionen i värdet av parameter.
${parameter:-word}
Denna expansion ger ett standardvärde om en parameter antingen inte är satt eller är tom.
[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo ${foo:-"substitute value if unset"}
substitute value if unset
[me@linuxbox ~]$ foo=bar
[me@linuxbox ~]$ echo ${foo:-"substitute value if unset"}
bar
Om parameter inte är satt eller är tom resulterar denna expansion i värdet av word. Dessutom tilldelas värdet av word till parameter. Om parameter inte är tom resulterar expansionen i värdet av parameter.
${parameter:=word}
Obs: Positionsparametrar och andra särskilda parametrar kan inte tilldelas på detta sätt.
[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo ${foo:="default value if unset"}
default value if unset
[me@linuxbox ~]$ echo $foo
default value if unset
Positionsparametrar och andra specialparametrar kan inte tilldelas på detta sätt.
Avbryt om en obligatorisk parameter inte är satt eller är tom:
${parameter:?word}
Om parameter inte är satt eller är tom gör denna expansion att skriptet avslutas med fel, och innehållet i word skickas till standardfel. Om parameter inte är tom resulterar expansionen i värdet av parameter.
${parameter:?word}
Denna expansion ersätter ett värde för en parameter som inte är tom.
[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo ${foo:?"parameter is empty"}
bash: foo: parameter is empty
Om parameter inte är satt eller är tom resulterar expansionen inte i någonting. Om parameter inte är tom ersätts parameter med värdet av word; värdet på parameter ändras dock inte.
${parameter:+word}
46.1.3. Expansioner som returnerar variabelnamn
Skalet har förmågan att returnera namnen på variabler. Detta används i vissa ganska exotiska situationer.
${!prefix*}
${!prefix@}
Denna expansion returnerar namnen på befintliga variabler vars namn börjar med prefix. Enligt bash-dokumentationen fungerar båda formerna av denna expansion identiskt när de inte är citerade. Här listar vi alla variabler i miljön vars namn börjar med BASH:
[me@linuxbox ~]$ echo ${!BASH*}
BASH BASH_ARGC BASH_ARGV BASH_COMMAND BASH_COMPLETION BASH_COMPLETION_DIR BASH_LINENO BASH_SOURCE BASH_SUBSHELL BASH_VERSINFO BASH_VERSION
46.2. Strängoperationer
Det finns en stor uppsättning expansioner som arbetar med strängar. Många av dessa expansioner lämpar sig särskilt väl för operationer på sökvägar.
Följande expansion expanderar till längden på strängen som finns i parameter. Normalt är parameter en sträng; om parameter däremot är antingen @ eller * blir expansionen antalet positionsparametrar.
${#parameter}
Exempel:
[me@linuxbox ~]$ foo="This string is long."
[me@linuxbox ~]$ echo "'$foo' is ${#foo} characters long."
'This string is long.' is 20 characters long.
Dessa expansioner används för att extrahera en del av strängen som finns i parameter. Extraktionen börjar offset tecken från början av strängen och fortsätter till strängens slut, om inte length anges. Om värdet på offset är negativt tolkas det som att det börjar från strängens slut i stället för från början. Lägg märke till att negativa värden måste föregås av ett blanksteg för att förhindra sammanblandning med expansionen ${parameter:-word}. length, om det finns, får inte vara mindre än noll. Om parameter är @ blir resultatet av expansionen length positionsparametrar, med början vid offset.
${parameter:offset}
${parameter:offset:length}
Exempel:
[me@linuxbox ~]$ foo="This string is long."
[me@linuxbox ~]$ echo ${foo:5}
string is long.
[me@linuxbox ~]$ echo ${foo:5:6}
string
[me@linuxbox ~]$ echo ${foo: -5}
long.
[me@linuxbox ~]$ echo ${foo: -5:2}
lo
Dessa expansioner tar bort en inledande del av strängen i parameter som definieras av pattern. pattern är ett jokerteckensmönster av samma slag som används vid sökvägsexpansion. Skillnaden mellan de två formerna är att formen med # tar bort den kortaste träffen, medan formen med ## tar bort den längsta träffen.
${parameter#pattern}
${parameter##pattern}
Exempel:
[me@linuxbox ~]$ foo=file.txt.zip
[me@linuxbox ~]$ echo ${foo#*.}
txt.zip
[me@linuxbox ~]$ echo ${foo##*.}
zip
Dessa expansioner är desamma som de föregående expansionerna med # och ##, men de tar bort text från slutet av strängen i parameter i stället för från början.
${parameter%pattern}
${parameter%%pattern}
Exempel:
[me@linuxbox ~]$ foo=file.txt.zip
[me@linuxbox ~]$ echo ${foo%.*}
file.txt
[me@linuxbox ~]$ echo ${foo%%.*}
file
Dessa expansioner utför en sök-och-ersätt-operation på innehållet i parameter. Om text hittas som matchar jokerteckensmönstret pattern ersätts den med innehållet i string. I normalformen ersätts bara den första förekomsten av pattern. I formen med // ersätts alla förekomster. Formen med /# kräver att träffen sker i början av strängen, och formen med /% kräver att träffen sker i slutet av strängen. I varje form kan /string utelämnas, vilket gör att texten som matchas av pattern tas bort.
${parameter/pattern/string}
${parameter//pattern/string}
${parameter/#pattern/string}
${parameter/%pattern/string}
Exempel:
[me@linuxbox ~]$ foo=JPG.JPG
[me@linuxbox ~]$ echo ${foo/JPG/jpg}
jpg.JPG
[me@linuxbox ~]$ echo ${foo//JPG/jpg}
jpg.jpg
[me@linuxbox ~]$ echo ${foo/#JPG/jpg}
jpg.JPG
[me@linuxbox ~]$ echo ${foo/%JPG/jpg}
JPG.jpg
Parameterexpansion är bra att kunna. Expansionerna för strängmanipulation kan användas som ersättning för andra vanliga kommandon som sed och cut. Expansioner kan förbättra effektiviteten i skript genom att eliminera användningen av externa program. Som exempel ska vi ändra programmet longest-word från föregående kapitel så att det använder parameterexpansionen ${#j} i stället för kommandosubstitutionen $(echo -n $j | wc -c) och dess resulterande underskal, så här:
#!/bin/bash
# longest-word3: find longest string in a file
for i; do
if [[ -r "$i" ]]; then
max_word=
max_len=0
for j in $(strings $i); do
len="${#j}"
if (( len > max_len )); then
max_len="$len"
max_word="$j"
fi
done
echo "$i: '$max_word' ($max_len characters)"
fi
done
Genom att använda ${#j} undviker vi ett underskal och det blir dramatiskt snabbare än att använda wc -c inuti slingan.
Den ursprungliga versionen av skriptet tar 3,618 sekunder på sig att genomsöka textfilen, medan den nya versionen, som använder parameterexpansion, bara tar 0,06 sekunder - en betydande förbättring.
46.3. Skiftlägesomvandling
bash har fyra parameterexpansioner och två flaggor till kommandot declare för att stödja omvandling mellan versaler och gemener i strängar.
Så vad är skiftlägesomvandling bra för? Förutom det uppenbara estetiska värdet har den en viktig roll i programmering. Låt oss ta fallet med en databasuppslagning. Tänk dig att en användare har matat in en sträng i ett datainmatningsfält som vi vill slå upp i en databas. Det är möjligt att användaren skriver värdet med enbart versaler, enbart gemener eller en blandning av båda. Vi vill verkligen inte fylla vår databas med varje möjlig permutation av stavningar med versaler och gemener. Vad gör vi?
En vanlig metod för detta problem är att normalisera användarens indata. Det vill säga att omvandla dem till en standardiserad form innan vi försöker slå upp dem i databasen. Vi kan göra detta genom att omvandla alla tecken i användarens indata till antingen gemener eller versaler och se till att databasposterna är normaliserade på samma sätt.
Kommandot declare kan användas för att normalisera strängar till antingen versaler eller gemener. Med declare kan vi tvinga en variabel att alltid innehålla det önskade formatet, oavsett vad som tilldelas den.
#!/bin/bash
# ul-declare: demonstrate case conversion via declare
declare -u upper
declare -l lower
if [[ $1 ]]; then
upper="$1"
lower="$1"
echo "$upper"
echo "$lower"
fi
Som vi ser har kommandoradsargumentet (aBc) normaliserats.
[me@linuxbox ~]$ ul-declare aBc
ABC
abc
| Format | Resultat |
|---|---|
|
Omvandla alla matchande tecken till gemener. |
|
Omvandla det första matchande tecknet till gemen. |
|
Omvandla alla matchande tecken till versaler. |
|
Omvandla det första matchande tecknet till versal. |
Utöver declare finns det fyra parameterexpansioner som utför omvandling mellan versaler och gemener, vilket beskrivs i tabell 34-1.
#!/bin/bash
# ul-param: demonstrate case conversion via parameter expansion
if [[ "$1" ]]; then
echo "${1,,}"
echo "${1,}"
echo "${1^^}"
echo "${1^}"
fi
46.4. Aritmetisk utvärdering och expansion
Vi tittade på aritmetisk expansion i kapitel 7. Den används för att utföra olika aritmetiska operationer på heltal. Dess grundform är följande, där expression är ett giltigt aritmetiskt uttryck:
$((expression))
Detta är besläktat med det sammansatta kommandot som används för aritmetisk utvärdering (sanningstester) som vi stötte på i kapitel 27.
46.4.1. Talbaser
| Notation | Beskrivning |
|---|---|
|
Decimal som standard. |
|
Oktal. |
|
Hexadecimal. |
|
|
I kapitel 9 tittade vi på oktala tal (bas 8) och hexadecimala tal (bas 16). I aritmetiska uttryck stöder skalet heltalskonstanter i valfri bas. Tabell 34-2 listar de notationer som används för att ange baser.
[me@linuxbox ~]$ echo $((0xff))
255
[me@linuxbox ~]$ echo $((2#11111111))
255
46.4.2. Unära operatorer
Det finns två unära operatorer, + och -, som anger om ett tal är positivt eller negativt.
46.4.3. Enkel aritmetik
| Operator | Beskrivning |
|---|---|
|
Addition |
|
Subtraktion |
|
Multiplikation |
|
Heltalsdivision |
|
Exponentiering |
|
Modulo (rest) |
De vanliga aritmetiska operatorerna listas i tabell 34-3.
#!/bin/bash
# modulo: demonstrate the modulo operator
for ((i = 0; i <= 20; i = i + 1)); do
remainder=$((i % 5))
if (( remainder == 0 )); then
printf "<%d> " "$i"
else
printf "%d " "$i"
fi
done
printf "\n"
46.4.4. Tilldelning
Även om användningen kanske inte är uppenbar direkt kan aritmetiska uttryck utföra tilldelning. Vi har utfört tilldelning många gånger, om än i ett annat sammanhang. Varje gång vi ger en variabel ett värde utför vi tilldelning. Vi kan också göra det inne i aritmetiska uttryck.
[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ if (( foo = 5 )); then echo "It is true."; fi
It is true.
[me@linuxbox ~]$ echo $foo
5
I exemplet ovan tilldelar vi först ett tomt värde till variabeln foo och verifierar att den verkligen är tom. Därefter utför vi ett if med det sammansatta kommandot foo = 5. Denna process gör två intressanta saker: den tilldelar värdet 5 till variabeln foo, och den utvärderas till sant eftersom foo tilldelades ett värde som inte är noll.
| Notation | Beskrivning |
|---|---|
|
Enkel tilldelning. |
|
Additionstilldelning. |
|
Subtraktionstilldelning. |
|
Multiplikationstilldelning. |
|
Heltalsdivisionstilldelning. |
|
Modulotilldelning. |
|
Postinkrement. |
|
Postdekrement. |
|
Preinkrement. |
|
Predekrement. |
Obs: Det är viktigt att komma ihåg den exakta betydelsen av = i föregående uttryck. Ett enkelt = utför tilldelning. foo = 5 betyder Gör foo lika med 5, medan == utvärderar likhet. foo == 5 betyder Är foo lika med 5? Detta är en vanlig egenskap i många programspråk. I skalet kan det vara lite förvirrande eftersom kommandot test accepterar ett enkelt = för strängjämförelse. Det är ytterligare ett skäl att använda de modernare sammansatta kommandona [[ ]] och i stället för test.
[me@linuxbox ~]$ foo=1
[me@linuxbox ~]$ echo $((foo++))
1
[me@linuxbox ~]$ echo $foo
2
[me@linuxbox ~]$ foo=1
[me@linuxbox ~]$ echo $((++foo))
2
[me@linuxbox ~]$ echo $foo
2
46.4.5. Bitoperationer
| Operator | Beskrivning |
|---|---|
|
Bitvis negation. |
|
Vänsterskift. |
|
Högerskift. |
|
Bitvis AND. |
` |
` |
Bitvis OR. |
|
En klass av operatorer manipulerar tal på ett ovanligt sätt. Dessa operatorer arbetar på bitnivå. De används för vissa typer av lågnivåuppgifter, ofta med att sätta eller läsa bitflaggor. Bitoperatorerna listas i tabell 34-5.
[me@linuxbox ~]$ for ((i=0;i<8;++i)); do echo $((1<<i)); done
1
2
4
8
16
32
64
128
46.4.6. Logik
| Operator | Beskrivning |
|---|---|
|
Mindre än eller lika med. |
|
Större än eller lika med. |
|
Mindre än. |
|
Större än. |
|
Lika med. |
|
Inte lika med. |
|
Logiskt AND. |
` |
|
` |
Logiskt OR. |
|
Ternär operator. |
Som vi upptäckte i kapitel 27 stöder det sammansatta kommandot en mängd jämförelseoperatorer. Det finns ytterligare några som kan användas för att utvärdera logik. Tabell 34-6 ger en fullständig lista.
[me@linuxbox ~]$ if ((1)); then echo "true"; else echo "false"; fi
true
[me@linuxbox ~]$ if ((0)); then echo "true"; else echo "false"; fi
false
Exempel med ternär operator:
[me@linuxbox ~]$ a=0
[me@linuxbox ~]$ ((a<1?++a:--a))
[me@linuxbox ~]$ echo $a
1
[me@linuxbox ~]$ ((a<1?++a:--a))
[me@linuxbox ~]$ echo $a
0
Här ser vi den ternära operatorn fungera som en växlingsknapp. Varje gång den körs byter värdet av a från noll till ett eller tillbaka.
Tilldelning inuti ternära uttryck kan vara knepigt. Till exempel ger a<1?a+=1:a-=1 ett felmeddelande i bash om inte tilldelningarna omges av parenteser.
Mer komplett aritmetiskt skript:
#!/bin/bash
# arith-loop: script to demonstrate arithmetic operators
finished=0
a=0
printf "a\ta**2\ta**3\n"
printf "=\t====\t====\n"
until ((finished)); do
b=$((a**2))
c=$((a**3))
printf "%d\t%d\t%d\n" "$a" "$b" "$c"
((a<10?++a:(finished=1)))
done
46.4.7. Kommateckenoperatorn
Den sista operatorn vi ska titta på är kommateckenoperatorn. Den gör det möjligt att placera flera uttryck i en och samma utvärdering, så här: expr, expr, expr. Vi kan använda detta i fall där vi vill kombinera flera operationer i en enda utvärdering. För att demonstrera detta ska vi ändra skriptet arith-loop för att förenkla det med hjälp av kommateckenoperatorn.
(( expr, expr, expr ))
Här kunde vi pressa ihop alla beräkningar till en enda rad. Lägg märke till att det sista uttrycket i utvärderingen bestämmer utvärderingens slutstatus.
#!/bin/bash
# arith-loop2: script to demonstrate comma arithmetic operator
a=0
printf "a\ta**2\ta**3\n"
printf "=\t====\t====\n"
until (( a++, b=a**2, c=a**3, a>10 )); do
printf "%d\t%d\t%d\n" $a $b $c
done
Vi har sett hur skalet kan hantera heltalsaritmetik, men vad händer om vi behöver utföra mer avancerad matematik eller ens bara använda flyttal? Svaret är att vi inte kan det, åtminstone inte direkt med skalet. För att göra detta måste vi använda ett externt program. Det finns flera angreppssätt. Att bädda in Perl- eller AWK-program är en möjlig lösning, men det ligger tyvärr utanför denna boks ram.
46.5. bc - ett kalkylspråk med godtycklig precision
Ett annat tillvägagångssätt är att använda ett specialiserat kalkylprogram. Ett sådant program som finns på många Linuxsystem heter bc.
Programmet bc läser en fil skriven i sitt eget C-liknande språk och kör den. Ett bc-skript kan vara en separat fil, eller så kan det läsas från standardindata. Språket bc stöder en hel del funktioner, däribland variabler, slingor och programmerardefinierade funktioner. Vi ska inte täcka bc helt här, bara tillräckligt för att få en känsla för det. bc är väl dokumenterat i sin manualsida.
46.5.1. Använda bc
Vi börjar med ett enkelt bc-skript:
/* A very simple bc script */
2 + 2
Kör det:
[me@linuxbox ~]$ bc foo.bc
[me@linuxbox ~]$ bc -q
2 + 2
4
quit
[me@linuxbox ~]$ bc < foo.bc
4
[me@linuxbox ~]$ bc <<< "2+2"
4
Den första raden i skriptet är en kommentar. bc använder samma syntax för kommentarer som programspråket C. Kommentarer, som kan sträcka sig över flera rader, börjar med /* och slutar med */. Flaggan -q undertrycker startbannern. Eftersom bc kan läsa från standardindata fungerar det naturligt med omdirigeringar, here-strängar och here-dokument.
När det används interaktivt skriver vi helt enkelt uttryck och ser resultaten direkt. Kommandot quit avslutar sessionen.
46.5.2. Ett exempelskript
Verkligt exempel, månatliga lånebetalningar:
#!/bin/bash
# loan-calc: script to calculate monthly loan payments
PROGNAME="${0##*/}" # Use parameter expansion to get basename
usage () {
cat << _EOF_
Usage: $PROGNAME PRINCIPAL INTEREST MONTHS
Where:
PRINCIPAL is the amount of the loan.
INTEREST is the APR as a number (7% = 0.07).
MONTHS is the length of the loan's term.
_EOF_
}
if (($# != 3)); then
usage
exit 1
fi
principal=$1
interest=$2
months=$3
bc <<- EOF
scale = 10
i = $interest / 12
p = $principal
n = $months
a = p * ((i * ((1 + i) ^ n)) / (((1 + i) ^ n) - 1))
print a, "\n"
EOF
Exempel:
[me@linuxbox ~]$ loan-calc 135000 0.0775 180
1270.7222490000
I detta exempel beräknas den månatliga betalningen för ett lån på $135 000 till 7,75 procents årsränta över 180 månader. Precisionen i svaret styrs av den speciella variabeln scale i bc-programmet.
Även om notationen i bc skiljer sig något från skalaritmetik känns mycket av det bekant när man väl är van vid aritmetiska uttryck och variabler.
46.6. Sammanfattning
I detta kapitel lärde vi oss om många av de små saker som kan användas för att få det riktiga arbetet gjort i skript. När vår erfarenhet av skriptskrivning växer kommer förmågan att effektivt manipulera strängar och tal att visa sig vara mycket värdefull. Vårt skript loan-calc visar att även enkla skript kan skapas för att göra riktigt användbara saker.
46.7. Extrapoäng
Även om den grundläggande funktionaliteten i skriptet loan-calc är på plats är skriptet långt ifrån färdigt. För extrapoäng, försök att förbättra skriptet loan-calc med följande funktioner:
-
Fullständig verifiering av kommandoradsargumenten.
-
En kommandoradsflagga som implementerar ett
interaktivtläge där användaren får mata in lånebelopp, ränta och lånets löptid. -
Ett bättre format på utdatan.
46.8. Vidare läsning
-
Bash Reference Manual om parameterexpansion:
-
Bitoperationer:
-
Ternär operator:
-
Formel för låneamortering:
47. 35 - Arrayer
I föregående kapitel tittade vi på hur skalet kan manipulera strängar och tal. De datatyper vi hittills har tittat på kallas inom datavetenskapen skalära variabler; det vill säga variabler som innehåller ett enda värde.
I detta kapitel ska vi titta på en annan sorts datastruktur som kallas en array och som innehåller flera värden. Arrayer är en funktion som finns i praktiskt taget alla programspråk. Skalet stöder dem också, om än på ett ganska begränsat sätt. Trots det kan de vara användbara för att lösa vissa typer av programmeringsproblem.
47.1. Vad är arrayer?
Arrayer är variabler som innehåller mer än ett värde åt gången. Arrayer är organiserade som en tabell. Låt oss ta ett kalkylblad som exempel. Ett kalkylblad fungerar som en tvådimensionell array. Det har både rader och kolumner, och en enskild cell i kalkylbladet kan lokaliseras utifrån sin rad- och kolumnadress. En array fungerar på samma sätt. En array har celler, som kallas element, och varje element innehåller data. Ett enskilt arrayelement nås med hjälp av en adress som kallas index eller subskript.
De flesta programspråk stöder flerdimensionella arrayer. Ett kalkylblad är ett exempel på en flerdimensionell array med två dimensioner, bredd och höjd. Många språk stöder arrayer med godtyckligt antal dimensioner, även om två- och tredimensionella arrayer förmodligen är de vanligast använda.
47.2. Skapa en array
Arrayvariabler namnges precis som andra bash-variabler och skapas automatiskt när de används. Här är ett exempel:
[me@linuxbox ~]$ a[1]=foo
[me@linuxbox ~]$ echo ${a[1]}
foo
Här ser vi ett exempel på både tilldelning till och åtkomst av ett arrayelement. Med det första kommandot tilldelas element 1 i arrayen a värdet foo. Det andra kommandot visar det lagrade värdet i element 1. Klammerparenteser i det andra kommandot krävs för att förhindra att skalet försöker utföra sökvägsexpansion på namnet på arrayelementet.
En array kan också skapas med kommandot declare.
[me@linuxbox ~]$ declare -a a
47.3. Tilldela värden till en array
Värden kan tilldelas på ett av två sätt. Enskilda värden kan tilldelas med följande syntax, där name är namnet på arrayen och subscript är ett heltal (eller ett aritmetiskt uttryck) större än eller lika med noll:
name[subscript]=value
Observera att det första elementet i en array har subskript noll, inte ett. value är en sträng eller ett heltal som tilldelas arrayelementet.
Flera värden kan tilldelas med följande syntax, där name är namnet på arrayen och platshållarna value är värden som sekventiellt tilldelas elementen i arrayen, med början i element noll:
name=(value1 value2 ...)
Om vi till exempel vill tilldela förkortade veckodagar till arrayen days, kan vi göra så här:
[me@linuxbox ~]$ days=(Sun Mon Tue Wed Thu Fri Sat)
Det går också att tilldela värden till ett specifikt element genom att ange ett subskript för varje värde.
[me@linuxbox ~]$ days=([0]=Sun [1]=Mon [2]=Tue [3]=Wed [4]=Thu [5]=Fri [6]=Sat)
47.4. Komma åt arrayelement
Så vad är arrayer bra för? Precis som många datahanteringsuppgifter kan utföras med ett kalkylprogram, kan många programmeringsuppgifter utföras med arrayer.
#!/bin/bash
# hours: script to count files by modification time
usage () {
echo "usage: ${0##*/} directory" >&2
}
# Check that argument is a directory
if [[ ! -d "$1" ]]; then
usage
exit 1
fi
# Initialize array
for i in {0..23}; do hours[i]=0; done
# Collect data
for i in $(stat -c %y "$1"/* | cut -c 12-13); do
j="${i#0}"
((++hours[j]))
((++count))
done
# Display data
echo -e "Hour\tFiles\tHour\tFiles"
echo -e "----\t-----\t----\t-----"
for i in {0..11}; do
j=$((i + 12))
printf "%02d\t%d\t%02d\t%d\n" \
"$i" \
"${hours[i]}" \
"$j" \
"${hours[j]}"
done
printf "\nTotal files = %d\n" "$count"
Låt oss titta på ett enkelt exempel på datainsamling och presentation. Vi ska konstruera ett skript som granskar ändringstiderna för filerna i en angiven katalog. Utifrån dessa data ska vårt skript skriva ut en tabell som visar vid vilken timme på dygnet filerna senast ändrades. Ett sådant skript skulle kunna användas för att avgöra när ett system är som mest aktivt. Detta skript, som heter hours, ger detta resultat:
47.5. Arrayoperationer
Det finns många vanliga operationer på arrayer. Sådant som att ta bort arrayer, avgöra deras storlek, sortera dem och så vidare har många användningsområden i skript.
47.5.1. Skriva ut hela innehållet i en array
Subskripten * och @ kan användas för att komma åt varje element i en array. Precis som med positionsparametrar är notationen @ den mer användbara av de två. Här är en demonstration:
[me@linuxbox ~]$ animals=("a dog" "a cat" "a fish")
[me@linuxbox ~]$ for i in ${animals[*]}; do echo $i; done
a
dog
a
cat
a
fish
[me@linuxbox ~]$ for i in "${animals[*]}"; do echo $i; done
a dog a cat a fish
[me@linuxbox ~]$ for i in "${animals[@]}"; do echo $i; done
a dog
a cat
a fish
Vi skapar arrayen animals och tilldelar den tre strängar med två ord i varje. Därefter kör vi fyra slingor för att se effekten av orduppdelning på arrayens innehåll. Beteendet hos notationerna ${animals[*]} och ${animals[@]} är identiskt tills de sätts inom citationstecken. Notationen * ger ett enda ord som innehåller arrayens innehåll, medan notationen @ ger tre strängar med två ord, vilket stämmer med arrayens verkliga innehåll.
47.5.2. Avgöra antalet arrayelement
[me@linuxbox ~]$ a[100]=foo
[me@linuxbox ~]$ echo ${#a[@]}
1
[me@linuxbox ~]$ echo ${#a[100]}
3
Med hjälp av parameterexpansion kan vi bestämma antalet element i en array ungefär på samma sätt som vi tar reda på längden på en sträng. Här är ett exempel:
47.5.3. Hitta vilka subskript som används av en array
Eftersom bash tillåter att arrayer innehåller luckor i tilldelningen av subskript är det ibland användbart att avgöra vilka element som faktiskt finns. Detta kan göras med hjälp av parameterexpansion med följande former, där array är namnet på en arrayvariabel:
${!array[*]}
${!array[@]}
Liksom för de andra expansionerna som använder * och @ är formen @ inom citationstecken den mest användbara, eftersom den expanderar till separata ord.
[me@linuxbox ~]$ foo=([2]=a [4]=b [6]=c)
[me@linuxbox ~]$ for i in "${foo[@]}"; do echo $i; done
a
b
c
[me@linuxbox ~]$ for i in "${!foo[@]}"; do echo $i; done
2
4
6
47.5.4. Tilldela arrayelement med read -a
Det inbyggda kommandot read har en flagga (-a) som placerar ord i en indexerad array i stället för i en serie variabler, som vi har gjort tidigare. Här är ett exempel:
[me@linuxbox ~]$ declare -a foo
[me@linuxbox ~]$ read -a foo <<< "0th 1st 2nd 3rd 4th"
[me@linuxbox ~]$ for i in "${foo[@]}"; do echo "$i"; done
0th
1st
2nd
3rd
4th
47.5.5. Lägga till element i slutet av en array
Att känna till antalet element i en array hjälper oss inte om vi behöver lägga till värden i slutet av en array, eftersom de värden som returneras av notationerna * och @ inte talar om vilket det högsta använda arrayindexet är. Som tur är ger skalet oss en lösning. Genom att använda tilldelningsoperatorn += kan vi automatiskt lägga till värden i slutet av en array. Här tilldelar vi tre värden till arrayen foo och lägger sedan till tre till.
[me@linuxbox ~]$ foo=(a b c)
[me@linuxbox ~]$ echo ${foo[@]}
a b c
[me@linuxbox ~]$ foo+=(d e f)
[me@linuxbox ~]$ echo ${foo[@]}
a b c d e f
47.5.6. Läsa in en fil i en array
Nyare versioner av bash innehåller ett nytt inbyggt kommando som heter mapfile, vilket kan läsa standardindata direkt till en indexerad array. Dess syntax ser ut så här:
mapfile -options array
| Flagga | Beskrivning |
|---|---|
|
Använd |
|
Läs bara |
|
Börja tilldela vid index |
|
Hoppa över |
|
Ta bort den avslutande avgränsaren från varje rad. |
Kommandot mapfile har några intressanta flaggor, varav vissa visas i tabell 35-1.
#!/bin/bash
# array-mapfile - demonstrate mapfile builtin
DICTIONARY=/usr/share/dict/words
WORDLIST=~/wordlist.txt
declare -a words
# create filtered word list
grep -v \' < "$DICTIONARY" \
| grep -v "[[:upper:]]" \
| shuf > "$WORDLIST"
# read WORDLIST into array
mapfile -t -n 32767 words < "$WORDLIST"
# create four word passphrase
while [[ -z $REPLY ]]; do
echo "${words[$RANDOM]}" \
"${words[$RANDOM]}" \
"${words[$RANDOM]}" \
"${words[$RANDOM]}"
echo
read -r -p "Enter to continue, q to quit > "
echo
done
47.5.7. Skiva upp en array
Det finns en form av parameterexpansion som vi kan använda för att extrahera en grupp sammanhängande element, kallad en skiva, ur en array. Denna expansion ger de arrayelement som tillhör den önskade skivan ur den ursprungliga arrayen, som visas nedan.
[me@linuxbox ~]$ arr=(0th 1st 2nd 3rd 4th)
[me@linuxbox ~]$ echo "${arr[@]:2:3}"
2nd 3rd 4th
[me@linuxbox ~]$ echo "${arr[@]: -2:2}"
3rd 4th
[me@linuxbox ~]$ arr2=("${arr[@]:2:3}")
47.5.8. Sortera en array
Precis som med kalkylblad är det ofta nödvändigt att sortera värdena i en datakolumn. Skalet har inget direkt sätt att göra detta, men det är inte svårt med lite kod.
#!/bin/bash
# array-sort: Sort an array
a=(f e d c b a)
echo "Original array: " "${a[@]}"
a_sorted=($(for i in "${a[@]}"; do echo "$i"; done | sort))
echo "Sorted array:"
echo "${a_sorted[@]}"
47.5.9. Ta bort en array
För att ta bort en array använder man kommandot unset.
[me@linuxbox ~]$ foo=(a b c d e f)
[me@linuxbox ~]$ unset foo
[me@linuxbox ~]$ unset 'foo[2]'
unset kan också användas för att ta bort enskilda arrayelement.
47.6. Associativa arrayer
bash version 4.0 och senare stöder associativa arrayer. Associativa arrayer använder strängar snarare än heltal som arrayindex och skapar därmed nyckel-värde-par ungefär som en ordlista eller hashtabell. Denna förmåga möjliggör intressanta nya sätt att hantera data. Vi kan till exempel skapa en array som heter colors och använda färgnamn som index.
declare -A colors
colors["red"]="#ff0000"
colors["green"]="#00ff00"
colors["blue"]="#0000ff"
echo ${colors["blue"]}
Till skillnad från heltalsindexerade arrayer, som skapas bara genom att man hänvisar till dem, måste associativa arrayer skapas uttryckligen med kommandot declare och flaggan -A. Associativa arrayelement nås på ungefär samma sätt som heltalsindexerade arrayer.
Vi kan använda associativa arrayers nyckel-värde-egenskap för att utföra uppslagsuppgifter. En associativ array gör det möjligt för oss att komma åt önskade data direkt i stället för att behöva söka igenom dem sekventiellt som med en indexerad array. I följande program läser vi in namnen på alla filer i katalogen /usr/bin tillsammans med deras respektive storlekar i en array och utför sedan uppslagningar utifrån användarens indata.
#!/bin/bash
# array-lookup - demonstrate lookup using associative array
declare -A cmds
# fill array with commands and file sizes
cd /usr/bin || exit 1
echo "Loading commands..."
for i in ./*; do
cmds["$i"]=$(stat -c "%s" "$i")
done
echo "${#cmds[@]} commands loaded"
# perform lookup
while true; do
read -r -p "Enter command (empty to quit) -> "
[[ -z $REPLY ]] && break
if [[ -x $REPLY ]]; then
echo "$REPLY" "${cmds[./$REPLY]}" "bytes"
else
echo "No such command '$REPLY'."
fi
done
47.6.1. Använda associativa arrayer för att simulera flera dimensioner
Även om det är sant att bash bara direkt stöder endimensionella arrayer är det inte svårt att låtsas ha flerdimensionella arrayer genom att använda en associativ array och skapa indexsträngar som ser ut som adresser i en flerdimensionell array. Här är ett exempel:
#!/bin/bash
# array-multi - simulate a multi-dimensional array
declare -A multi_array
# Load array with a sequence of numbers
counter=1
for row in {1..10}; do
for col in {1..5}; do
address="$row, $col"
multi_array["$address"]=$counter
((counter++))
done
done
# Output array contents
for row in {1..10}; do
for col in {1..5}; do
address="$row, $col"
echo -ne "${multi_array["$address"]}" "\t"
done
echo
done
47.7. Sammanfattning
Om vi söker efter ordet array i bash-manualsidan hittar vi många ställen där bash använder arrayvariabler. De flesta av dessa är ganska obskyra, men de kan ge viss nytta under särskilda omständigheter. Hela ämnet arrayer är faktiskt ganska underutnyttjat i skalprogrammering, till stor del eftersom de traditionella Unix-skalprogrammen (som sh) saknade allt stöd för arrayer. Denna brist på popularitet är olycklig, eftersom arrayer används flitigt i andra programspråk och erbjuder ett kraftfullt verktyg för att lösa många slags programmeringsproblem.
Arrayer och slingor har en naturlig samhörighet och används ofta tillsammans. Följande slingform är särskilt väl lämpad för att beräkna indexsubskript för arrayer:
for ((expr; expr; expr))
47.8. Vidare läsning
-
Skalärvärden:
-
Associativa arrayer:
48. 36 - Exotika
I detta, det sista kapitlet på vår resa, ska vi titta på lite blandat smått och gott. Även om vi utan tvekan har täckt mycket mark i de föregående kapitlen finns det många bash-funktioner som vi inte har tagit upp. De flesta är ganska obskyra och användbara främst för dem som integrerar bash i en Linuxdistribution. Det finns dock några som, även om de inte används dagligen, är till hjälp för vissa programmeringsproblem. Dem ska vi gå igenom här.
48.1. Gruppkommandon och underskal
bash låter kommandon grupperas tillsammans på två sätt:
{ command1; command2; [command3; ...] }
(command1; command2; [command3; ...])
Den första formen är ett gruppkommando; den andra är ett underskal.
Klammerparenteserna i ett gruppkommando måste skiljas från kommandona med ett blanksteg, och det sista kommandot måste avslutas med antingen ett semikolon eller en nyrad före den avslutande klammerparentesen.
Denna syntaxregel är lätt att glömma bort eftersom klammerparenteserna ser ut nästan som skiljetecken, men i bash tolkas de som reserverade ord.
Båda formerna är användbara för omstyrning.
Utan gruppering:
ls -l > output.txt
echo "Listing of foo.txt" >> output.txt
cat foo.txt >> output.txt
Med ett gruppkommando:
{ ls -l; echo "Listing of foo.txt"; cat foo.txt; } > output.txt
Med ett underskal:
(ls -l; echo "Listing of foo.txt"; cat foo.txt) > output.txt
De är särskilt användbara med pipelines:
{ ls -l; echo "Listing of foo.txt"; cat foo.txt; } | lpr
Den viktiga skillnaden är denna:
-
ett gruppkommando körs i det aktuella skalet
-
ett underskal körs i en underordnad kopia av det aktuella skalet
Detta innebär att ett underskal inte kan ändra förälderskalets miljö.
Det gäller variabler, funktioner, alias och den aktuella arbetskatalogen.
Gruppkommando, exempel:
[me@linuxbox ~]$ foo="Original Value"
[me@linuxbox ~]$ { foo="Altered Value"; echo $foo; }
Altered Value
[me@linuxbox ~]$ echo $foo
Altered Value
Underskal, exempel:
[me@linuxbox ~]$ foo="Original Value"
[me@linuxbox ~]$ ( foo="Altered Value"; echo $foo )
Altered Value
[me@linuxbox ~]$ echo $foo
Original Value
Detta är praktiskt för tillfälliga katalogbyten:
[me@linuxbox ~]$ pwd
/home/me
[me@linuxbox ~]$ ( cd /usr/bin; pwd )
/usr/bin
[me@linuxbox ~]$ pwd
/home/me
I allmänhet är gruppkommandon att föredra om inte ett verkligt underskal behövs, eftersom de är snabbare och kräver mindre minne.
Skriptet array-2 som följer demonstrerar detta tydligt genom att gruppera slingor och skicka deras kombinerade utdata via sort.
Följande skript använder gruppkommandon med associativa arrayer för att lista filer i en katalog tillsammans med ägar- och gruppägarstatistik:
#!/bin/bash
# array-2: Use arrays to tally file owners
declare -A files file_group file_owner groups owners
if [[ ! -d "$1" ]]; then
echo "Usage: array-2 dir" >&2
exit 1
fi
for i in "$1"/*; do
owner="$(stat -c %U "$i")"
group="$(stat -c %G "$i")"
files["$i"]="$i"
file_owner["$i"]="$owner"
file_group["$i"]="$group"
((++owners[$owner]))
((++groups[$group]))
done
# List the collected files
{ for i in "${files[@]}"; do
printf "%-40s %-10s %-10s\n" \
"$i" "${file_owner["$i"]}" "${file_group["$i"]}"
done; } | sort
echo
# List owners
echo "File owners:"
{ for i in "${!owners[@]}"; do
printf "%-10s: %5d file(s)\n" "$i" "${owners["$i"]}"
done; } | sort
echo
# List groups
echo "File group owners:"
{ for i in "${!groups[@]}"; do
printf "%-10s: %5d file(s)\n" "$i" "${groups["$i"]}"
done; } | sort
48.2. Processubstitution
Vi såg ett exempel på problemet med underskalets miljö i kapitel 28 när vi upptäckte att ett read-kommando i en pipeline inte fungerar så som man intuitivt skulle förvänta sig. För att repetera: om vi bygger en pipeline som denna kommer innehållet i variabeln REPLY alltid att vara tomt eftersom kommandot read körs i ett underskal, och dess kopia av REPLY förstörs när underskalet avslutas.
echo "foo" | read
echo $REPLY
Eftersom kommandon i pipelines alltid körs i underskal kommer varje kommando som tilldelar variabler att stöta på detta problem. Som tur är tillhandahåller skalet en exotisk expansionsform som kallas processubstitution och som kan användas för att komma runt problemet.
Processubstitution uttrycks på två sätt.
<(list)
För processer som producerar standardutdata ser det ut så här:
>(list)
eller, för processer som tar emot standardindata, så här:
read < <(echo "foo")
echo $REPLY
här är list en lista över kommandon.
[me@linuxbox ~]$ echo <(echo "foo")
/dev/fd/63
För att lösa vårt problem med read kan vi använda processubstitution på följande sätt:
#!/bin/bash
# pro-sub: demo of process substitution
while read -r attr links owner group size date time filename; do
cat << _EOF_
Filename:
$filename
Size:
$size
Owner:
$owner
Group:
$group
Modified:
$date $time
Links:
$links
Attributes: $attr
_EOF_
done < <(ls -l --time-style="+%F %H:%m" | tail -n +2)
48.3. Bygga kommandon med eval
Det inbyggda kommandot eval är ett märkligt och mystiskt kommando. Enkelt beskrivet tar det en lista av argument, fogar samman dem till en sträng och skickar den till skalet för körning. Den naturliga frågan blir då: vad är detta bra för? Varför skulle vi använda det?
Det finns vissa situationer i skalprogrammering där vi behöver bygga ett kommando dynamiskt under körning, och eval gör detta möjligt. Låt oss göra ett experiment. Även om vi inte tog upp det uttryckligen, är det möjligt att lägga ett kommando i en strängvariabel och sedan förlita sig på parameterexpansion för att expandera variabeln till ett kommando.
[me@linuxbox ~]$ cmd="echo foo"
[me@linuxbox ~]$ $cmd
foo
Nu ska vi titta på vad som händer när vi inkluderar en variabel i vårt kommando.
[me@linuxbox ~]$ str=abcde
[me@linuxbox ~]$ cmd='echo $str'
[me@linuxbox ~]$ $cmd
$str
[me@linuxbox ~]$ eval "$cmd"
abcde
[me@linuxbox ~]$ str=ABCDE
[me@linuxbox ~]$ eval "$cmd"
ABCDE
Det gör vad vi förväntar oss, men låt oss se vad som händer när vi tilldelar ett nytt värde till str.
Kommandot eval har dåligt rykte. Detta beror på hur lätt det är att införa säkerhetsbrister i ett skalskript med eval. Om strängen som ges till eval kommer från en extern källa (det vill säga användarinmatning), måste man vara noga med att säkerställa att den inte innehåller obehöriga kommandon (det som kallas en kodinjektionsattack). Kontrollera alltid innehållet i strängen som skickas till eval.
48.3.1. En Wordle-hjälpare
I skriptet som följer ska vi använda eval för att hjälpa till att hitta möjliga svar på ett Wordle-pussel.
#!/bin/bash
# eval-wordle - demonstrate eval by solving wordle puzzles
PROGNAME=${0##*/}
DICTIONARY=/usr/share/dict/words
usage() {
printf "%s\n" "Usage: ${PROGNAME} [-h|--help]"
printf "%s\n" "
${PROGNAME} regex +-char..."
}
help_message() {
cat << _EOF_
$PROGNAME - Wordle Helper
$(usage)
Options:
-h, --help
Display this help message and exit.
Arguments:
The first argument must be a five character regular
expression at minimum of '.....' representing five
unknown characters in the answer. This is followed by
zero or more characters known to be either present or
absent from the answer. These are expressed as either
+char for letters known to be present or -char for
letters known not to be in the answer.
Example:
$PROGNAME a.... +o -v
This means we know that the answer starts with 'a' in
the first position and also contains an 'o' but does
not contain a 'v'.
_EOF_
return
}
add_plus() { # Add a letter
local char="$1"
echo " | grep $char"
return
}
add_minus() { # Subtract a letter
local char="$1"
echo " | grep -v $char"
return
}
# Parse command-line
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
help_message
exit 0
fi
if (( "${#1}" == 5 )); then
known_chars="$1"
shift
else
echo "First argument must be a 5 character regex." >&2
exit 1
fi
cmd="LANG=POSIX grep '^.....$' $DICTIONARY \
| grep -v '[[:punct:]]' \
| grep -v '[[:upper:]]' \
| grep '$known_chars'"
while [[ -n "$1" ]]; do
case "$1" in
-[[:alpha:]])
cmd="${cmd}$(add_minus "${1:1}")"
;;
+[[:alpha:]])
cmd="${cmd}$(add_plus "${1:1}")"
;;
*)
echo "Invalid argument '$1'" >&2
exit 1
;;
esac
shift
done
eval "$cmd | tee >(wc -l)"
48.4. Fällor (traps)
I kapitel 10 såg vi hur program kan reagera på signaler. Skript kan göra detta också med hjälp av det inbyggda kommandot trap.
Syntax:
trap argument signal [signal...]
Enkelt exempel:
#!/bin/bash
# trap-demo: simple signal handling demo
trap "echo 'I am ignoring you.'" SIGINT SIGTERM
for i in {1..5}; do
echo "Iteration $i of 5"
sleep 5
done
Om användaren trycker Ctrl-c under körningen utlöses fällan i stället för att skriptet avslutas.
Signalen fångas med andra ord upp och ersätts med det beteende som skriptets författare har valt.
Att använda funktioner är ofta renare:
#!/bin/bash
# trap-demo2: simple signal handling demo
exit_on_signal_SIGINT () {
echo "Script interrupted." 2>&1
exit 0
}
exit_on_signal_SIGTERM () {
echo "Script terminated." 2>&1
exit 0
}
trap exit_on_signal_SIGINT SIGINT
trap exit_on_signal_SIGTERM SIGTERM
for i in {1..5}; do
echo "Iteration $i of 5"
sleep 5
done
trap stöder också interna fällpunkter som EXIT och ERR.
#!/bin/bash
# trap-demo3 - demonstrate ERR and EXIT signal handling
trap 'echo "There is an error."' ERR
trap 'echo "The program has ended."' EXIT
echox "Running..."
read -r -p "Say something... " something
echo "$something"
Detta skript visar att ERR aktiveras när ett kommando avslutas med status som inte är noll, medan EXIT aktiveras när skriptet avslutas.
Tillsammans gör dessa fällpunkter det möjligt att centralisera städning och felrapportering i stället för att sprida ut den genom hela skriptet.
I exemplet trap-demo3 utlöser det felstavade kommandot echox ERR-fällan, och EXIT-fällan körs när skriptet avslutas.
48.4.1. Tillfälliga filer
En vanlig anledning att använda fällor är att städa upp tillfälliga filer när ett skript avslutas eller avbryts.
Namngivning av tillfälliga filer är inte bara en bekvämlighetsfråga; det är också en säkerhetsfråga när skript körs i delade kataloger som /tmp.
Om filnamn är förutsägbara kan en angripare eventuellt utnyttja detta i en så kallad temp race-attack. Att använda mktemp minskar denna risk avsevärt genom att skapa svårgissade namn på ett säkert sätt.
Traditionell ad hoc-namngivning:
tempfile=/tmp/$(basename $0).$$.$RANDOM
Bättre:
tempfile=$(mktemp /tmp/foobar.$$.XXXXXXXXXX)
För vanliga användare är en privat temp-katalog under $HOME ofta att föredra:
[[ -d $HOME/tmp ]] || mkdir $HOME/tmp
48.5. Asynkron körning
48.5.1. wait
Det är ibland önskvärt att utföra mer än en uppgift samtidigt. Vi har sett hur alla moderna operativsystem åtminstone är fleruppgiftsystem, om inte även fleranvändarsystem. Skript kan konstrueras så att de beter sig på ett sätt som liknar fleruppgiftskörning.
Vanligtvis innebär detta att man startar ett skript som i sin tur startar ett eller flera barnskript för att utföra ytterligare en uppgift medan förälderskriptet fortsätter att köra. Men när en serie skript körs på detta sätt kan det uppstå problem med att hålla föräldern och barnet samordnade. Vad händer till exempel om föräldern eller barnet är beroende av den andra, och det ena skriptet måste vänta på att det andra ska slutföra sin uppgift innan det kan slutföra sin egen?
#!/bin/bash
# async-parent: Asynchronous execution demo (parent)
echo "Parent: starting..."
echo "Parent: launching child script..."
async-child &
pid=$!
echo "Parent: child (PID= $pid) launched."
echo "Parent: continuing..."
sleep 2
echo "Parent: pausing to wait for child to finish..."
wait "$pid"
echo "Parent: child is finished. Continuing..."
echo "Parent: parent is done. Exiting."
bash har ett inbyggt kommando som hjälper till att hantera asynkron körning av detta slag. Kommandot wait gör att ett förälderskript pausar tills en angiven process (det vill säga barnskriptet) avslutas.
#!/bin/bash
# async-child: Asynchronous execution demo (child)
echo "Child: child is running..."
sleep 5
echo "Child: child is done. Exiting."
Vi ska först demonstrera kommandot wait. För att göra detta behöver vi två skript. Först ett förälderskript.
48.6. Namngivna rör
Unix-liknande system stöder en särskild typ av fil som kallas ett namngivet rör, eller FIFO. Det kan användas för att koppla samman två processer precis som en pipeline, men via ett namngivet filsystemobjekt.
Namngivna rör är inte lika vanliga som andra former av kommunikation mellan processer, men de är bra att känna till och de passar naturligt in i Unix-tankesättet att nästan allt kan behandlas som en fil.
De passar också naturligt in i klient-server-tänkande, där en process producerar data och en annan förbrukar den. Ett namngivet rör är ett enkelt sätt att koppla samman sådana processer.
48.6.1. Skapa ett namngivet rör
Skapa ett med mkfifo:
[me@linuxbox ~]$ mkfifo pipe1
[me@linuxbox ~]$ ls -l pipe1
prw-r--r-- 1 me me 0 2009-07-17 06:41 pipe1
Använd det i en terminal:
[me@linuxbox ~]$ ls -l > pipe1
Och i en annan terminal:
[me@linuxbox ~]$ cat < pipe1
Det första kommandot blockerar tills det andra börjar läsa.
Det namngivna röret beter sig med andra ord som en vanlig pipeline vars ändpunkter råkar ligga i filsystemet.
Så dessa två kommandon:
process1 > named_pipe
process2 < named_pipe
beter sig ungefär som denna vanliga pipeline:
process1 | process2
48.7. Sammanfattning
Ja, därmed är vår resa fullbordad. Det enda som återstår nu är att öva, öva, öva. Även om vi täckte mycket mark under vår vandring finns det så mycket mer att utforska. Det finns tusentals kommandoradsprogram som väntar på att upptäckas och uppskattas. Börja gräva runt i /usr/bin och se efter!
För läsare som fortfarande vill ha mer finns uppföljningsboken Adventures with the Linux Command Line, tillgänglig från LinuxCommand.org.
48.8. Vidare läsning
-
Avsnittet
Compound Commandsibash-manualsidan beskriver gruppkommandon och underskal. -
Avsnittet
EXPANSIONibash-manualsidan behandlar processubstitution. -
Advanced Bash-Scripting Guide om processubstitution:
-
Linux Journal om namngivna rör:
Kolofon
Den här boken skrevs ursprungligen med OpenOffice.org Writer i typsnitten Liberation Serif och Sans på en Dell Inspiron 530N, fabrikskonfigurerad med Ubuntu 8.04. PDF-versionen av texten genererades direkt av OpenOffice.org Writer. Andra internetutgåvan producerades på samma dator med LibreOffice Writer under Ubuntu 12.04. Tredje och efterföljande internetutgåvor producerades med LibreOffice Writer på en System76 Ratel Pro-dator med Ubuntu LTS (versionerna 14.04–24.04). Tryckt på 100 % återvunna elektroner.