Terug naar hoofdinhoud
Linux concept: POSIX
Op deze pagina
# Topics

Linux concept: POSIX

06 oktober 2026

Een script werkt op je laptop. Je kopieert het naar de server, het draait om drie uur 's nachts vanuit cron, en het crasht op een regel die niet is veranderd. Niemand heeft eraan gezeten, er is geen pakket bijgewerkt, en hetzelfde bestand werkt nog steeds als je het met de hand start. De oorzaak is meestal geen fout in je logica. Het is dat de twee machines het oneens zijn over wat een Unix-systeem is, en het document dat bepaalt wie gelijk heeft heet POSIX.

Dit artikel legt uit wat POSIX echt is, waar de naam vandaan komt, hoe veertig jaar Unix-ruzie het heeft opgeleverd, en hoe je het gebruikt: je eigen systeem vragen welke versie het implementeert, de standaard gratis lezen, shell-scripts schrijven die een verhuizing naar een andere machine overleven, en de feature test macro's die bepalen welke functies je C-compiler wil erkennen. Onderweg scheidt het de C-bibliotheek van de kernel, en portabiliteit van broncode van compatibiliteit van binaries. Het behandelt ook wat POSIX niet is, want daar zit de meeste verwarring.

Schrijf je alleen shell-scripts, dan kun je sectie 6 overslaan: die is voor wie C schrijft of compileert, en de rest van het artikel bouwt er niet op voort.

Van "waarom brak mijn script op de server" naar "wie van deze twee systemen heeft het mis".

Het doel: na het lezen kun je bij elk portabiliteitsprobleem zeggen of de standaard er een uitspraak over doet.

1. De basis

POSIX is geen programma, geen bibliotheek en geen onderdeel van Linux. Het is een geschreven document: een standaard die zegt wat een Unix-achtig besturingssysteem moet bieden aan de programma's die erop draaien. Het beschrijft systeemaanroepen, functies uit de C-bibliotheek, een shell-taal en een verzameling opdrachtregelprogramma's, en het beschrijft ze zo nauwkeurig dat software die tegen die beschrijving is geschreven werkt op elk systeem dat de beschrijving volgt.

Je kunt POSIX niet installeren. Je kunt het alleen implementeren, en Linux, de BSD's, macOS, AIX, Solaris en andere doen dat allemaal in verschillende mate. De standaard is de gedeelde woordenschat die de zin "een Unix-systeem" iets concreets laat betekenen in plaats van iets historisch.

Het juiste mentale model: POSIX is het contract tussen jouw code en het systeem eronder. Jouw programma houdt zich aan zijn kant door alleen te gebruiken wat in het contract staat. Het systeem houdt zich aan zijn kant door dat allemaal te leveren, precies zoals beschreven. Portabiliteit is wat er gebeurt als beide kanten zich eraan houden.

1.1 Wat er in de standaard staat

De huidige standaard verschijnt in vier delen. Weten welk deel welk soort vraag beantwoordt, scheelt veel zoeken:

DeelKorte naamWat het vastlegt
Base DefinitionsXBDTermen, begrippen, headerbestanden, de syntaxis van reguliere expressies, het locale-model
System InterfacesXSHDe C-functies en systeemaanroepen: open(), fork(), pthread_create() en ongeveer duizend andere
Shell and UtilitiesXCUDe shell-taal en de opdrachtregelprogramma's: sh, awk, sed, grep, find en de rest
RationaleXRATWaarom de commissie besloot wat ze besloot; toelichtend, niet bindend

Die verdeling helpt in de praktijk. Als je vraagt "is sed -i portabel?" stel je een vraag over XCU. Als je vraagt "waarom zegt de compiler dat strdup niet bestaat?" gaat het over XBD en XSH. De twee helften van de standaard zijn door dezelfde groep geschreven maar lossen verschillende problemen op, en dit artikel volgt diezelfde verdeling: sectie 5 is de shell-helft, sectie 6 is de C-helft.

1.2 Vraag het je eigen systeem

Je machine vertelt je zelf welke versie van de standaard hij zegt te implementeren. Het programma getconf (kort voor get configuration) leest de waarden die het systeem tijdens het draaien rapporteert:

$ getconf _POSIX_VERSION
200809

Dat getal is een datum, geen versienummer: jaar 2008, maand 09. Dit systeem implementeert POSIX.1-2008, de uitgave van september 2008. Dezelfde codering kom je overal in POSIX tegen, dus 199506 betekent de uitgave van juni 1995 en 200112 die van december 2001.

Een tweede getal vertelt hoeveel van het optionele X/Open-materiaal aanwezig is:

$ getconf _XOPEN_VERSION
700

700 betekent Issue 7 van de Single UNIX Specification, dezelfde tekst als POSIX.1-2008 met een paar extra delen erbij. Sectie 3 legt uit waarom één standaard twee namen en twee nummeringen heeft.

1.3 Standaard, implementatie en certificering

Drie begrippen lopen voortdurend door elkaar, en ze uit elkaar halen haalt de lucht uit de meeste discussies over wie "POSIX compliant" is:

BegripWat het betekentVoorbeeld
De standaardHet document zelf, uitgegeven door IEEE en The Open GroupIEEE Std 1003.1-2024
Een implementatieSoftware die levert wat het document beschrijftLinux met glibc, coreutils en een shell
CertificeringBetalen om een testsuite te doorstaan en het handelsmerk UNIX te licentiërenmacOS, AIX, HP-UX, Solaris

Linux implementeert een heel groot deel van POSIX en is door niemand gecertificeerd, omdat certificering een commerciële licentiekwestie is en geen technische. Daarom heet Linux "Unix-achtig" en niet "UNIX": het woord UNIX is een handelsmerk, en dat handelsmerk is wat certificering koopt. Sectie 7.3 heeft de vreemde uitzondering op deze regel.

In het dagelijks taalgebruik betekent "POSIX compliant" bijna altijd "dicht genoeg bij de standaard dat gewone programma's werken", wat een nuttige uitspraak is en geen bewering die iemand heeft gecontroleerd. Als het precies moet zijn, noem dan de versie: "POSIX.1-2008" zegt iets, "POSIX compliant" niet.

1.4 De woorden die de standaard gebruikt

Een standaard kan niet alleen zeggen "dit werkt". Hij moet zeggen hoeveel je erop mag rekenen, en POSIX doet dat met vier termen die op synoniemen lijken en het niet zijn. Ze vormen het verschil tussen een portabel script en een script dat toevallig draait:

TermWat het voor jou betekentVoorbeeld in dit artikel
Defined (vastgelegd)Elk systeem dat de standaard volgt doet hetzelfde. Reken erop.[ "$a" = "$b" ] vergelijkt twee teksten
Implementation-defined (door de implementatie bepaald)Het systeem kiest zelf en moet die keuze documenteren. Reken er pas op nadat je die documentatie hebt gelezen, en alleen op dat systeem.echo met een -n vooraan of met een backslash (sectie 5.3)
Unspecified (niet gespecificeerd)Het systeem kiest zelf en hoeft het niemand te vertellen. Twee systemen mogen verschillen en hebben allebei gelijk.local in een shell-script (sectie 5.2)
Undefined (ongedefinieerd)De standaard stelt helemaal geen eis. Alles mag gebeuren, inclusief perfect werken tot het moment dat het stopt.een C-pointer gebruiken nadat het geheugen is vrijgegeven

In de middelste twee zitten de portabiliteitsfouten. Niets waarschuwt je, beide machines gedragen zich correct, en code die op een niet-gespecificeerd detail leunt ziet er precies zo uit als code die op een garantie leunt. Gebruikt de standaard een van deze woorden over iets waar jouw script van afhangt, dan is die afhankelijkheid van jou om weg te halen, want geen enkel systeem is verplicht hem te blijven ondersteunen.

