Results 1 to 6 of 6

Thread: Korte uitleg: File descriptors en redirection in de shell (en pipes)

  1. #1
    Join Date
    Jun 2008
    Location
    Netherlands
    Posts
    27,108

    Default Korte uitleg: File descriptors en redirection in de shell (en pipes)

    File descriptors en redirection in de shell (en pipes)

    De zogenaamde Redirection faciliteit zoals gebruikt in de shell is soms lastig te overzien. Vooral bash heeft nogal wat uitbreidingen op de oorspronkelijke syntax, die niet allemaal even makkelijk te volgen zijn. Dit verhaal (want Korte uitleg) blijft beperkt tot de belangrijkste zaken, waarmee je echt goed vooruit kunt. Voor we verder gaan nu eerst enige uitleg over File descriptors.

    Zoals je al hebt gelezen gaat alle gebruik van hardware via de Kernel, dus ook alle I/O (invoer/uitvoer). Daar zijn weer een heleboel library routines voor met namen als fopen(), fread(), fwrite(), fclose(). Omdat een proces natuurlijk tegelijkertijd op meer bestanden bezig kan zijn, moet aan ieder van die routines minstens worden verteld welk bestand bedoeld is. De Kernel heeft voor elk open bestand een tabel met gegevens over dat bestand en zijn toestand (waar staat het bestand, waar zijn we met lezen gebleven, enz., enz.). Deze tabel heet de File Descriptor. File descriptors hebben een unieke identificatie binnen de Kernel, ten eerste de PID en daarna een nummer uniek binnen de PID. Ieder proces dat wordt gestart krijgt al gelijk van de Kernel drie file descriptors met vastgestelde file descriptor nummers: standard input (stdin met nummer 0), standard output (stdout met nummer 1) en standard error (stderr met nummer 2).

    Interaktieve (in de CLI) programma's gebruiken stdin (0) om te lezen van de terminal, stdout (1) om te schrijven naar de terminal en stderr (2) om eventuele waarschuwingen en foutmeldingen op de terminal te zetten. Bij het opstarten van een proces dat met een terminal is verbonden zorgt de shell ervoor dat stdin, stdout en stderr ook inderdaad van en naar de terminal werken.

    Maar daar kunnen we verandering in brengen. Als we aan de Kernel konden vertellen dat stdout niet naar de terminal moet maar naar een bestand, konden we de uitvoer van een programma zoals ls opvangen in een bestand en daar later iets leuks mee doen. En dat kan. We kunnen de Kernel vertellen om de bestemming in zijn file descriptor 1 te wijzigen in de naam van een bestand. En de shell geeft ons de mogelijkheid dat te gebruiken. Belangrijk voor het begrip is dat in de file descriptor dus een veldje is waarin staat waar het heen moet (of waar het vandaan komt). En de inhoud van dat veldje kunnen we veranderen.

    De uitgangstoestand is dus zo:
    0 TTY
    1 TTY
    2 TTY

    Willen we nu de stdout van ls naar het bestand /tmp/ls-uitvoer sturen dan doen we:
    Code:
    ls -l >/tmp/ls-uitvoer
    Het ls proces wordt nu opgestart met als tabel:
    0 TTY
    1 /tmp/ls-uitvoer
    2 TTY

    En na afloop kunnen we in /tmp/ls-uitvoer kijken. Dus bijv.:
    Code:
    henk@boven:~/test/bestanden> ls -l verf.jpeg lak.jpeg
    ls: kan geen toegang krijgen tot lak.jpeg: Bestand of map bestaat niet
    -rw-r--r-- 1 henk wij 157943 10 mei 20:38 verf.jpeg
    henk@boven:~/test/bestanden> ls -l verf.jpeg lak.jpeg >/tmp/ls-uitvoer
    ls: kan geen toegang krijgen tot lak.jpeg: Bestand of map bestaat niet
    henk@boven:~/test/bestanden> cat /tmp/ls-uitvoer
    -rw-r--r-- 1 henk wij 157943 10 mei 20:38 verf.jpeg
    henk@boven:~/test/bestanden>
    Inderdaad de uitvoer zit in /tmp/ls-uitvoer en we zien ook een foutmelding omdat lak.jpeg niet bestaat, die komt via stderr en dus via de TTY. Daar kunnen we wat aan doen:
    Code:
    henk@boven:~/test/bestanden> ls -l verf.jpeg lak.jpeg >/tmp/ls-uitvoer 2>/tmp/ls-fout
    henk@boven:~/test/bestanden> cat /tmp/ls-uitvoer
    -rw-r--r-- 1 henk wij 157943 10 mei 20:38 verf.jpeg
    henk@boven:~/test/bestanden> cat /tmp/ls-fout
    ls: kan geen toegang krijgen tot lak.jpeg: Bestand of map bestaat niet
    henk@boven:~/test/bestanden>
    En onze referentietabel is nu zo:
    0 TTY
    1 /tmp/ls-uitvoer
    2 /tmp/ls-error

    Nog even over de syntax. Het > wijst op redirection van uitvoer. Daarvoor staat het nummer van de file descriptor, dus 1 voor stdout en 2 voor stderr. Die 1 kun je weglaten.

    Hetzelfde geldt voor redirection van invoer, alleen gebruiken we daar de <. En weglaten van het nummer betekent stdin. Stel je hebt de invoer van een programma voorbereidt door met een editor een bestand met invoerregels te maken, dan kun je die aan het programma toevoeren alsof je ze had ingetikt na de programma aanroep:
    Code:
    programma <invoer
    De bijbehorende tabel is:
    0 invoer
    1 TTY
    2 TTY

    Misschien even pauze? -------------------------------

    En nu de bekende:
    Code:
    programma >/tmp/uitvoer 2>&1
    Bijzonder is hier die 2>&1. We redirecten stderr naar de plek waar stdout verwijst. Hiervoor is belangrijk te onthouden dat de redirections van links naar rechts worden verwerkt. We starten met:
    0 TTY
    1 TTY
    2 TTY

    dan komt >/tmp/uitvoer:
    0 TTY
    1 /tmp/uitvoer
    2 TTY

    en nu komt 2>&1, dus in rij 2 komt hetzelfde als nu in rij 1 staat:
    0 TTY
    1 /tmp/uitvoer
    2 /tmp/uitvoer

    stdout zowel als stderr gaan nu naar /tmp/uitvoer.

    Als je dit begrijpt, begrijp je ook waarom
    Code:
    programma 2>&1 1>/tmp/uitvoer
    iets anders is. Je begrijpt dit als je het maar stap voor stap naspeelt. Start:
    0 TTY
    1 TTY
    2 TTY

    dan komt 2>&1, dus in rij 2 komt hetzelfde als nu in rij 1 staat:
    0 TTY
    1 TTY
    2 TTY

    er is dus eigenlijk niets veranderd en nu komt >/tmp/uitvoer:
    0 TTY
    1 /tmp/uitvoer
    2 TTY

    en dus anders dan de vorige keer. Zo'n n>&m kopieëert dus de inhoud van het veld van m naar het veld van n. Verder niets.

    Redirection wordt ook vaak gebruikt om uitvoer in "het zwarte gat" te laten verdwijnen. We willen wel een programma aanroepen, maar zijn niet geïntereseert in zijn stdout en stderr:
    Code:
    programma >/dev/null 2>&1
    Extra achtergrondinformatie: /dev/null is een character device special file waar je naar toe kunt schrijven, maar er wordt niets mee gedaan. (Over device special files komt/is een aparte Korte uitleg).

    Behalve > kun ke ook >> gebruiken. Wat is het verschil?
    Bij > wordt het bestand als het nog niet bestaat aangemaakt, als het wel bestaat wordt het leeggemaakt.
    bij >> wordt het bestand als het nog niet bestaat aangemaakt, als het wel bestaat komt de nieuwe uitvoer er achteraan bij.

    Dit wordt een lange Korte uitleg ------------------------------------------

    Je kunt ook de stdin, stdout en stderr van de shell waar je nu in zit redirecten. Dat gaat gek genoeg met het shell build in commando exec (dat eigenlijk heel ergens anders voor is).
    Code:
    exec >pagina.html
    en alle uitvoer van alle commando's die daarna komen (ingetikt in een interaktieve shell, of vanuit een script) gaat naar pagina.html. Ik gebruik dit zelf in een script dat een HTML pagina opbouwt uit allerlei aanroepen. Daarbij stuitte ik op het volgende. Als de pagina klaar is, wil ik niet dat eventuele volgende uitvoer nog naar pagina.html gaat. Ik wil stdout dus weer "terugzetten" op wat hij was. Maar wat was hij? Het volgende lost dit op. Uitgangspunt:
    0 TTY
    1 weetniet
    2 TTY

    dan doen we:
    Code:
    exec 3>&1 >pagina.html
    en de table wordt eerst:
    0 TTY
    1 weetniet
    2 TTY
    3 weetniet

    en dan:
    0 TTY
    1 pagina.html
    2 TTY
    3 weetniet

    Na afloop doen we:
    Code:
    exec >&3
    dat levert:
    0 TTY
    1 weetniet
    2 TTY
    3 weetniet

    en stdout is weer wat hij eerst was, zelfs al weten we niet wat dat was.

    We hebben het al gehad over het redirecten van input naar een bestand. Als dat bestand niet te groot is kun je besluiten het in het script zelf te zetten als een zogenaamd "here document". Dat ziet er zo uit:
    Code:
    programma <<EOF
    invoer regel
    nog een invoer regel
    EOF
    De shell neemt de regels na het commando en voor de regel met EOF apart en stuurt ze via de stdin van programma. De kreet EOF mag ook iets anders zijn. EOF (of wat je hebt gekozen) aan het eind moet geheel alleen op de regel staan. Er zijn wat varianten. Zoek dit op in de man pagina van bash.

    Als laatste: "pipes".
    Dit lijkt op redirection, maar het wordt door de shell anders gezien en staat ook heel ergens anders in de man pagina. Een pipe is een koppeling tussen twee programma's waarbij de stdout van het eerste programma wordt gekoppeld aan de stdin van het tweede programa. Dit is een zeer krachtig middel en met wat handig gebruik kun je hier al veel mee doen. Zowel gelijk ingetikt als in een script.
    Code:
    henk@boven:~/test/bestanden> ps -ef | grep '^henk' | tail
    henk     19341 11627  0 17:03 pts/2    00:00:00 vi 5-Redirect
    henk     20133  2860  0 17:21 ?        00:00:00 kio_http [kdeinit] http /tmp/ksocket-henk/k  
    henk     20141  2860  0 17:24 ?        00:00:00 kio_http [kdeinit] http /tmp/ksocket-henk/k  
    henk     20142  2860  0 17:24 ?        00:00:00 kio_http [kdeinit] http /tmp/ksocket-henk/k  
    henk     20143  2860  0 17:24 ?        00:00:00 kio_http [kdeinit] http /tmp/ksocket-henk/k  
    henk     20144  2860  0 17:24 ?        00:00:00 kio_http [kdeinit] http /tmp/ksocket-henk/k  
    henk     20145  2860  0 17:24 ?        00:00:00 kio_http [kdeinit] http /tmp/ksocket-henk/k  
    henk     20342 12879  0 17:25 pts/3    00:00:00 ps -ef
    henk     20343 12879  0 17:25 pts/3    00:00:00 grep --color=auto ^henk
    henk     20344 12879  0 17:25 pts/3    00:00:00 tail
    henk@boven:~/test/bestanden>
    Het programma ps levert een lijst van draaiende processen op stdout. Die lijst gaat naar de stdin van grep waar alleen de regels die beginnen met de tekst henk worden doorgelaten naar stdout. Die gaan naar stdin van tail, die alleen het staartje van de lijst laat zien.
    Last edited by hcvv; 16-Aug-2016 at 07:58.
    Henk van Velden

  2. #2
    Join Date
    Nov 2009
    Location
    Groningen, NL
    Posts
    242

    Default Re: Korte uitleg: File descriptors en redirection in de shell (en pipes)

    Wat een heldere uitleg! Deed me denken aan een tocht met een gids door onoverzichtelijk en moeilijk alpien terrein die achteraf zonder problemen en met veel leermomenten was verlopen.

    Nog een vraagje over redirection:
    De notatie is 2>&1 en niet 2>1. Voor zover ik kan nagaan wordt bij "2>1" de error-output naar een bestand met de naam "1" geschreven. De notatie "&1" zorgt er dus(?) voor dat de bestemming via file-descriptor 1 loopt.
    Klopt dat?
    Hans Schreuder

  3. #3
    Join Date
    Jun 2008
    Location
    Netherlands
    Posts
    27,108

    Default Re: Korte uitleg: File descriptors en redirection in de shell (en pipes)

    Quote Originally Posted by hws38 View Post
    Wat een heldere uitleg! Deed me denken aan een tocht met een gids door onoverzichtelijk en moeilijk alpien terrein die achteraf zonder problemen en met veel leermomenten was verlopen.
    Hoera! Een bergsporter!

    Quote Originally Posted by hws38 View Post
    Nog een vraagje over redirection:
    De notatie is 2>&1 en niet 2>1. Voor zover ik kan nagaan wordt bij "2>1" de error-output naar een bestand met de naam "1" geschreven. De notatie "&1" zorgt er dus(?) voor dat de bestemming via file-descriptor 1 loopt.
    Klopt dat?
    Ja, achter de > staat een bestandsnaam. En 1 is natuurlijk gewoon een bestandsnaam. Die & is echt gebruikt om af te wijken van een (gebruiklijke) bestandsnaam en het de speciale betekenis te geven. Helaas is dat niet geheel waterdicht. Hoewel je zou denken dat een bestandsnaam geen & zou mogen bevatten, of in elk geval er niet mee zou mogen beginnen, is dat niet zo:
    Code:
    henk@boven:~/test/bestanden/gewone file> touch '&'
    henk@boven:~/test/bestanden/gewone file> l
    totaal 8
    drwxr-xr-x 2 henk wij 4096 12 mei 12:33 ./
    drwxr-xr-x 3 henk wij 4096 10 mei 20:45 ../
    -rw-r--r-- 1 henk wij    0 12 mei 12:33 &
    henk@boven:~/test/bestanden/gewone file>
    Maar als je wilt kun je zelfs met redirection een file waarvan de naam met een & begint maken:
    Code:
    henk@boven:~/test/bestanden/gewone file> l aap 2>'&1'
    henk@boven:~/test/bestanden/gewone file> l
    totaal 12
    drwxr-xr-x 2 henk wij 4096 12 mei 12:40 ./
    drwxr-xr-x 3 henk wij 4096 10 mei 20:45 ../
    -rw-r--r-- 1 henk wij   66 12 mei 12:40 &1
    henk@boven:~/test/bestanden/gewone file> cat '&1'
    ls: kan geen toegang krijgen tot aap: Bestand of map bestaat niet
    henk@boven:~/test/bestanden/gewone file>
    Henk van Velden

  4. #4
    Join Date
    Jun 2008
    Location
    Netherlands
    Posts
    27,108

    Default Re: Korte uitleg: File descriptors en redirection in de shell (en pipes)

    Deze Korte uitleg is helaas vrij lang geworden. Maar volgens mij is de uitleg met de tabelletjes nodig om te begrijpen wat er gebeurt. Er zijn al flink wat "kijkers" van deze pagina geweest (ik weet natuurlijk niet of en hoeveel ze gelezen hebben) en nog maar weinig vragen.

    Zelf zou ik bij lezen de volgende vraag hebben:
    Je gebruikt ineens file descriptor 3 in dat laatste voorbeeld. Mag dat zomaar?
    In de man pagina van bash staat dat je 3 t/m 9 zelf mag gebruiken. De shell behoudt zich het recht voor om 10 en hoger te gebruiken en daar moet je dus afblijven.
    Henk van Velden

  5. #5
    Join Date
    Nov 2009
    Location
    Groningen, NL
    Posts
    242

    Default Re: Korte uitleg: File descriptors en redirection in de shell (en pipes)

    Quote Originally Posted by hcvv View Post
    Hoera! Een bergsporter!

    Ja, achter de > staat een bestandsnaam. En 1 is natuurlijk gewoon een bestandsnaam. Die & is echt gebruikt om af te wijken van een (gebruiklijke) bestandsnaam en het de speciale betekenis te geven. Helaas is dat niet geheel waterdicht. Hoewel je zou denken dat een bestandsnaam geen & zou mogen bevatten, of in elk geval er niet mee zou mogen beginnen, is dat niet zo:
    Code:
    henk@boven:~/test/bestanden/gewone file> touch '&'
    henk@boven:~/test/bestanden/gewone file> l
    totaal 8
    drwxr-xr-x 2 henk wij 4096 12 mei 12:33 ./
    drwxr-xr-x 3 henk wij 4096 10 mei 20:45 ../
    -rw-r--r-- 1 henk wij    0 12 mei 12:33 &
    henk@boven:~/test/bestanden/gewone file>
    Maar als je wilt kun je zelfs met redirection een file waarvan de naam met een & begint maken:
    Code:
    henk@boven:~/test/bestanden/gewone file> l aap 2>'&1'
    henk@boven:~/test/bestanden/gewone file> l
    totaal 12
    drwxr-xr-x 2 henk wij 4096 12 mei 12:40 ./
    drwxr-xr-x 3 henk wij 4096 10 mei 20:45 ../
    -rw-r--r-- 1 henk wij   66 12 mei 12:40 &1
    henk@boven:~/test/bestanden/gewone file> cat '&1'
    ls: kan geen toegang krijgen tot aap: Bestand of map bestaat niet
    henk@boven:~/test/bestanden/gewone file>
    - De foutmelding "Bestand of map bestaat niet" lijkt me misleidend, want het bestand is er wel, ook al is het leeg.
    - En natuurlijk komt nu de vraag wat er gebeurt als "2>&1" wordt gebruikt terwijl er al een bestand met de naam &1 is.
    Hans Schreuder

  6. #6
    Join Date
    Jun 2008
    Location
    Netherlands
    Posts
    27,108

    Default Re: Korte uitleg: File descriptors en redirection in de shell (en pipes)

    Quote Originally Posted by hws38 View Post
    - De foutmelding "Bestand of map bestaat niet" lijkt me misleidend, want het bestand is er wel, ook al is het leeg.
    Beter lezen.
    De foutmelding is de in het bestand &1 opgevangen foutmelding van de eerdere ls. En aap bestaat inderdaad niet.
    Het is niet een foutmelding van cat want &1bestaat en is ook niet leeg, want de opgevangen foutmelding staat erin.
    Quote Originally Posted by hws38 View Post
    - En natuurlijk komt nu de vraag wat er gebeurt als "2>&1" wordt gebruikt terwijl er al een bestand met de naam &1 is.
    Ten eerste heb je dit zelf natuurlijk in minder tijd uitgeprobeerd dan dat je hier een nieuwe post maakt.
    Ten tweede, dat maakt niets uit. 2>&1 wordt door de shell eerst geïnterpreteerd, en &1 betekent gewoon "de tabelverwijzing van file descriptor 1".
    '&1' ontsnapt aan die interpretatie omdat het niet met & begint.
    Last edited by hcvv; 12-May-2013 at 05:18.
    Henk van Velden

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •