page header photo

januari 2017 Archieven

Shell scripts en stdin


Geplaatst door hjt op 2017-01-29 19:01:26 | Permanente link | Categorie: Programmeren | Reacties: 0

Wanneer je een shellscript hebt gemaakt dan heb je daarna ruwweg drie verschillende methoden om het te starten (je mag voor ksh ook bash lezen):

    (1)  ksh < scriptnaam
    (2)  ksh   scriptnaam
    (3)  ./scriptnaam   # nadat chmod +x is gedaan

Onder de motorkap wordt de derde methode technisch gelijkgeschakeld aan de tweede: de gestarte scriptshell ontvangt de naam van het script als eerste argument.

In deze blog bekijken we de subtiele gedragsverschillen tussen de eerste en de tweede methode. Die hebben soms grote gevolgen.

Iedere gebruiker van de UNIX-commandotaal moet goed doordrongen zijn van (en kunnen spelen met) het technische verschil tussen "standaard input" en "argument-list", als twee onderscheiden routes waarlangs een shell-vaderproces informatie naar een startend kindproces toesluist (er is zelfs nog een derde route voor informatie van shell-vader naar kind, het "export-shell-environment", maar die speelt in dit blog-verhaal geen rol).

Kijken we nu naar een commandoregel in dat script.

De scriptshell moet aan dat commando (zijn kindproces) koppelingen leveren voor diens stdin, stdout en stderr. Als de betreffende commandoregel in het script zelf geen expliciete I/O-redirectie aanwijzingen >, <, | bevat, dan maakt de scriptshell kopieen van zijn eigen koppelingen en geeft die aan het kindproces/commando.

In de tweede methode hierboven gaat dat zonder complicaties. Die ksh-scriptshell heeft met grote waarschijnlijkheid je keyboard aan zijn stdin hangen, en scherm aan stdout en stderr, en het is geen probleem om die door te koppelen naar het kind.

Maar in de eerste methode heeft de shell aan zijn stdin de scriptfile gekoppeld. Als hij dat zo ook aan zijn kindproces doorgeeft (dat doet hij inderdaad) dan moet hij maar hopen dat dat kind een beetje voorzichtig met de inhoud van de scriptfile omspringt. Dat gaat niet altijd vanzelf goed.

Maak maar eens een script dat als laatste twee regels bevat:

cat - > /tmp/tempfile.$$
echo Nu is het script klaar

Dit script zal zich heel verschillend gedragen, afhankelijk van of je 'm via methode 1) of methode 2) start!

Bij methode 2) merk je dat het cat-commando aan je toetsenbord komt te hangen. Jij typt input data, vervolgens ^D om die cat weg te jagen, en de laatste echo-regel uit het script wordt vervolgens nog netjes uitgevoerd, met z'n output op het scherm. Als je behalve de ^D geen input-data op je toetsenbord hebt getypt dan blijft de tempfile leeg.

Bij methode 1) zie je dat het cat-commando de hele staart van het script opvreet en naar de tempfile schrijft. Voor dit voorbeeld vind je dus achteraf die laatste echo-commandoregel in de tempfile terug (inclusief het woord 'echo' zelf). Het cat-commando stopt bij EOF met het lezen van de scriptfile; de scriptshell komt weer in beweging, merkt dat aan zijn stdin geen data meer zijn overgebleven (die zijn weggesnoept door de cat), en stopt ook. En in elk geval leest die cat niet van het toetsenbord!

Wanneer je in een script commandoregels opneemt die zo expliciet met stdin werken als de cat - in ons voorbeeld, dan voel je meestal wel nattigheid als ksh < script niet doet wat je wilt. Maar er zijn commando's waar de interactie wat subtieler ligt. Het meest berucht is ssh; er zijn flinke ongelukken gebeurd met scripts die een ssh-commando bevatten, en via methode 1) werden gestart. De staart van dat script kwam dan als input terecht bij het commando dat op de remote machine werd gestart. Zo begrijp je achteraf pas waarom ssh een -n vlag heeft. Want als je die vlag van ssh probeert te snappen door de manpage te lezen dan kom je niet ver.