Naar boven

2. Waar de naam vandaan komt

POSIX staat voor Portable Operating System Interface. Lees de woorden op volgorde en ze beschrijven het hele project: een interface naar een besturingssysteem, zo vastgelegd dat software overdraagbaar is tussen systemen die hem bieden.

De laatste letter komt niet uit die woorden. Er zit geen X in "Portable Operating System Interface", en de standaard eindigt er toch op door een naamgewoonte: de Unix-systemen van die tijd heetten AIX, IRIX, HP-UX, Ultrix, Xenix, en een X aan het eind las als "dit hoort bij de Unix-familie". De naam is met opzet uitspreekbaar, en hij bleef meteen hangen.

Wie hem heeft voorgesteld staat vastgelegd in de Linux-handleidingen. Draai man 7 standards en lees het stuk over de eerste uitgave:

$ man 7 standards
POSIX.1-1988
       This was the first POSIX standard, ratified by IEEE as IEEE Std
       1003.1-1988, and subsequently adopted (with minor revisions) as
       an ISO standard in 1990.  The term "POSIX" was coined by Richard
       Stallman.

De IEEE-werkgroep had een naam nodig voor een document dat tot dan toe alleen bij zijn commissienummer bekend was. Dat nummer is nooit verdwenen, en daarom kom je dezelfde standaard onder meerdere namen tegelijk tegen:

NaamWie gebruikt hemBetekenis
IEEE 1003.1IEEEHet commissie- en documentnummer; .1 is het deel over de systeeminterface
POSIX.1-2008Iedereen, informeelHet document 1003.1, uitgave 2008
ISO/IEC 9945ISODezelfde tekst, overgenomen als internationale standaard
Base Specifications Issue 7The Open GroupWeer dezelfde tekst, in hun nummering

Een letter achter het nummer betekent een aanvulling en geen nieuwe uitgave. POSIX.1b voegde in 1993 realtime-voorzieningen toe en POSIX.1c in 1995 threads, en allebei zijn ze later in het hoofddocument opgenomen. Zie je 1003.1c in een handleiding staan, dan vertelt die je dat een functie met de threads-aanvulling is gekomen.

Het beeld dat de naam beschrijft is één lijn dwars door een draaiend systeem:

    your program        your shell script
    ------------------------------------------   <-- POSIX describes this line
    system calls    C library    sh    utilities
    ------------------------------------------
    the kernel and the hardware underneath

Alles boven de lijn is van jou. Alles eronder is de zaak van de implementatie, en de standaard zegt niets over hoe dat gebeurt. POSIX heeft het nergens over inodes op schijf, over planningsalgoritmes, of over hoe een bestandssysteem is ingedeeld. Het legt alleen vast hoe de laag er van bovenaf uitziet, en dat is precies wat een programma moet weten en niets meer.

Naar boven

3. Een korte geschiedenis

POSIX bestaat omdat Unix uiteenviel. AT&T's System V en Berkeley's BSD groeiden in de vroege jaren tachtig uit elkaar, elke leverancier had zijn eigen variant, en software die op de ene machine draaide had aanpassingen nodig voor de volgende. Het probleem was eerder commercieel dan technisch: kopers wilden software tussen leveranciers kunnen verplaatsen, en leveranciers wilden verkopen aan kopers die daarop stonden.

De eerste poging kwam van gebruikers, niet van leveranciers. De vereniging /usr/group publiceerde in 1984 een standaard met wat haar leden van een Unix-systeem verwachtten, en de IEEE nam dat werk als startpunt voor een formele standaard. In 1988 bekrachtigde de IEEE IEEE Std 1003.1-1988, en had Unix voor het eerst een geschreven definitie.

Die eerste uitgave ging alleen over de C-interface. Opdrachten en programma's kwamen vier jaar later in POSIX.2, waarin de shell-taal, awk, sed en de rest van de standaardgereedschapskist werden vastgelegd. Daarna volgden aanvullingen voor realtime-werk en voor threads.

Ondertussen liep er een tweede standaardisatietraject. Het X/Open-consortium publiceerde zijn eigen Portability Guides, en de herziening uit 1994 kreeg de bijnaam Spec 1170, naar het aantal interfaces dat erin stond. X/Open bezat het handelsmerk UNIX, dus hun documenten bepaalden wie de naam mocht gebruiken, en systemen die voldeden aan hun Single UNIX Specification mochten zich UNIX 95 noemen, en later UNIX 98.

Twee standaarden voor één ding is er één te veel. In 1998 vormden de IEEE, The Open Group en ISO samen de Austin Group, een gezamenlijke commissie genoemd naar de stad waar ze voor het eerst bijeenkwam, met één uitgangspunt: schrijf de tekst één keer en laat alle drie de organisaties hem uitgeven. Het resultaat verscheen in 2001 tegelijk als POSIX.1-2001, als Single UNIX Specification versie 3 en als ISO/IEC 9945. Door die samenvoeging heeft één document vier namen.

JaarMijlpaal
1969-1979Unix bij Bell Labs. Version 7 uit 1979 is de laatste uitgave voordat BSD en System V uit elkaar groeien.
1984De /usr/group-standaard: gebruikers schrijven op wat ze van een Unix-systeem verwachten.
1988IEEE Std 1003.1-1988, de eerste POSIX. Alleen systeeminterfaces.
1990Overgenomen door ISO als ISO/IEC 9945-1:1990.
1992POSIX.2: de shell-taal en de opdrachtregelprogramma's.
1993, 1995Aanvullingen 1003.1b (realtime) en 1003.1c (threads).
1994X/Open Spec 1170, dat de Single UNIX Specification en het merk UNIX 95 wordt.
1998De Austin Group ontstaat om de splitsing tussen POSIX en de SUS te beëindigen.
2001POSIX.1-2001 = SUSv3 = UNIX 03. Eén document, vier namen, vier delen.
2008POSIX.1-2008 = SUSv4. De uitgave die de meeste systemen vandaag nog rapporteren.
2013, 2016Technical Corrigenda 1 en 2: correcties, geen nieuwe functionaliteit.
2017POSIX.1-2017: de tekst van 2008 met beide correctiebladen verwerkt. Technisch identiek.
2024POSIX.1-2024, Base Specifications Issue 8. De eerste echte herziening in zestien jaar.

3.1 Wat de herziening van 2024 veranderde

IEEE en The Open Group publiceerden POSIX.1-2024 op 14 juni 2024. Na zestien jaar correctiebladen is het een flinke herziening, en bijna alles erin is de standaard die inhaalt wat implementaties allang deden.

Aan de C-kant komen er ongeveer honderd functies bij, de meeste uit de C17-taalstandaard, plus allang bestaande uitbreidingen die elk systeem los van elkaar had gekregen: strlcpy() en strlcat() uit OpenBSD, memmem(), reallocarray(), getentropy() en qsort_r(). Het compilerprogramma c99 is verdwenen en vervangen door c17.

Aan de shell-kant komen er programma's bij die al tientallen jaren op elk Linux-systeem staan: readlink, realpath, timeout en de gettext-familie voor vertaalde meldingen. Ook worden twee dingen gestandaardiseerd waarvan scriptschrijvers jarenlang te horen kregen dat ze niet portabel waren: set -o pipefail en sed -E voor uitgebreide reguliere expressies.

Bij dat nieuws horen twee waarschuwingen. Ten eerste is jouw documentatie waarschijnlijk ouder dan de standaard: de handleiding standards(7) die met Linux man-pages 6.7 meekomt, stopt bij de uitgave van 2018. Ten tweede, en belangrijker voor het dagelijks werk: een standaard is een belofte over de toekomst, geen beschrijving van de machine die voor je staat. Sectie 7.4 laat een shell zien die pipefail twee jaar na de standaardisatie nog steeds weigert.

Naar boven

