Skal och skriptning
Skalet är ett effektivt, textbaserat gränssnitt till din dator.
Skalprompten är det som möter dig när du öppnar en terminal. Den låter dig köra program och kommandon. Vanliga kommandon är:
cdför att byta kataloglsför att lista filer och katalogermvochcpför att flytta och kopiera filer
Men skalet låter dig göra så mycket mer. Du kan anropa vilket program som helst på datorn, och det finns kommandoradsverktyg för i princip allt du kan vilja göra. De är ofta effektivare än grafiska motsvarigheter. Vi går igenom många av dem i den här kursen.
Skalet erbjuder också ett interaktivt programmeringsspråk (“skriptning”). Det finns många skal:
- Du har troligen använt
shellerbash. - Det finns också skal som följer språk, som
csh. - Eller “bättre” skal som
fish,zshochksh.
I den här kursen fokuserar vi på de allmänt förekommande sh och bash,
men prova gärna andra.
Jag gillar fish.
Skalprogrammering är ett mycket användbart verktyg i din verktygslåda.
Du kan antingen skriva program direkt i prompten,
eller i en fil.
#!/bin/sh + chmod +x gör ett skalskript körbart.
Arbeta med skalet
Kör ett kommando flera gånger:
for i in $(seq 1 5); do echo hello; done
Det finns mycket att packa upp här:
for x in list; do BODY; done;avslutar ett kommando, motsvarar radbrytning- delar upp
list, tilldelar varje element tillx, och kör body - uppdelningen är “whitespace splitting”, som vi återkommer till
- skalet använder inte klammerparenteser här, därför
do+done
$(seq 1 5)- kör programmet
seqmed argumenten1och5 - ersätter hela
$()med programmets utdata - motsvarar
for i in 1 2 3 4 5
- kör programmet
echo hello- allt i ett skalskript är ett kommando
- här kör vi kommandot
echo, som skriver ut sina argument med argumentethello. - alla kommandon söks i
$PATH(kolonseparerad)
Vi har variabler:
for f in $(ls); do echo $f; done
Det skriver ut varje filnamn i aktuell katalog.
Du kan också sätta variabler med = (ingen blanktecken):
foo=bar
echo $foo
Det finns också en mängd “specialvariabler”:
$1till$9: argument till skriptet$0: namnet på själva skriptet$#: antal argument$$: process-ID för nuvarande skal
För att bara skriva ut kataloger:
for f in $(ls); do if test -d $f; then echo dir $f; fi; done
Här finns mer att packa upp:
if CONDITION; then BODY; fiCONDITIONär ett kommando. Om det avslutas med statuskod 0 (lyckat), körsBODY.- du kan också använda
elseellerelif - återigen inga klammerparenteser, därför
then+fi
testär ett annat program som erbjuder olika kontroller och jämförelser, och avslutas med 0 om villkoret är sant ($?)man COMMANDär din vän:man test- kan också anropas med
[+]:[ -d $f ]- se
man testochwhich "["
- se
Men vänta. Det här är fel. Vad händer om en fil heter “My Documents”?
for f in $(ls)expanderar tillfor f in My Documents- först körs testet på
My, sedan påDocuments - inte alls vad vi ville
- det här är en av de största felkällorna i skalskript
Argumentsplittring
Bash delar argument på blanktecken, vilket inte alltid är det du vill.
- du behöver citattecken för att hantera mellanslag i argument
for f in "My Documents"skulle fungera korrekt - samma problem finns någon annanstans, ser du var?
test -d $f: om$finnehåller blanktecken fårtestfel echoråkar vara okej, eftersom split + join med mellanslag- men vad händer om ett filnamn innehåller radbrytning? då blir det ett blanksteg
- citera alla variabler som du inte vill ska splittras
- men hur fixar vi skriptet ovan?
vad tror du att
for f in "$(ls)"gör?
Globbing är svaret.
- bash kan hitta filer med mönster:
*vilken teckensträng som helst?ett godtyckligt enskilt tecken{a,b,c}något av dessa tecken
for f in *: alla filer i den här katalogen- vid globbing blir varje matchad fil ett eget argument
- du måste fortfarande citera vid användning:
test -d "$f"
- du måste fortfarande citera vid användning:
- du kan skapa avancerade mönster:
for f in a*: alla filer i aktuell katalog som börjar påafor f in foo/*.txt: alla.txt-filer ifoofor f in foo/*/p??.txtalla textfiler på tre bokstäver som börjar på p i underkataloger tillfoo
Problem med blanktecken slutar inte där:
if [ $foo = "bar" ]; then– ser du problemet?- vad händer om
$fooär tom? argumenten till[blir=ochbar… - det går att kringgå med
[ x$foo = "xbar" ], men usch - använd i stället
[[: bash-inbyggd jämförare med särskild parsning- den tillåter också
&&i stället för-a,||i stället för-o, osv.
- den tillåter också
Komponerbarhet
Skalet är kraftfullt delvis tack vare komponerbarhet. Du kan kedja flera program i stället för att ha ett enda program som gör allt.
Nyckeltecknet är | (pipe).
a | bbetyder att bådeaochbkörs och att all utdata frånaskickas som indata tillboch att utdata frånbskrivs ut
Alla program du startar (“processer”) har tre “strömmar”:
STDIN: när programmet läser indata kommer den härifrånSTDOUT: när programmet skriver ut något går det hitSTDERR: en andra utström som programmet kan välja att använda- som standard är
STDINditt tangentbord, ochSTDOUTochSTDERRgår båda till terminalen. Men det kan du ändra.a | bkopplarSTDOUTföratillSTDINförb.- du har också:
a > foo(STDOUTfrånagår till filenfoo)a 2> foo(STDERRfrånagår till filenfoo)a < foo(STDINtillaläses från filenfoo)- tips:
tail -fskriver ut en fil medan den skrivs
- varför är detta användbart?
för att du kan bearbeta ett programs utdata.
ls | grep foo: alla filer som innehåller ordetfoops | grep foo: alla processer som innehåller ordetfoojournalctl | grep -i intel | tail -n5: de senaste 5 systemloggraderna med ordet intel (skiftlägesokänsligt)who | sendmail -t me@example.comskicka listan över inloggade användare tillme@example.com- detta är grunden för mycket datahantering, som vi tar upp senare
Bash har också flera andra sätt att komponera program.
Du kan gruppera kommandon med (a; b) | tac.
Det kör a, sedan b, och skickar all deras utdata till tac,
som skriver ut indata i omvänd ordning.
Ett mindre känt men mycket användbart sätt är process substitution.
b <(a) kör a,
skapar ett temporärt filnamn för dess utström,
och skickar det filnamnet till b.
Till exempel:
diff <(journalctl -b -1 | head -n20) <(journalctl -b -2 | head -n20)
visar skillnaden mellan de första 20 raderna i senaste bootloggen och bootloggen före den.
Jobb- och processkontroll
Vad gör du om du vill köra långvariga saker i bakgrunden?
- suffixet
&kör ett program “i bakgrunden”- du får tillbaka prompten direkt
- praktiskt om du vill köra två program samtidigt,
som server och klient:
server & client - notera att programmet fortfarande har din terminal som
STDOUTprova:server > server.log & client
- se alla sådana processer med
jobs- notera att det står “Running”
- ta tillbaka ett jobb i förgrunden med
fg %JOB(utan argument = senaste) - om du vill bakgrundssätta aktuellt program:
^Z+bg(^ZbetyderCtrl+Z)^Zstoppar aktuell process och gör den till ett “jobb”bgkör senaste jobbet i bakgrunden (som om du hade skrivit&)
- bakgrundsjobb är fortfarande knutna till din nuvarande session,
och avslutas när du loggar ut.
disownlåter dig bryta den kopplingen. Eller användnohup. $!är pid för senaste bakgrundsprocessen
Vad gäller annan processaktivitet på datorn?
psär din vän: listar körande processerps -A: skriv ut processer från alla användare (ocksåps ax)pshar många argument: seman ps
pgrep: hitta processer via sökning (somps -A | grep)pgrep -af: sök och visa med argument
kill: skicka en signal till en process via ID (pkillsöker +-f)- signaler säger åt en process att “göra något”
- vanligast:
SIGKILL(-9eller-KILL): säg åt den att avsluta nu motsvarar^\ - även
SIGTERM(-15eller-TERM): be den avsluta kontrollerat motsvarar^C
Flaggor
De flesta kommandoradsverktyg tar parametrar via flaggor.
Flaggor finns oftast i kort form (-h) och lång form (--help).
Vanligen ger CMD -h eller man CMD en lista över flaggor som programmet stöder.
Korta flaggor kan oftast kombineras,
så rm -r -f är samma som rm -rf eller rm -fr.
Vissa vanliga flaggor är i praktiken en standard,
och du ser dem i många program:
-asyftar ofta på alla filer (inklusive de som börjar med punkt)-fsyftar ofta på att tvinga något, som irm -f-hvisar hjälp för de flesta kommandon-vslår ofta på utförlig utdata-Vskriver oftast ut kommandots version
Ett dubbelt bindestreck -- används också i inbyggda kommandon och många andra kommandon
för att markera slutet på kommandoalternativ,
varefter endast positionsargument accepteras.
Om du därför har en fil som heter -v (det går) och vill köra grep på den,
fungerar grep pattern -- -v medan grep pattern -v inte gör det.
Ett sätt att skapa en sådan fil är faktiskt touch -- -v.
Övningar
-
Om du är helt ny i skalet kan du läsa en mer heltäckande guide, till exempel BashGuide. Om du vill ha en djupare introduktion är The Linux Command Line en bra resurs.
-
PATH, which, type
Vi pratade kort om att miljövariabeln
PATHanvänds för att hitta programmen du kör från kommandoraden. Låt oss utforska det lite mer.- Kör
echo $PATH(ellerecho $PATH | tr -s ':' '\n'för snyggare utskrift) och granska innehållet. Vilka sökvägar listas? - Kommandot
whichletar upp ett program i användarens PATH. Provawhichför vanliga kommandon somecho,lsellermv. Notera attwhichär lite begränsat eftersom det inte förstår skalalias. Testatypeochcommand -vför samma kommandon. Hur skiljer sig utdata? - Kör
PATH=och testa de tidigare kommandona igen. Vissa fungerar och vissa inte. Kan du lista ut varför?
- Kör
- Specialvariabler
- Till vad expanderar
~? Vad sägs om.? Och..? - Vad gör variabeln
$?? - Vad gör variabeln
$_? - Till vad expanderar
!!? Och!!*? Och!l? - Leta upp dokumentation för dessa och bekanta dig med dem
- Till vad expanderar
-
xargs
Ibland fungerar piping inte riktigt, eftersom kommandot som tar emot data inte förväntar sig ett radseparerat format. Till exempel visar kommandot
fileegenskaper för en fil.Kör
ls | fileochls | xargs file. Vad görxargs? -
Shebang
När du skriver ett skript kan du ange vilket program som ska tolka skriptet, via en shebang-rad. Skriv ett skript som heter
hellomed innehållet nedan, gör det körbart medchmod +x hello, och kör det sedan med./hello. Ta därefter bort första raden och kör igen. Hur använder skalet den första raden?#! /usr/bin/python print("Hello World!")Du kommer ofta att se program med en shebang som ser ut så här:
#! usr/bin/env bash. Det är en mer portabel lösning med egna för- och nackdelar. Hur skiljer sigenvfrånwhich? Vilken miljövariabel använderenvför att avgöra vilket program som ska köras? -
Pipes, process substitution, subshell
Skapa ett skript som heter
slow_seq.shmed innehållet nedan, och körchmod +x slow_seq.shför att göra det körbart.#! /usr/bin/env bash for i in $(seq 1 10); do echo $i; sleep 1; donePipes (och process substitution) skiljer sig från att använda subshell-körning, alltså
$(). Kör följande kommandon och observera skillnaderna:./slow_seq.sh | grep -P "[3-6]"grep -P "[3-6]" <(./slow_seq.sh)echo $(./slow_seq.sh) | grep -P "[3-6]"
- Övrigt
- Kör
touch {a,b}{a,b}och sedanls. Vad dök upp? - Ibland vill du behålla STDIN och samtidigt skriva till fil.
Kör
echo HELLO | tee hello.txt. - Kör
cat hello.txt > hello.txt. Vad tror du händer? Vad händer faktiskt? - Kör
echo HELLO > hello.txtoch sedanecho WORLD >> hello.txt. Vad innehållerhello.txt? Hur skiljer sig>från>>? - Kör
printf "\e[38;5;81mfoo\e[0m\n". Hur blev utdata annorlunda? Om du vill veta mer, sök på ANSI color escape sequences. - Kör
touch a.txtoch sedan^txt^log. Vad gjorde bash åt dig? Kör i samma andafc. Vad gör det?
- Kör
-
Kortkommandon
Precis som med alla program du använder ofta är det värt att lära sig kortkommandon. Skriv in följande och försök förstå vad de gör, och i vilka situationer de är praktiska. För vissa kan det vara enklast att söka online. (Kom ihåg att
^XbetyderCtrl+X.)^A,^E^R^L^C,^\och^D^Uoch^Y
Licensed under CC BY-NC-SA.