Kapitel 12. Programmering

Innehållsförteckning

12.1. Shell-skriptet
12.1.1. POSIX shell-kompatibilitet
12.1.2. Parametrar för skal
12.1.3. Villkor för skal
12.1.4. Öglor i skalet
12.1.5. Shell-miljövariabler
12.1.6. Bearbetningssekvensen för kommandoraden i Shell
12.1.7. Verktygsprogram för shell-skript
12.2. Skriptning i tolkade språk
12.2.1. Felsökning av tolkade språkkoder
12.2.2. GUI-program med shell-skript
12.2.3. Anpassade åtgärder för GUI-fil
12.2.4. Perl kortskript galenskap
12.3. Kodning i kompilerade språk
12.3.1. C
12.3.2. Enkelt C-program (gcc)
12.3.3. Flex - en bättre Lex
12.3.4. Bison - en bättre Yacc
12.4. Verktyg för statisk kodanalys
12.5. Debugg
12.5.1. Grundläggande gdb-körning
12.5.2. Felsökning av Debian-paketet
12.5.3. Hämta bakspårning
12.5.4. Avancerade gdb-kommandon
12.5.5. Kontrollera beroendet av bibliotek
12.5.6. Dynamiska verktyg för samtalsspårning
12.5.7. Felsökning av X-fel
12.5.8. Verktyg för detektering av minnesläckor
12.5.9. Demontera binär
12.6. Bygg verktyg
12.6.1. Märke
12.6.2. Autotools
12.6.2.1. Kompilera och installera ett program
12.6.2.2. Avinstallera program
12.6.3. Meson
12.7. Webb
12.8. Översättning av källkoden
12.9. Skapa Debian-paket

Jag ger några tips för att människor ska lära sig programmering på Debian-systemet tillräckligt för att spåra den paketerade källkoden. Här är anmärkningsvärda paket och motsvarande dokumentationspaket för programmering.

Online-referenser finns tillgängliga genom att skriva "man name" efter installation av paketen manpages och manpages-dev. Online-referenser för GNU-verktygen finns tillgängliga genom att skriva "info programnamn" efter att du har installerat de relevanta dokumentationspaketen. Du kan behöva inkludera arkiven contrib- och non-free utöver main eftersom vissa GFDL-dokumentationer inte anses vara DFSG-kompatibla.

Tänk på att använda verktyg för versionskontrollsystem. Se Avsnitt 10.5, ”Git”.

[Varning] Varning

Använd inte "test" som namn på en körbar testfil. "test" är en inbyggd funktion i skalet.

[Observera] Observera

Du bör installera program som är direkt kompilerade från källkod i "/usr/local" eller "/opt" för att undvika kollision med systemprogram.

[Tips] Tips

Kodexempel för att skapa "Song 99 Bottles of Beer" bör ge dig goda idéer om praktiskt taget alla programmeringsspråk.

Shellskriptet är en textfil med exekveringsbiten aktiverad och innehåller kommandon i följande format.

#!/bin/sh
 ... command lines

Den första raden anger vilken shell-tolk som ska läsa och exekvera filinnehållet.