4. Eenvoudige toepassingen: POSIX op je prompt

POSIX klinkt als iets waar alleen normcommissies aan zitten. In werkelijkheid staan er op je systeem meerdere programma's waarvan het enige doel is er vragen over te beantwoorden, en het loont om ze te kennen voordat je ze nodig hebt.

4.1 Het systeem vragen wat het biedt

getconf zonder argumenten geeft één waarde; met -a (kort voor all) dumpt het alles wat het systeem weet. Dat filteren is de snelste manier om te zien welke optionele delen van de standaard aanwezig zijn:

$ getconf -a | grep -E '_POSIX_(VERSION|THREADS|TIMERS|SPAWN|SHELL)'
_POSIX_THREADS                     200809
_POSIX_TIMERS                      200809
_POSIX_VERSION                     200809
_POSIX_SHELL                       1
_POSIX_SPAWN                       200809

Lees die waarden als antwoorden op ja-nee-vragen. Een datum betekent "deze optionele functionaliteit is aanwezig, op het niveau van deze uitgave van de standaard". Een 1 betekent "aanwezig", een lege waarde betekent "hier niet ondersteund". Threads, timers en posix_spawn() zijn in de tekst van de standaard allemaal optionele onderdelen, ook al heeft elk systeem voor algemeen gebruik ze al twintig jaar.

Hetzelfde programma rapporteert de limieten van het systeem, en die zijn nuttiger dan ze lijken:

$ getconf ARG_MAX          # bytes available for a command line
2097152
$ getconf LINE_MAX         # longest input line a text utility must handle
2048
$ getconf NAME_MAX /       # longest single filename on this filesystem
255
$ getconf PATH_MAX /       # longest full path
4096

ARG_MAX is het getal achter de foutmelding "Argument list too long", en het is de reden dat find ... -exec cmd {} + en xargs bestaan. NAME_MAX en PATH_MAX krijgen een pad mee omdat het antwoord afhangt van het bestandssysteem dat daar gekoppeld is, en niet van het systeem als geheel.

4.2 Het standaard-PATH

POSIX legt een waarde voor PATH vast waarmee je gegarandeerd de standaardprogramma's vindt, en getconf geeft hem je zo:

$ getconf PATH
/bin:/usr/bin

Dit is de waarde die je gebruikt als een script niet mag afhangen van het PATH dat het toevallig heeft geërfd. De shell heeft daar een bijpassend hulpmiddel voor: command -p (kort voor path) zoekt de opdracht op in precies dat standaard-PATH en negeert dat van jou:

$ command -p -v ls
/bin/ls

Dat telt in een cronjob, in een systemd-unit, of in alles wat onder sudo draait, want daar is het PATH niet hetzelfde als waarmee jij hebt getest. Werkt een script op je prompt en faalt het vanuit cron met "command not found", dan is het PATH het eerste om te controleren, en command -p is de portabele manier om er niet meer om te hoeven geven.

4.3 De standaard zelf lezen

De standaard is geen geheim document achter een betaalmuur. The Open Group publiceert de volledige tekst gratis als HTML op pubs.opengroup.org, en dat is de bron om aan te halen in elke discussie over portabiliteit. Hij is bovendien leesbaar: elke pagina over een programma heeft dezelfde indeling, met SYNOPSIS, OPTIONS, een EXAMPLES-sectie en een RATIONALE waarin staat waarom de commissie koos wat ze koos.

Je kunt hem ook als handleidingen lezen. De POSIX-tekst komt als eigen handleidingsecties, waarbij 1p de programma's zijn en 3p de functies:

$ man 1p ls
No manual entry for ls in section 1p

Die pagina's zitten in een apart pakket, omdat de licentie van de standaard niet vrij genoeg is voor de hoofdarchieven. Op Debian en Ubuntu staat het in multiverse:

$ sudo apt install manpages-posix manpages-posix-dev
$ man 1p ls            # what the standard says about ls
$ man 3p open          # what the standard says about open()

Het verschil tussen man 1 ls en man 1p ls is precies waar dit artikel over gaat. De eerste vertelt je wat jouw ls doet. De tweede vertelt je wat elke ls moet doen. Als ze het oneens zijn, is het extra gedrag een lokale uitbreiding, en die gebruiken is een keuze in plaats van een ongelukje.

Nog twee pagina's zijn het bewaren waard, en allebei staan ze al op je systeem:

$ man 7 standards        # every standard a STANDARDS section can name
$ man 7 posixoptions     # the optional units and how to test for them

4.4 Controleren of een bestandsnaam portabel is

POSIX legt een portable filename character set vast: letters, cijfers, punt, liggend streepje en koppelteken. Het programma pathchk toetst namen daaraan, met -p voor portable:

$ pathchk -p 'My File.txt'
pathchk: non-portable character ' ' in file name 'My File.txt'
$ pathchk -p 'a+b'
pathchk: non-portable character '+' in file name 'a+b'
$ pathchk -p 'report_1.txt'
$ echo $?
0

Stilte betekent dat de naam veilig is. Dit gaat niet over wat Linux accepteert, want dat is zo ongeveer elke byte behalve / en NUL. Het gaat over wat een reis overleeft door een ander systeem, een archiefformaat, of een script dat vergat een variabele te quoten.

-p toetst ook de lengte aan de standaard, en die is strenger dan elk systeem dat je deze eeuw hebt gebruikt:

$ pathchk -p 'backup-2026.tar.gz'
pathchk: limit 14 exceeded by length 18 of file name component
         'backup-2026.tar.gz'

Veertien tekens is de ondergrens die POSIX garandeert voor één stuk van een bestandsnaam, een getal dat is overgenomen uit de vroege Unix-bestandssystemen. Jouw bestandssysteem staat er 255 toe, en daarom loopt niemand hier tegenaan. Het is een mooie illustratie van wat -p betekent: niet "werkt dit hier", maar "werkt dit op het kleinste systeem dat de standaard nog toestaat".

Naar boven

5. Gematigde toepassingen: shell-scripts die reizen

Het meeste POSIX dat je in de praktijk tegenkomt zit in de shell, want daar is de belofte het makkelijkst te doen en het makkelijkst te breken. Deze sectie gaat over hem nakomen.

5.1 Wat #!/bin/sh echt belooft

De eerste regel van een script is een uitspraak over de taal waarin de rest van het bestand is geschreven. #!/bin/bash zegt "dit is Bash". #!/bin/sh zegt iets heel anders: "dit is de standaard shell-taal, en elke POSIX-shell mag het draaien".

Dat is een belofte over jouw code, niet over het systeem. En het systeem houdt je aan je woord, want /bin/sh is niet overal hetzelfde programma:

$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Mar 31  2024 /bin/sh -> dash

Op Debian en Ubuntu is /bin/sh gelijk aan dash, een kleine strenge shell die de standaard implementeert en vrijwel niets daarbuiten. Op Red Hat en Fedora is het Bash gestart onder de naam sh, wat een gedeeltelijke compatibiliteitsmodus aanzet. Op Alpine is het BusyBox ash. Schrijf #!/bin/sh en gebruik een Bash-functie, en het script werkt op een van die drie en faalt op de andere.

De shebang is geen suggestie. Heeft een script Bash nodig, zet er dan #!/bin/bash boven en maak je geen zorgen meer over portabiliteit. Staat er #!/bin/sh, dan heeft het beloofd binnen de standaard te blijven, en vroeg of laat houdt iets het aan die belofte.

5.2 Wat de POSIX-shell niet heeft

Dit zijn de constructies die het vaakst breken, gecontroleerd tegen dash op Ubuntu 24.04. Elk ervan is prima in Bash en prima in een script dat dat ook zegt; elk ervan is een fout in een bestand dat met #!/bin/sh begint:

