Bash output redirection
- May 1, 2014
- 0
De output van een shell-commando schrijf je weg in een bestand met het groter-dan-teken (>) of gebruik je als input voor een ander commando met het pipe-teken (|). Hier volgen enkele tips om slimmer om te gaan met die zogenaamde output redirection in bash.
1. File descriptors
Bash verwerkt alle in- en output via file descriptors. Standaard worden er drie file descriptors voorzien: 0 voor standard input (stdin), 1 voor standard output (stdout) en 2 voor standard error (stderr). Stdin komt normaal gesproken via je toetsenbord of van een bestand (via <), terwijl stdout en stderr naar je terminal gaan. In de volgende tips bekijken we hoe je stdout en stderr anders afhandelt.
2. Error messages filteren
Sommige commando’s produceren heel wat output, waardoor error messages mogelijk onopgemerkt blijven. In zulke gevallen splits je stdout en stderr best met 1> (of gewoon >) en 2>. Bijvoorbeeld:
$ ./script.sh > stdout.txt 2> stderr.txt
Zo merk je meteen na het uitvoeren van het commando of er fouten zijn opgetreden of niet. Ben je niet geïnteresseerd in één van beide file descriptors, dan redirect je die gewoon naar /dev/null.
Wil je daarentegen stderr op dezelfde manier afhandelen als stdout, gebruik dan 2>&1. Die constructie redirect stderr (2>) naar file descriptor 1 (&1), ofwel stdout:
$ ./script.sh > output.txt 2>&1
Bash kent ook de shortcuts >& output.txt of &> output.txt voor bovenstaande constructie.
3. Eigen foutmeldingen
Om stdout en stderr correct te splitsen, moet het commando in kwestie natuurlijk de juiste file descriptors gebruiken voor normale output en foutmeldingen. In shell scripts definieer je de te gebruiken output filedesciptor met >&. In afbeelding 1 zie je hoe dit precies werkt.
4. Output dupliceren
Soms wil je een bepaalde output stream dupliceren, bijvoorbeeld om foutmeldingen tegelijkertijd op het scherm te tonen en in een logfile weg te schrijven. Bash bevat daarvoor geen builtin functionaliteit, dus moeten we het externe tee-commando gebruiken. De syntax is erg eenvoudig: je redirect stdout van een commando met | naar tee en tee schrijft dit vervolgens weg in een bestand én kopieert het naar stdout. Meestal gebruik je nog de -a-optie bij tee om geen bestaande bestanden te overschrijven. Bijvoorbeeld:
$ ./script.sh | tee -a logfile
5. Stderr en tee
Met een pipe redirect je stdout naar een ander commando. In het voorbeeld uit tip 4 bevat logfile dus enkel stdout, terwijl op het scherm stdout en stderr getoond worden. Wil je stderr ook opnemen in de logfile, gebruik dan ./script.sh 2>&1 | tee -a logfile. Heb je liever aparte bestanden voor stdout en stderr? Het volgende voorbeeld stuurt stdout naar het bestand out, stderr naar err en toont enkel stderr in je terminal:
$ ./script.sh 2>&1 > out | tee -a err
De volgorde van redirection is hier belangrijk: eerst wordt stderr herleid naar stdout om het als input voor tee te gebruiken, maar onmiddellijk daarna wordt stdout herleid naar het bestand out. Draaien we die volgorde om (> out 2>&1), dan zou tee geen input krijgen en komen zowel stdout als stderr in het bestand out terecht.
6. Extra file descriptors
Volstaan stdout en stderr niet om de output van jouw script netjes weg te schrijven in meerdere files? Definieer dan extra file descriptors met het exec-commando. Die kan je achteraf gebruiken, zoals we in tip 3 uitgelegd hebben. Vergeet ook niet om de file descriptor af te sluiten, zodra je hem niet meer nodig hebt. In afbeelding 1 zie je een kort voorbeeld.
7. Output van meerdere commando’s
Als je in een script output moet wegschrijven naar een bepaald bestand, wordt het al snel vervelend om bij elk commando dezelfde redirection te plaatsen. Een eerste mogelijke oplossing is extra file descriptor te definiëren zoals in de vorige tip. Dan hoef je tenminste niet steeds de bestandsnaam te herhalen. Een andere mogelijkheid is om meerdere commando’s te groeperen met accolades ({ … }) en de redirection achter de } te plaatsen, bijvoorbeeld:
$ { date; time-consuming-script.sh; date; } > logfile
Merk op dat elk commando (ook het laatste!) gevolgd moet worden door een puntkomma (;).