Att läsa skalskript är det bästa sättet att förstå hur ett Unix-liknande system fungerar. Här ger jag några tips och påminnelser för programmering i skalet. Se "Shell Mistakes"(https://www.greenend.org.uk/rjk/2001/04/shell.html) för att lära dig av misstag.

Till skillnad från det interaktiva läget i shell (se Avsnitt 1.5, ”Det enkla shell-kommandot” och Avsnitt 1.6, ”Unix-liknande textbehandling”) används ofta parametrar, villkor och loopar i shell-skript.

Varje kommando returnerar en utgångsstatus som kan användas för villkorliga uttryck.

  • Framgång: 0 ("True")

  • Fel: non 0 ("False")

[Notera] Notera

"0" i skalvillkorskontexten betyder "sant", medan "0" i C-villkorskontexten betyder "falskt".

[Notera] Notera

"[" är motsvarigheten till kommandot test, som utvärderar sina argument upp till "]" som ett villkorligt uttryck.

Grundläggande villkorliga idiom att komma ihåg är följande.

  • "kommando && if_success_run_this_command_too || true"

  • "kommandot || if_not_success_run_this_command_too || true"

  • Ett skriptutdrag med flera rader enligt följande

if [ conditional_expression ]; then
 if_success_run_this_command
else
 if_not_success_run_this_command
fi

Här behövdes det efterföljande "|| true" för att säkerställa att detta skalskript inte avslutas på denna rad av misstag när skalet anropas med flaggan "-e".



Aritmetiska heltalsjämförelseoperatorer i det villkorliga uttrycket är "-eq", "-ne", "-lt", "-le", "-gt" och "-ge".

Shell bearbetar ett skript ungefär enligt följande sekvens.

  • Shell läser en rad.

  • Skalet grupperar en del av raden som en token om den ligger inom "..." eller '... '.

  • Shell delar upp andra delar av en rad i tokens på följande sätt.

    • Whitespaces: mellanslag flik nytt streck

    • Metatecken: < > | ; & ( )

  • Skalet kontrollerar det reserverade ordet för varje token för att anpassa sitt beteende om det inte finns inom "..." eller '... '.

    • reserverat ord: if then elif else fi for in while unless do done case esac

  • Skalet expanderar alias om det inte finns inom "..." eller '...'.

  • Skalet expanderar tilde om det inte finns inom "..." eller '... '.

    • "~" → den aktuella användarens hemkatalog

    • "~användare" → användarensanvändarens hemkatalog

  • Skalet expanderar parametern till dess värde om den inte finns inom '...'.

    • parameter: "$PARAMETER" eller "${PARAMETER}"

  • Skalet expanderar kommandosubstitutionen om den inte finns inom '...'.

    • "$( kommando )" → utdata från "kommando"

    • "`kommando `" → utdata från "kommando"

  • Skalet expanderar sökvägsnamnet glob till matchande filnamn om det inte finns inom "..." eller '...'.

    • * → alla tecken

    • ? → ett tecken

    • [...] → något av tecknen i "... "

  • Shell letar upp kommandot från följande och utför det.

    • definition av funktion

    • inbyggt kommando

    • körbar fil i "$PATH"

  • Skalet går till nästa rad och upprepar denna process igen från början av denna sekvens.

Enkla citattecken inom dubbla citattecken har ingen effekt.

Om du kör "set -x" i skalet eller anropar skalet med alternativet "-x" skriver skalet ut alla kommandon som har körts. Detta är ganska praktiskt för felsökning.


När du vill automatisera en uppgift på Debian bör du först skripta den med ett tolkat språk. Riktlinjerna för valet av tolkat språk är:

  • Använd dash om det handlar om en enkel uppgift som kombinerar CLI-program med ett skalprogram.

  • Använd python3, om uppgiften inte är enkel och du skriver den från grunden.

  • Använd perl, tcl, ruby, ... om det finns en befintlig kod som använder något av dessa språk på Debian som behöver förbättras för att utföra uppgiften.

Om den resulterande koden är för långsam kan du skriva om endast den del som är kritisk för exekveringshastigheten i ett kompilerat språk och anropa den från det tolkade språket.

Shell-skriptet kan förbättras för att skapa ett attraktivt GUI-program. Tricket är att använda något av de s.k. dialogprogrammen istället för tråkig interaktion med echo och read-kommandon.


Här är ett exempel på ett GUI-program som visar hur enkelt det är att bara använda ett skalskript.

Detta skript använder zenity för att välja en fil (standard /etc/motd) och visa den.

GUI-startprogrammet för detta skript kan skapas enligt Avsnitt 9.4.10, ”Starta ett program från GUI”.

#!/bin/sh -e
# Copyright (C) 2021 Osamu Aoki <osamu@debian.org>, Public Domain
# vim:set sw=2 sts=2 et:
DATA_FILE=$(zenity --file-selection --filename="/etc/motd" --title="Select a file to check") || \
  ( echo "E: File selection error" >&2 ; exit 1 )
# Check size of archive
if ( file -ib "$DATA_FILE" | grep -qe '^text/' ) ; then
  zenity --info --title="Check file: $DATA_FILE" --width 640  --height 400 \
    --text="$(head -n 20 "$DATA_FILE")"
else
  zenity --info --title="Check file: $DATA_FILE" --width 640  --height 400 \
    --text="The data is MIME=$(file -ib "$DATA_FILE")"
fi

Den här typen av tillvägagångssätt för GUI-program med shell-skript är endast användbart för enkla val. Om du ska skriva något program med komplexitet bör du överväga att skriva det på en mer kapabel plattform.


Här ingår Avsnitt 12.3.3, ”Flex - en bättre Lex” och Avsnitt 12.3.4, ”Bison - en bättre Yacc” för att visa hur ett kompilatorliknande program kan skrivas i C-språk genom att kompilera en beskrivning på högre nivå till C-språk.

Du kan skapa en lämplig miljö för att kompilera program som är skrivna i programmeringsspråket C genom att göra följande.

# apt-get install glibc-doc manpages-dev libc6-dev gcc build-essential

Paketet libc6-dev, dvs GNU C Library, tillhandahåller C-standardbiblioteket som är en samling av rubrikfiler och biblioteksrutiner som används av programmeringsspråket C.

Se referenser för C som följande.

  • "info libc" (referens till C-bibliotekets funktioner)

  • gcc(1) och "info gcc"

  • varje_C_biblioteks_funktionsnamn(3)

  • Kernighan & Ritchie, "The C Programming Language", 2:a upplagan (Prentice Hall)

Flex är en Lex-kompatibel snabb generator för lexikal analys.

Handledning för flex(1) kan hittas i "info flex".

Många enkla exempel finns under "/usr/share/doc/flex/examples/". [7]

Flera paket tillhandahåller en Yacc-kompatibel lookahead LR-parser eller LALR-parsergenerator i Debian.


Handledning för bison(1) finns i "info bison".

Du måste tillhandahålla dina egna "main()" och "yyerror()". "main()"anropar "yyparse()" som anropar "yylex()", vanligtvis skapat med Flex.

Här följer ett exempel på hur du skapar ett enkelt kalkylatorprogram för terminalen.

Låt oss skapa exempel.y:

/* calculator source for bison */
%{
#include <stdio.h>
extern int yylex(void);
extern int yyerror(char *);
%}

/* declare tokens */
%token NUMBER
%token OP_ADD OP_SUB OP_MUL OP_RGT OP_LFT OP_EQU

%%
calc:
 | calc exp OP_EQU    { printf("Y: RESULT = %d\n", $2); }
 ;

exp: factor
 | exp OP_ADD factor  { $$ = $1 + $3; }
 | exp OP_SUB factor  { $$ = $1 - $3; }
 ;

factor: term
 | factor OP_MUL term { $$ = $1 * $3; }
 ;

term: NUMBER
 | OP_LFT exp OP_RGT  { $$ = $2; }
  ;
%%

int main(int argc, char **argv)
{
  yyparse();
}

int yyerror(char *s)
{
  fprintf(stderr, "error: '%s'\n", s);
}

Låt oss skapa, exempel.l:

/* calculator source for flex */
%{
#include "example.tab.h"
%}

%%
[0-9]+ { printf("L: NUMBER = %s\n", yytext); yylval = atoi(yytext); return NUMBER; }
"+"    { printf("L: OP_ADD\n"); return OP_ADD; }
"-"    { printf("L: OP_SUB\n"); return OP_SUB; }
"*"    { printf("L: OP_MUL\n"); return OP_MUL; }
"("    { printf("L: OP_LFT\n"); return OP_LFT; }
")"    { printf("L: OP_RGT\n"); return OP_RGT; }
"="    { printf("L: OP_EQU\n"); return OP_EQU; }
"exit" { printf("L: exit\n");   return YYEOF; } /* YYEOF = 0 */
.      { /* ignore all other */ }
%%

Kör sedan följande från shellprompten för att prova detta:

$ bison -d example.y
$ flex example.l
$ gcc -lfl example.tab.c lex.yy.c -o example
$ ./example
1 + 2 * ( 3 + 1 ) =
L: NUMBER = 1
L: OP_ADD
L: NUMBER = 2
L: OP_MUL
L: OP_LFT
L: NUMBER = 3
L: OP_ADD
L: NUMBER = 1
L: OP_RGT
L: OP_EQU
Y: RESULT = 9

exit
L: exit

Lint-liknande verktyg kan hjälpa till med automatisk statisk kodanalys.

Indragningsliknande verktyg kan underlätta mänskliga kodgranskningar genom att omformatera källkoder på ett konsekvent sätt.

Taggar som verktyg kan hjälpa mänskliga kodgranskare genom att generera en indexfil (eller taggfil) med namn som finns i källkoder.

[Tips] Tips

Om du konfigurerar din favoritredigerare (emacs eller vim) så att den använder asynkrona insticksmoduler för lint-motorer blir det lättare att skriva kod. Dessa insticksmoduler blir mycket kraftfulla genom att dra nytta av Language Server Protocol. Eftersom de utvecklas snabbt kan det vara ett bra alternativ att använda deras uppströmskod istället för Debian-paketet.

Tabell 12.12. Lista över verktyg för statisk kodanalys

paket popcon storlek beskrivning
vim-ale I:0 2833 Asynkron Lint-motor för Vim 8 och NeoVim
vim-syntastic I:2 1379 Syntaxkontroll-hacks för vim
elpa-flycheck V:0, I:1 826 modern on-the-fly syntaxkontroll för Emacs
elpa-relint V:0, I:0 150 Emacs Lisp regexp misstagssökare
cppcheck-gui V:0, I:1 7682 verktyg för statisk analys av C/C++-kod (GUI)
shellcheck V:2, I:15 35220 lint-verktyg för shell-skript
pyflakes3 V:2, I:15 20 passiv kontroll av Python 3-program
pylint V:4, I:20 2089 Statisk kontroll av Python-kod
perl V:673, I:990 841 tolk med intern statisk kodkontroll: B::Lint(3perl)
rubocop V:0, I:1 3247 Analysator för statisk kod i Ruby
clang-tidy V:1, I:12 22 clang-baserat C++ linter-verktyg
splint V:0, I:1 2328 verktyg för att statiskt kontrollera C-program för buggar
flawfinder V:0, I:0 205 verktyg för att undersöka C/C++-källkod och leta efter säkerhetsbrister
black V:4, I:16 10138 kompromisslös Python-kodformaterare
perltidy V:0, I:3 3086 Indenterare och omformaterare av Perl-skript
indent V:0, I:5 438 C-språk källkod formateringsprogram
astyle V:0, I:2 769 Källkodsindenter för C, C++, Objective-C, C# och Java
bcpp V:0, I:0 114 C(++) förskönare
xmlindent V:0, I:0 52 Omformatering av XML-ström
global V:0, I:1 1923 Verktyg för att söka och bläddra i källkod
exuberant-ctags V:1, I:14 341 skapa taggfilsindex för källkodsdefinitioner
universal-ctags V:1, I:12 4238 skapa taggfilsindex för källkodsdefinitioner

Felsökning är en viktig del av programmeringsaktiviteter. Att veta hur man felsöker program gör dig till en bra Debian-användare som kan producera meningsfulla felrapporter.


Den primära felsökaren på Debian är gdb(1) som gör att du kan inspektera ett program medan det körs.

Låt oss installera gdb och relaterade program enligt följande.

# apt-get install gdb gdb-doc build-essential devscripts

Bra handledning av gdb kan hittas:

Här följer ett enkelt exempel på hur du använder gdb(1) på ett "program" som kompilerats med alternativet "-g" för att ta fram felsökningsinformation.

$ gdb program
(gdb) b 1                # set break point at line 1
(gdb) run args           # run program with args
(gdb) next               # next line
...
(gdb) step               # step forward
...
(gdb) p parm             # print parm
...
(gdb) p parm=12          # set value to 12
...
(gdb) quit
[Tips] Tips

Många gdb(1)-kommandon kan förkortas. Tabbexpansion fungerar som i skalet.

Eftersom alla installerade binärfiler som standard bör vara strippade på Debian-systemet, tas de flesta felsökningssymbolerna bort i det normala paketet. För att kunna felsöka Debian-paket med gdb(1) måste *-dbgsym-paket installeras (t.ex. coreutils-dbgsym när det gäller coreutils). Källpaketen genererar *-dbgsym-paket automatiskt tillsammans med normala binära paket och dessa felsökningspaket placeras separat i debian-debug-arkivet. Se artiklar på Debian Wiki för mer information.

Om ett paket som ska felsökas inte tillhandahåller sitt *-dbgsym-paket måste du installera det efter att ha byggt upp det på följande sätt.

$ mkdir /path/new ; cd /path/new
$ sudo apt-get update
$ sudo apt-get dist-upgrade
$ sudo apt-get install fakeroot devscripts build-essential
$ apt-get source package_name
$ cd package_name*
$ sudo apt-get build-dep ./

Åtgärda buggar om det behövs.

Förbättra paketversionen till en som inte kolliderar med officiella Debian-versioner, t.ex. en som har tillägget "+debug1" vid omkompilering av befintlig paketversion, eller en som har tillägget "~pre1" vid kompilering av outgiven paketversion genom följande.

$ dch -i

Kompilera och installera paket med debug-symboler med följande.

$ export DEB_BUILD_OPTIONS="nostrip noopt"
$ debuild
$ cd ..
$ sudo debi package_name*.changes

Du måste kontrollera paketets byggskript och se till att du använder "CFLAGS=-g -Wall" för att kompilera binärfiler.

När du stöter på programkrasch är det en bra idé att rapportera felrapport med klippt och klistrat backtrace-information.

Bakspårningen kan hämtas av gdb(1) med hjälp av något av följande tillvägagångssätt:

Vid oändlig loop eller fruset tangentbord kan du tvinga programmet att krascha genom att trycka på Ctrl\ eller Ctrl-C eller köra "kill -ABRT PID". (Se Avsnitt 9.4.12, ”Döda en process”)

[Tips] Tips

Ofta ser man en backtrace där en eller flera av de översta raderna är i "malloc()" eller "g_malloc()". När detta händer är chansen stor att din backtrace inte är särskilt användbar. Det enklaste sättet att hitta användbar information är att ställa in miljövariabeln "$MALLOC_CHECK_" till ett värde av 2 (malloc(3)). Du kan göra detta medan du kör gdb genom att göra följande.

 $ MALLOC_CHECK_=2 gdb hello

Make är ett verktyg för att underhålla grupper av program. Vid exekvering av make(1) läser make regelfilen "Makefile" och uppdaterar ett mål om det är beroende av förutsättningsfiler som har ändrats sedan målet senast ändrades, eller om målet inte existerar. Exekveringen av dessa uppdateringar kan ske samtidigt.

Syntaxen för regelfilen är följande.

target: [ prerequisites ... ]
 [TAB]  command1
 [TAB]  -command2 # ignore errors
 [TAB]  @command3 # suppress echoing

Här är "[TAB]" en TAB-kod. Varje rad tolkas av skalet efter att variabeln har bytts ut. Använd "\" i slutet av en rad för att fortsätta skriptet. Använd "$$" för att ange "$" för miljövärden i ett skalskript.

Implicita regler för mål och förutsättningar kan skrivas till exempel enligt följande.

%.o: %.c header.h

Här innehåller målet tecknet "%" (exakt ett av dem). "%" kan matcha alla icke-tomma delsträngar i de faktiska målfilnamnen. Förutsättningarna använder också "%" för att visa hur deras namn förhåller sig till det faktiska målnamnet.



Kör "make -p -f/dev/null" för att se automatiska interna regler.

Autotools är en uppsättning programmeringsverktyg som är utformade för att hjälpa till att göra källkodspaket portabla till många Unix-liknande system.

  • Autoconf är ett verktyg för att producera ett skalskript "configure" från "configure.ac".

    • "configure" används senare för att producera "Makefile" från "Makefile.in"-mallen.

  • Automake är ett verktyg för att producera "Makefile.in" från "Makefile.am".

  • Libtool är ett skalskript för att lösa problemet med programvaruportabilitet när delade bibliotek kompileras från källkod.

Systemet för programvaruutveckling har utvecklats:

  • Autotools på toppen av Make har varit de facto-standarden för den portabla bygginfrastrukturen sedan 1990-talet. Detta är extremt långsamt.

  • CMake, som ursprungligen släpptes 2000, förbättrade hastigheten avsevärt men byggdes ursprungligen på toppen av den i sig långsamma Make. (Nu kan Ninja vara dess backend.)

  • Ninja, som ursprungligen släpptes 2012, är tänkt att ersätta Make för den ytterligare förbättrade bygghastigheten och är utformad för att få sina indatafiler genererade av ett byggsystem på högre nivå.

  • Meson, som ursprungligen släpptes 2013, är det nya populära och snabba byggsystemet på högre nivå som använder Ninja som backend.

Se dokument som finns på"The Meson Build system" och "The Ninja build system".

Enkla interaktiva dynamiska webbsidor kan skapas på följande sätt.

  • Frågorna presenteras för webbläsaren med hjälp av HTML-formulär.

  • Genom att fylla i och klicka på formulärposterna skickas en av följande URL-strängar med kodade parametrar från webbläsaren till webbservern.

    • "https://www.foo.dom/cgi-bin/program.pl?VAR1=VAL1&VAR2=VAL2&VAR3=VAL3"

    • "https://www.foo.dom/cgi-bin/program.py?VAR1=VAL1&VAR2=VAL2&VAR3=VAL3"

    • "https://www.foo.dom/program.php?VAR1=VAL1&VAR2=VAL2&VAR3=VAL3"

  • "%nn" i URL ersätts med ett tecken med hexadecimalt nn-värde.

  • Miljövariabeln är inställd som: "QUERY_STRING="VAR1=VAL1 VAR2=VAL2 VAR3=VAL3"".

  • CGI-program (vilket som helst av "program.*") på webbservern kör sig själv med miljövariabeln "$QUERY_STRING".

  • stdout från CGI-programmet skickas till webbläsaren och presenteras som en interaktiv dynamisk webbsida.

Av säkerhetsskäl är det bättre att inte själv skapa nya hack för att tolka CGI-parametrar. Det finns etablerade moduler för dem i Perl och Python. PHP kommer med dessa funktioner. När lagring av klientdata behövs används HTTP-cookies. När databehandling på klientsidan behövs används ofta Javascript.

För mer information, se Common Gateway Interface, Apache Software Foundation och JavaScript.

Att söka på "CGI tutorial" på Google genom att skriva in den kodade URL:en https://www.google.com/search?hl=en&ie=UTF-8&q=CGI+tutorial direkt i webbläsarens adress är ett bra sätt att se CGI-skriptet i aktion på Googles server.

Om du vill skapa ett Debian-paket ska du läsa följande.

Det finns paket som debmake, dh-make, dh-make-perl etc. som hjälper till med paketering.



[7] Vissa justeringar kan krävas för att få dem att fungera under det nuvarande systemet.