Alleen BashWat dash doetPortabele vorm
[[ "$a" = "$b" ]][[: not found[ "$a" = "$b" ]
[ "$a" == "$b" ][: x: unexpected operator[ "$a" = "$b" ], één isgelijkteken
arr=(one two)Syntax error: "(" unexpectedpositionele parameters: set -- one two
${name^^}Bad substitutionprintf '%s' "$name" | tr a-z A-Z
source filesource: not found. file, een punt en een spatie
function f { ... }Syntax error: "}" unexpectedf() { ... }
echo -e "a\tb"drukt -e zelf af als eerste woordprintf 'a\tb\n'
{1..5}drukt de tekst {1..5} afeen while-lus met rekenwerk
<<< "text"syntaxfoutprintf '%s\n' "text" | of een here-document
$RANDOMexpandeert naar nietsawk, of lezen uit /dev/urandom

Eén regel uit die tabel verdient een aantekening. local werkt in dash, in Bash, in ksh en in BusyBox, en toch is het geen standaard: de tekst van POSIX noemt local bij de namen waarvan het resultaat unspecified is. In de praktijk is het veilig op elk systeem dat je waarschijnlijk tegenkomt, en dat is een mooi voorbeeld van het gat tussen "portabel" en "standaard". De standaard beschrijft de ondergrens, niet het plafond.

De expansies die wel standaard zijn dekken het meeste waarvoor mensen naar Bash grijpen:

$ dash -c 'x=abc; echo ${x:-default} ${#x} ${x%c} ${x#a}'
abc 3 ab bc

Dat zijn de POSIX-parameterexpansies: een standaardwaarde, een lengte, en het afhalen van een achtervoegsel of voorvoegsel. ${x%.txt} om een extensie weg te halen en ${x##*/} om een bestandsnaam uit een pad te vissen zijn overal standaard, en ze besparen je een aanroep van basename in een lus.

5.3 Waarom printf echo heeft vervangen

De standaard zegt dat het resultaat implementation-defined is zodra het eerste argument met -n begint, of zodra een argument een backslash bevat. Dat is geen muggenzifterij van een commissie. Elke shell kiest een gedrag, documenteert dat en heeft volledig gelijk, en de twee antwoorden staan op dezelfde machine:

$ bash -c 'echo "a\tb"'
a\tb
$ dash -c 'echo "a\tb"'
a	b

Dezelfde opdracht, dezelfde tekst, twee uitkomsten. Bash drukt de backslash en de letter af; dash verwerkt de escape en drukt een tab af. Geen van beide is fout, en dat is precies het probleem: er is geen antwoord waar je een script op kunt bouwen. printf heeft één vastgelegd gedrag en is zelf ook een standaardprogramma:

$ printf 'a\tb\n'
a	b

De regel die daaruit volgt is kort: gebruik echo voor een vaste tekst zonder escapes en zonder opties, en printf voor al het andere. Let op de expliciete \n: printf zet er geen regeleinde voor je achter, en dat is een voordeel zodra je uitvoer stukje bij beetje opbouwt.

5.4 command -v, niet which

Om te testen of een opdracht bestaat, roepen scripts vaak which aan. Het staat helemaal niet in POSIX, het verschilt per distributie, en op sommige systemen is het een csh-script. Het standaardantwoord is een shell-builtin:

if command -v git >/dev/null 2>&1; then
    echo "git is available"
fi

Dit werkt in elke POSIX-shell, start geen enkel proces, en meldt ook builtins en functies, niet alleen programma's op schijf. which ziet altijd alleen die laatste soort.

5.5 Testen of een script portabel is

Je hoeft niet te gokken of een script binnen de standaard is gebleven. Draai het onder een shell die niets extra's te bieden heeft. dash -n leest een bestand zonder het uit te voeren, en vangt zo in één keer elke bashism op syntaxniveau:

$ dash -n deploy.sh
$ echo $?
0

$ dash -n legacy.sh
legacy.sh: 2: Syntax error: "(" unexpected
$ echo $?
2

Dat is een controle van twee seconden die je in een Makefile of een CI-taak kunt zetten. Hij vindt alleen syntaxfouten, dus een verkeerde optie bij een programma glipt erdoor, maar de array op regel 2 niet.

Voor de rest leest shellcheck de shebang en past het de bijbehorende regels toe, zodat een bestand dat met #!/bin/sh begint waarschuwingen krijgt over constructies die alleen Bash heeft. Heeft een bestand geen shebang, zeg het dan expliciet met een aanwijzing op de eerste regel: # shellcheck shell=sh.

5.6 POSIXLY_CORRECT en de GNU-uitbreidingen

De GNU-programma's gaan verder dan de standaard in één gewoonte die je dagelijks gebruikt zonder het te merken: ze laten opties ook na de bestandsnamen toe. De standaard zegt dat een optie na het eerste argument gewoon een argument is, en GNU-programma's herschikken je opdrachtregel in plaats daarvan. De omgevingsvariabele POSIXLY_CORRECT zet dat uit:

$ ls notes.txt -l
-rw-rw-r-- 1 peter peter 0 Sep  7 10:36 notes.txt

$ POSIXLY_CORRECT=1 ls notes.txt -l
ls: cannot access '-l': No such file or directory
notes.txt

Met die variabele gezet is -l geen optie meer maar een bestandsnaam, en die bestaat niet. Dit is wat een streng systeem al die tijd had gedaan.

Dezelfde variabele verandert standaardwaarden waar GNU een vriendelijkere koos dan de standaard eist. du is het klassieke geval, want POSIX schrijft blokken van 512 bytes voor en GNU gebruikt 1024:

$ du -s report.log
4	report.log
$ POSIXLY_CORRECT=1 du -s report.log
8	report.log

Hetzelfde bestand, dezelfde schijf, twee getallen die een factor twee verschillen. Geen van beide is fout; ze tellen in andere eenheden. Zet POSIXLY_CORRECT=1 voor één opdracht als je wilt weten hoe een streng systeem zich zou gedragen, en zet het niet globaal: het verandert het gedrag van heel veel programma's tegelijk, en de verrassing komt weken later. Merk ook op dat POSIXLY_CORRECT zelf een GNU-afspraak is. De standaard noemt hem nergens.

Naar boven

6. Gevorderde toepassingen: POSIX in C

De andere helft van de standaard is de C-interface, en die bepaalt iets wat mensen de eerste keer verrast: welke functies je compiler wil erkennen.

Compileer je zelf nooit C, ga dan meteen door naar sectie 7. Niets daarin bouwt voort op deze sectie, en de C-punten in de lijsten verderop zijn korte samenvattingen van wat hier volgt.

6.1 De C-bibliotheek is de laag, niet de kernel

POSIX beschrijft een interface voor programma's, en op Linux wordt die interface geleverd door de C-bibliotheek, niet door de kernel. Jouw programma roept een standaardfunctie aan; de C-bibliotheek bepaalt wat er aan de kernel wordt gevraagd:

your program
     |
POSIX functions:  open()  fork()  pthread_create()
     |
the C library:  glibc, or musl on a smaller system
     |
the Linux system call interface
     |
the kernel

Die twee middelste lagen zijn niet dezelfde lijst, en één opdracht laat dat zien. POSIX legt fork() vast. Linux heeft ook echt een fork-systeemaanroep, en glibc gebruikt hem niet: het maakt het kindproces met clone(), omdat die ene aanroep processen en threads samen afdekt.

$ strace -f -e trace=clone,fork ./forktest
clone(child_stack=NULL,
      flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, ...) = 16716
+++ exited with 0 +++

Het programma riep fork() aan. De kernel kreeg clone() te verwerken. Er is niets kapot: een POSIX-functie is een belofte over gedrag, geen naam van een systeemaanroep. Sommige functies zijn dunne omhulsels, sommige zijn uit een andere aanroep opgebouwd, en delen van pthread leven vooral in de bibliotheek.

Daar volgen twee dingen uit. POSIX zegt niets over welke systeemaanroepen bestaan of hoe ze genummerd zijn, dus de kernel mag veranderen hoe een functie wordt uitgevoerd zolang die functie zich blijft gedragen zoals vastgelegd. En conformiteit is een eigenschap van een heel systeem - kernel, C-bibliotheek, shell en programma's samen - en daarom heeft "is Linux POSIX?" geen net antwoord. Linux is de kernel. Wat POSIX implementeert is de distributie.

6.2 Feature test macro's

Hier is een programma dat strdup() gebruikt, een functie die sinds 2008 in POSIX zit en al veel langer in elke C-bibliotheek:

#include <stdio.h>
#include <string.h>

int main(void) {
    char *copy = strdup("hello");
    printf("%s\n", copy);
    return 0;
}

Compileer het als strikte C99 en de compiler zegt dat de functie niet bestaat:

$ gcc -std=c99 -Wall -o t t.c
t.c: In function 'main':
t.c:4:18: warning: implicit declaration of function 'strdup';
                   did you mean 'strcmp'?

Er ontbreekt niets aan je systeem. strdup() zit in de C-bibliotheek, en de linker vindt hem gewoon. Wat er gebeurde is dat -std=c99 om de ISO C-taal vroeg en om niets meer, en strdup() zit niet in ISO C: het is POSIX. Dus verstopte de header hem.

De schakelaar die hem tevoorschijn haalt is een feature test macro, gedefinieerd voordat er een header wordt ingelezen, of op de opdrachtregel:

$ gcc -std=c99 -D_POSIX_C_SOURCE=200809L -Wall -o t t.c
$ ./t
hello

Het getal is dezelfde datumcodering als eerder: vraag om de uitgave van 2008 en je krijgt alles wat die uitgave vastlegt. Vraag om 199309L en je krijgt de kleinere verzameling van de aanvulling uit 1993. De macro's zijn een manier om te zeggen tegen welke standaard je schrijft, en de headers laten je precies zoveel zien:

MacroWat het zichtbaar maakt
_POSIX_C_SOURCE=200809LPOSIX.1-2008 en alles wat ISO C vastlegt
_XOPEN_SOURCE=700Het bovenstaande plus de XSI-uitbreidingen (SUSv4)
_GNU_SOURCEAlles: POSIX, XSI, BSD en de toevoegingen die alleen GNU heeft
geen van alleMet GCC's standaard -std=gnu* ongeveer _DEFAULT_SOURCE: POSIX plus de gebruikelijke uitbreidingen

Dit is waarom hetzelfde bronbestand op je laptop compileert en faalt in een container met een strengere bouwvlag, en waarom handleidingen in de SYNOPSIS een regel hebben die vertelt welke macro een functie nodig heeft. man 7 feature_test_macros is het volledige naslagwerk, en het is een van de nuttigste pagina's op het systeem.

De koppeling is zichtbaar in de headers zelf:

$ grep -n 'define _POSIX_VERSION' /usr/include/unistd.h
34:# define _POSIX_VERSION	200809L
37:# define _POSIX_VERSION	200112L
40:# define _POSIX_VERSION	199506L
43:# define _POSIX_VERSION	199309L
46:# define _POSIX_VERSION	199009L

Vijf definities in één bestand, binnen een reeks #if-takken. Welke macro je ook hebt gevraagd, hij kiest er één van, en jouw programma ziet daarna de versie van de interface die daarbij hoort.

6.3 Hoe POSIX-functies fouten melden

Bijna elke functie in de standaard meldt problemen op dezelfde manier: een retourwaarde die zegt dat er iets mis is gegaan, en de variabele errno die zegt wat. De standaard legt de foutnamen vast, en daarom betekent ENOENT hetzelfde op Linux, op macOS en op AIX:

#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

int main(void) {
    int fd = open("missing.txt", O_RDONLY);
    if (fd == -1) {
        int saved = errno;          /* copy it on the very next line */
        printf("failed: errno %d, %s\n", saved, strerror(saved));
    }
    return 0;
}
$ ./err
failed: errno 2, No such file or directory

Bij errno horen twee regels, en allebei staan ze in de standaard en niet in de folklore. Ten eerste: lees hem pas nadat een aanroep je heeft verteld dat hij faalde. Een geslaagde aanroep mag hem veranderen, en niemand zet hem voor je op nul, dus een oude waarde uit een eerdere aanroep is de gebruikelijke reden dat een foutmelding het verkeerde probleem noemt. Ten tweede: lees hem meteen. Elke bibliotheekaanroep ertussen, ook printf(), mag hem overschrijven. Daarom kopieert het voorbeeld hem naar saved voordat het iets anders doet.

De fout die je bij naam moet kennen is EINTR. Een blokkerende aanroep die door een signaal wordt onderbroken kan daarmee terugkeren in plaats van af te maken, dus portabele code moet beslissen of hij het opnieuw probeert. man 7 signal is het naslagwerk, en die pagina noemt ook een plek waar Linux verder gaat dan de standaard: een blokkerende aanroep kan hier falen met EINTR nadat het proces is gestopt en hervat, en volgens de pagina "is not sanctioned by POSIX.1, and doesn't occur on other systems".

6.4 Vragen tijdens het compileren en tijdens het draaien

Dezelfde vraag heeft twee antwoorden, en ze door elkaar halen is een echte fout. Een programma kan de constante uit <unistd.h> lezen die bij het compileren vaststond:

#include <stdio.h>
#include <unistd.h>

int main(void) {
    printf("%ld\n", (long) _POSIX_VERSION);
    return 0;
}
$ gcc -o v v.c && ./v
200809

Dat is wat de headers op de bouwmachine zeiden. Om het de machine te vragen waar het programma echt op draait, gebruik je de functies die tijdens het draaien werken: sysconf() voor systeembrede waarden, pathconf() en fpathconf() voor waarden die van een bestandssysteem afhangen, en confstr() voor tekstwaarden zoals het standaard-PATH. getconf uit sectie 4.1 is een dun omhulsel om precies deze drie.

Het onderscheid telt bij limieten. De constanten waarvan de naam met _POSIX_ begint zijn het minimum dat de standaard garandeert, niet de grootte van iets op jouw machine. De twee getallen liggen niet dicht bij elkaar:

$ grep -n '_POSIX_NAME_MAX\|_POSIX_PATH_MAX' \
      /usr/include/x86_64-linux-gnu/bits/posix1_lim.h
74:#define	_POSIX_NAME_MAX		14
97:#define	_POSIX_PATH_MAX		256

$ getconf NAME_MAX /       # what this filesystem really allows
255
$ getconf PATH_MAX /
4096

Een systeem dat de standaard volgt moet een naamdeel van 14 tekens en een pad van 256 toestaan. Dit systeem staat 255 en 4096 toe. Schrijf voor de ondergrens als je code overal moet draaien, maar zet geen van beide getallen vast in een buffergrootte: vraag het tijdens het draaien, op het pad dat je gaat gebruiken, want het antwoord verschilt tussen een lokale ext4-koppeling en een netwerkschijf op hetzelfde systeem.

6.5 De optionele delen

Niet alles in POSIX is verplicht. Hele gebieden zijn optionele onderdelen die een systeem dat de standaard volgt mag weglaten, en elk ervan heeft een macro om op te testen. Threads, realtime-signalen, berichtenwachtrijen, asynchrone I/O en gedeeld geheugen vallen allemaal in die categorie:

$ man 7 posixoptions      # the full list, with the sysconf name for each

Op een Linux-systeem voor algemeen gebruik is bijna alles aanwezig, en daarom merk je hier zelden iets van. Het gaat tellen zodra je doel kleiner is: een embedded systeem, een minimale container-image met musl in plaats van glibc, of een realtime-variant. De juiste manier om erachter te komen is het systeem vragen in plaats van aannemen, met getconf in een script of met sysconf() in code.

6.6 De standaardcompiler is een shell-script

POSIX legt een C-compiler vast als opdrachtregelprogramma. Tot 2024 heette dat c99, en jouw systeem heeft het:

$ ls -l /bin/c99
lrwxrwxrwx 1 root root 21 Nov 17  2020 /bin/c99 -> /etc/alternatives/c99
$ ls -l /usr/bin/c99-gcc
-rwxr-xr-x 1 root root 454 Nov 17  2020 /usr/bin/c99-gcc
$ head -10 /usr/bin/c99-gcc
#! /bin/sh

# Call the appropriate C compiler with options to accept ANSI/ISO C
# The following options are the same (as of gcc-3.3):
# 	-std=c99
# 	-std=c9x
# 	-std=iso9899:1999
# 	-std=iso9899:199x

extra_flag=-std=c99

De POSIX C-compiler op een Linux-systeem is een shell-script van 454 bytes dat -std=c99 toevoegt en GCC aanroept. Dat is het patroon voor een groot deel van de standaard: hij eist geen bepaalde implementatie, alleen een opdracht met een vastgelegde naam en vastgelegd gedrag. Alles wat aan de beschrijving voldoet is een implementatie die de standaard volgt, ook een omhulsel dat je bij een kop koffie uitleest. De herziening van 2024 heeft c99 met pensioen gestuurd en c17 in de plaats gezet.

6.7 De STANDARDS-sectie in handleidingen

Linux-handleidingen van functies hebben een STANDARDS-sectie die noemt uit welke standaard een interface komt, en dat is de snelste portabiliteitscontrole die er is:

$ man 2 open      # then look for STANDARDS: POSIX.1-2008
$ man 3 strdup    # POSIX.1-2008; before that, a common extension
$ man 2 epoll_create  # STANDARDS: Linux only

"Linux" in die sectie betekent dat de code die je schrijft niet compileert op een BSD of op macOS. "POSIX.1-2008" betekent dat hij overal werkt. Oudere pagina's noemen dit CONFORMING TO, wat dezelfde informatie onder een andere kop is.

6.8 Waar Linux verder gaat dan de standaard

Die sectie is het nuttigst wanneer twee interfaces hetzelfde werk doen en er maar één portabel is. Wachten op activiteit op veel file descriptors tegelijk is het duidelijkste geval:

$ man 2 select      # STANDARDS: POSIX.1-2008
$ man 2 poll        # STANDARDS: POSIX.1-2008
$ man 7 epoll       # STANDARDS: Linux
$ man 2 signalfd    # STANDARDS: Linux
$ man 7 inotify     # STANDARDS: Linux

select() en poll() staan in de standaard en werken op elke Unix. epoll is alleen Linux, en het bestaat omdat die twee slecht schalen zodra je duizenden descriptors in de gaten houdt: ze geven de kernel bij elke aanroep de hele lijst mee. Hetzelfde patroon herhaalt zich met inotify voor bestandswijzigingen, met eventfd en signalfd, en met het nieuwere io_uring. Elk daarvan is een Linux-antwoord op een echte beperking van de portabele interface.

Dat is een afweging, geen fout. Een portabele bibliotheek houdt het bij poll(). Een server die toch alleen op Linux gaat draaien gebruikt epoll en zegt dat erbij. Wat misgaat is kiezen zonder het te merken, en dat is precies wat de STANDARDS-sectie voorkomt.

Dezelfde tweedeling bestaat buiten C. /proc is een Linux-uitvinding en staat in geen enkele standaard, dus elk script dat /proc/$$/fd of /proc/meminfo leest is alleen-Linux, hoe gewoon het er ook uitziet. Op een server die je zelf beheert is dat meestal prima. Het is handig om te weten voordat iemand het script op een Mac draait.

Naar boven

7. Iets wat de meeste gebruikers niet weten

7.1 Het standaard-archiefprogramma is niet tar

Vraag iemand een POSIX-programma te noemen en tar komt al snel langs. Het staat niet in de standaard. Het standaard-archiefprogramma is pax, en de rationale legt in één zin uit waarom: "The pax utility was new for the ISO POSIX-2:1993 standard. It represents a peaceful compromise between advocates of the historical tar and cpio utilities." De commissie kon niet kiezen tussen twee archiefformaten en twee kampen, dus legde ze een derde programma vast dat allebei leest en schrijft.

Het compromis heeft niet gewonnen:

$ command -v tar
/bin/tar
$ command -v pax
$ echo $?
1

tar staat op de machine en pax is helemaal niet geïnstalleerd. Het niet-standaard programma staat overal, het standaardprogramma moet je met opzet installeren, en elk script ter wereld gebruikt tar. Dat is het onthouden waard zodra "POSIX" als synoniem voor "portabel" wordt gebruikt: de standaard beschrijft wat een systeem dat hem volgt moet leveren, en de werkelijkheid levert een nogal andere verzameling. GNU tar en BSD tar verschillen bovendien van elkaar, en dat is precies het praktische portabiliteitsprobleem dat de standaard niet heeft opgelost.

7.2 POSIX-ACL's zijn geen POSIX

Toegangslijsten op Linux heten overal POSIX-ACL's: in documentatie, in koppelopties, en in de naam van het uitgebreide attribuut waarin ze staan (system.posix_acl_access). Ze zijn nooit gestandaardiseerd. De handleiding zegt het onomwonden:

$ man 5 acl
STANDARDS
       The IEEE 1003.1e draft 17 ("POSIX.1e") document describes several
       security extensions to the IEEE 1003.1 standard. While the work on
       1003.1e has been abandoned, many UNIX style systems implement parts
       of POSIX.1e draft 17, or of earlier drafts.

Het werk aan POSIX.1e stopte eind jaren negentig en het concept werd ingetrokken. Bouwers hadden al tegen draft 17 aan gewerkt, dus de interface overleefde zijn eigen standaard: getfacl, setfacl en de acl_*-functies in C op Linux implementeren een document dat nooit is afgemaakt. De naam bleef hangen omdat er niets anders was om het te noemen.

Het praktische gevolg is dat ACL-gedrag per systeem verschilt op manieren die geen enkele standaard oplost, en dat een cp zonder -p of -a ze stilletjes laat vallen. Als rechten zich "vanzelf resetten" na een kopie of een terugzetactie, is dit meestal de reden.

7.3 Twee gecertificeerde UNIX-systemen zijn Linux-distributies

Linux is geen gecertificeerde UNIX, en dat weet iedereen. Wat bijna niemand weet, is dat twee Linux-distributies de certificering hebben doorstaan en het handelsmerk voeren: Inspur K-UX, gebaseerd op Red Hat Enterprise Linux, en Huawei's EulerOS, gebaseerd op CentOS. Allebei staan ze bij The Open Group geregistreerd, naast macOS, AIX, HP-UX en Solaris.

Ze bewijzen het punt uit sectie 1.3. Certificering is een commercieel proces: je draait de testsuite, je betaalt de kosten, je licentieert het handelsmerk. Er is technisch niets wat een Linux-distributie tegenhoudt om gecertificeerde UNIX te zijn, en de reden dat de gangbare distributies dat niet zijn, is dat niemand wil betalen voor een handelsmerk waar zijn gebruikers niet om vragen. Het verschil tussen "UNIX" en "Unix-achtig" is juridisch, niet technisch.

7.4 De standaard volgt de praktijk, en jouw systeem loopt erachteraan

Mensen nemen aan dat standaarden voorop lopen en implementaties volgen. POSIX werkt andersom: de commissie beschrijft liever wat implementaties al doen, en daarom standaardiseerde de herziening van 2024 eindelijk readlink, realpath en timeout, gereedschap dat al twintig jaar op elk Linux-systeem stond.

De vertraging loopt twee kanten op, en de tweede kant verrast mensen. set -o pipefail werd in 2024 gestandaardiseerd. Hier is hij in de shell die vandaag /bin/sh is op deze machine:

$ dash -c 'set -o pipefail'
dash: 1: set: Illegal option -o pipefail

Hetzelfde geldt voor quoten met $'...', wat de tekst van 2024 in een eigen sectie vastlegt en dash niet begrijpt:

$ bash -c "printf '%s\n' \$'a\tb'"
a	b
$ dash -c "printf '%s\n' \$'a\tb'"
$a\tb

Dus "het staat in POSIX" en "het werkt overal" zijn twee verschillende beweringen, en voor alles wat in de laatste uitgave is toegevoegd is de tweede nog jaren onwaar. Portabiliteit wordt bepaald door het oudste systeem dat je moet ondersteunen, niet door de nieuwste standaard.

7.5 bash --posix is geen portabiliteitscontrole

Bash heeft een POSIX-modus, te bereiken met --posix of set -o posix, en dat lijkt precies het gereedschap dat je wilt om een script te testen. Dat is het niet. Het past een lijst gedragingen aan waar Bash standaard afwijkt van de standaard, en laat elke Bash-uitbreiding gewoon staan:

$ bash --posix -c '[[ 1 = 1 ]] && echo yes'
yes
$ bash --posix -c 'a=(x y); echo ${a[1]}'
y
$ bash --posix -c 'echo {1..3}'
1 2 3

Dubbele blokhaken, arrays en accolade-expansie werken allemaal gewoon in POSIX-modus. Een script vol bashisms komt er schoon doorheen en faalt dan op de eerste machine waar /bin/sh dash is. Bash die onder de naam sh start gaat in dezelfde gedeeltelijke modus, en daarom kan een script slagen op Red Hat, waar sh Bash is, en falen op Debian, waar dat niet zo is. Om portabiliteit te testen, draai je het script onder een andere shell, zoals in sectie 5.5.

7.6 Portabele broncode is geen portabele binary

POSIX standaardiseert broncode. Compileer die broncode op Linux en je krijgt een programma dat op Linux draait en nergens anders, hoe zorgvuldig portabel de code ook was. Twee dingen bepalen dat, en de standaard gaat over geen van beide.

Het eerste is het uitvoerbare formaat. Linux gebruikt ELF, macOS gebruikt Mach-O, en een bestand in het ene formaat zegt niets tegen een systeem dat het andere verwacht:

$ file /bin/ls
/bin/ls: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), ...

Het tweede is de nummering onder elke systeemaanroep. Het nummer van write is niet eens hetzelfde tussen Linux-architecturen:

$ grep -w 'define __NR_write' /usr/include/x86_64-linux-gnu/asm/unistd_64.h
#define __NR_write 1
$ grep -w 'define __NR_write' /usr/include/asm-generic/unistd.h
#define __NR_write 64

Nummer 1 op x86-64, nummer 64 op de architecturen die de generieke tabel gebruiken, zoals arm64. Die nummering, de registers waarin de argumenten reizen, hoe een structuur in het geheugen ligt, hoe symbolen heten: dat alles is de ABI, de application binary interface. POSIX is een API, een application programming interface. Het is een belofte over wat jouw broncode betekent, en elk systeem komt die belofte na met zijn eigen compiler.

"Het draait op elk POSIX-systeem" heeft er altijd een ongeschreven woord voor staan: hercompileerd. Portabiliteit op broncodeniveau en compatibiliteit op binair niveau zijn verschillende problemen, en POSIX lost alleen het eerste op.

Daarom draait een container-image dat voor de ene architectuur is gebouwd niet op de andere, ook al zijn het allebei Linux, en daarom betekende "werkt op Unix" altijd een bouwstap en geen kopieeractie.

7.7 Sommige dagelijkse opties waren nooit standaard

Een paar gewoontes zijn zo gangbaar dat mensen aannemen dat ze overal werken. Het programma date uit de standaard legt precies één optie vast, -u, en zijn opmaakcodes komen uit strftime(), dat geen %s kent. Allebei de volgende zijn GNU-uitbreidingen:

$ date +%s                # seconds since the epoch: GNU, not POSIX
$ date -d '2 days ago'    # date arithmetic: GNU only, BSD uses -v

Hetzelfde geldt voor sed -i, dat GNU als -i schrijft en BSD als -i '', voor grep -P, voor find -print0 en xargs -0, en voor mktemp en seq, die helemaal niet in de standaard staan. Dat betekent niet dat je ze niet meer moet gebruiken. Het betekent dat dit de regels zijn die aandacht nodig hebben zodra een script op meer dan één soort systeem moet draaien, en dat find -exec cmd {} + de standaardvervanging is voor de -print0-pijplijn.

Naar boven

8. Beste werkwijzen

  • Laat de shebang passen bij de taal die je hebt geschreven. #!/bin/bash als je Bash-functies gebruikt, #!/bin/sh alleen als je binnen de standaard bent gebleven. Allebei zijn goed; de verkeerde claimen is de fout.
  • Test sh-scripts onder een strenge shell. dash -n script.sh duurt twee seconden en vangt elke bashism op syntaxniveau. Zet het in je Makefile of CI-taak, naast shellcheck.
  • Gebruik printf en niet echo zodra er escapes of opties in het spel zijn. Het gedrag van echo met -n en backslashes is implementation-defined, en twee shells op één machine zijn het al oneens.
  • Gebruik command -v om te testen of een opdracht bestaat. which is geen standaard, verschilt per distributie, en ziet geen builtins of functies.
  • Quote elke variabele, en houd bestandsnamen in de portabele tekenset. "$file" kost twee tekens. pathchk -p vertelt je welke namen elders problemen geven.
  • Vraag het systeem naar limieten in plaats van ze vast in te typen. getconf in scripts, sysconf() en pathconf() in C. De _POSIX_-constanten zijn gegarandeerde minima, niet de waarden van jouw machine.
  • Zet expliciet de feature test macro die je code nodig heeft. -D_POSIX_C_SOURCE=200809L legt vast tegen welke standaard je hebt geschreven en voorkomt dat een strengere bouwvlag de halve C-bibliotheek verstopt.
  • Noem de versie als je "POSIX" zegt. "POSIX.1-2008" is een feit. "POSIX compliant" is een gevoel, en de twee systemen in de discussie hebben waarschijnlijk allebei gelijk.
  • Zet POSIXLY_CORRECT niet globaal. Gebruik het per opdracht om te zien hoe een streng systeem zich zou gedragen, en haal het daarna weg. Het verandert veel programma's tegelijk.
  • Lees de documentatie. Alles staat er al, of is één pakket weg:
$ man 7 standards            # every standard, with dates and lineage
$ man 7 posixoptions         # the optional units of the standard
$ man 7 feature_test_macros  # which macro exposes which interfaces
$ getconf -a                 # what this system reports about itself
$ man 1p sh                  # the standard itself, after installing manpages-posix

De volledige tekst is gratis te lezen op pubs.opengroup.org, en dat is de enige bron die een discussie beslecht.

Naar boven

9. Veelgemaakte fouten

9.1 Mythe versus werkelijkheid

MytheWerkelijkheid
"POSIX is software die je kunt installeren." POSIX is een document. Systemen implementeren het; niets op jouw machine is POSIX zelf.
"Linux is POSIX-gecertificeerd." Linux is niet gecertificeerd. Het implementeert het grootste deel van de standaard, en certificering is een commerciële licentie en geen technisch rapportcijfer.
"#!/bin/sh betekent Bash." Op Debian en Ubuntu is het dash, op Alpine BusyBox ash. Het betekent "de standaardshell", welk programma dat ook is.
"Als het een gangbare opdracht is, is het POSIX." tar, which, mktemp, seq, ping en ssh ontbreken allemaal in de standaard.
"POSIX-ACL's horen bij POSIX." Ze komen uit IEEE 1003.1e draft 17, dat is opgegeven. De naam heeft de standaard overleefd.
"bash --posix controleert mijn script op portabiliteit." Het verandert een paar standaardwaarden en houdt elke Bash-uitbreiding. Arrays en [[ ]] werken gewoon. Test in plaats daarvan met dash.
"Het is gestandaardiseerd, dus het werkt overal." set -o pipefail staat in POSIX.1-2024 en dash weigert het nog steeds. Standaarden lopen jaren voor op implementaties.
"Mijn code is POSIX, dus de binary draait op elke Unix." POSIX standaardiseert broncode. Uitvoerbare formaten en nummers van systeemaanroepen horen bij de ABI, en daar raakt POSIX niet aan. Portabiliteit betekent hercompileren.
"POSIX bepaalt waar bestanden staan, zoals /etc en /var." Dat is de Filesystem Hierarchy Standard, een apart Linux-document. POSIX zegt vrijwel niets over de mappenindeling.
"De _POSIX_-limieten vertellen me wat mijn systeem aankan." Het zijn de minima die de standaard garandeert. Vraag sysconf() of pathconf() naar de echte waarden.

9.2 Andere valkuilen om te vermijden

  • #!/bin/sh uit gewoonte schrijven. De meeste scripts die zo beginnen zijn Bash-scripts die dash nog nooit hebben ontmoet. Repareer de code, of repareer de regel.
  • Alleen testen op de machine waarop je het schreef. Een portabiliteitsfout is per definitie onzichtbaar op één systeem. De controle is goedkoop: dash -n, of een container met een andere basis-image.
  • Aannemen dat de GNU-handleiding de standaard is. man 1 sed beschrijft jouw sed. man 1p sed beschrijft elke sed. Het gat ertussen is precies jouw portabiliteitsrisico.
  • PATH_MAX vast in een buffergrootte zetten. Het is een gegarandeerd minimum en het verschilt per bestandssysteem. Reserveer op basis van pathconf().
  • Denken dat een compilerfout een ontbrekende bibliotheek betekent. "Implicit declaration of function" bij een POSIX-functie betekent bijna altijd een feature test macro, en niet een ontbrekend pakket.
  • POSIX verwarren met de FHS of de LSB. POSIX legt de interface vast. De Filesystem Hierarchy Standard legt de mappenindeling vast, en de Linux Standard Base legde een profiel voor binaire compatibiliteit vast. Andere documenten, ander bereik.
  • Leunen op uitvoer die van de locale afhangt. Sorteren, datumnotaties en meldingsteksten veranderen met de locale. Zet LC_ALL=C als een script de uitvoer van een ander programma leest, zodat het op elke machine dezelfde bytes krijgt.
  • "Unspecified" behandelen als "dat overkomt mij niet". De vier woorden uit sectie 1.4 vormen een schaal van beloftes, en unspecified is waar implementaties met recht verschillen. Precies daar zit de storing van drie uur 's nachts.
Naar boven

10. Samenvatting

POSIX is het geschreven contract tussen programma's en de systemen waarop ze draaien: een document, uitgegeven door IEEE en The Open Group, dat zegt wat een Unix-achtig systeem moet leveren. Het is de reden dat een shell-script uit 1995 nog draait, dat man 1p en man 1 verschillende pagina's zijn, en dat de discussie of een machine "fout" is meestal een antwoord heeft dat je kunt aanhalen.

  • POSIX is een standaarddocument, geen software. Systemen implementeren het; certificering is een apart commercieel traject, en Linux koopt dat niet.
  • De naam betekent Portable Operating System Interface. De X is een knipoog naar de Unix-naamgeving, en Richard Stallman bedacht de term.
  • Het verschijnt in vier delen: definities, C-interfaces, shell en programma's, en de rationale. Eén document, ook bekend als IEEE 1003.1, ISO/IEC 9945 en de Single UNIX Specification.
  • Het ontstond uit de splitsing tussen System V en BSD, werd in 2001 door de Austin Group samengevoegd met de Single UNIX Specification, en werd na zestien jaar rust herzien als POSIX.1-2024.
  • getconf beantwoordt vragen over je eigen systeem: _POSIX_VERSION, het standaard-PATH, en de limieten achter foutmeldingen als "Argument list too long".
  • #!/bin/sh belooft standaard shell-code. Op Debian en Ubuntu is die shell dash, en dash -n controleert die belofte in twee seconden.
  • De gebruikelijke bashisms - [[ ]], arrays, ${x^^}, source, echo -e - hebben allemaal een standaardequivalent, en printf vervangt echo zodra er een escape in zit.
  • Vier woorden dragen de hele belofte: defined, implementation-defined, unspecified, undefined. In de middelste twee zitten de portabiliteitsfouten.
  • Op Linux levert de C-bibliotheek POSIX, niet de kernel. Een programma dat fork() aanroept bereikt de kernel als clone(), en conformiteit hoort bij de hele distributie en niet bij Linux zelf.
  • In C bepalen feature test macro's welke functies de headers laten zien. -D_POSIX_C_SOURCE=200809L is het verschil tussen "die functie bestaat niet" en een schone build.
  • Standaardfuncties melden een fout via een retourwaarde plus errno. Lees hem alleen na een fout, en lees hem meteen: de volgende bibliotheekaanroep kan hem overschrijven.
  • POSIX is een API-standaard en geen ABI-standaard: portabele broncode, per systeem opnieuw gecompileerd. Uitvoerbare formaten en nummers van systeemaanroepen vallen erbuiten, en select() is portabel waar epoll alleen Linux is.
  • Vraag limieten tijdens het draaien op met sysconf() en pathconf(). De _POSIX_-constanten zijn minima, geen metingen.
  • Het standaard-archiefprogramma is pax, en dat is niet geïnstalleerd. POSIX-ACL's komen uit een opgegeven concept. Twee gecertificeerde UNIX-systemen zijn Linux-distributies.
  • bash --posix is geen portabiliteitscontrole, en dat iets in de nieuwste standaard staat betekent niet dat de shell voor je neus het al heeft.

Dit is het spiekbriefje dat het bewaren waard is:

getconf _POSIX_VERSION       which edition this system implements
getconf _XOPEN_VERSION       which Single UNIX Specification issue
getconf -a                   every value the system reports
getconf PATH                 the standard PATH for finding utilities
getconf ARG_MAX              the limit behind "Argument list too long"
command -p -v NAME           find a command using the standard PATH
command -v NAME              portable test that a command exists
pathchk -p NAME              is this filename portable
dash -n script.sh            parse a script with a strict POSIX shell
POSIXLY_CORRECT=1 cmd        run one command as a strict system would
printf 'a\tb\n'              portable output; echo escapes are implementation-defined
man 7 standards              every standard, with dates and lineage
man 7 posixoptions           the optional units of the standard
man 7 feature_test_macros    which macro exposes which C interfaces
man 2 poll / man 7 epoll     a portable interface versus a Linux-only one
strace -e trace=clone ./prog which system call a POSIX function becomes
man 1p sh / man 3p open      the standard's own manual pages
gcc -D_POSIX_C_SOURCE=200809L  expose POSIX.1-2008 in the headers

En als een script drie jaar lang onaangeroerd heeft gedraaid en dan faalt in de week dat een server opnieuw is opgebouwd, is de code zelden wat er is veranderd: iets eronder is opgehouden het systeem te zijn dat het script stilzwijgend aannam, en in de standaard vind je wie van de twee zat te verzinnen.

Naar boven
Linux concept: POSIX
Peter Martin
Peter Martin
Joomla Specialist

Peter is Joomla specialist en Linux admin voor snelle, veilige en schaalbare websites.

Gerelateerde artikelen