Ce sunt stdin, stdout și stderr pe Linux?

În momentul în care inițiezi o comandă în Linux, se creează automat trei canale de date, cunoscute sub numele de stdin, stdout și stderr. Aceste fluxuri te ajută să identifici dacă scripturile tale sunt redirecționate sau dacă li se aplică o altă destinație. În continuare, vom explora modul în care funcționează aceste fluxuri.

Legătura dintre fluxuri

Încă de la primele interacțiuni cu sistemele de operare Linux și Unix, te vei întâlni cu termenii stdin, stdout și stderr. Acestea sunt trei fluxuri standard care sunt activate în momentul execuției unei comenzi Linux. În esență, un flux în informatică este un mediu prin care se pot transfera date. În cazul acestor fluxuri, datele sunt sub formă de text.

Similar unui flux de apă, aceste canale de date au două extremități: o sursă și o destinație. Orice comandă Linux inițiezi va ocupa una dintre extremitățile fluxului. Cealaltă extremitate este stabilită de shell-ul care a lansat comanda. Această extremitate va fi conectată la fereastra terminalului, la o conductă, redirecționată către un fișier sau către o altă comandă, în funcție de instrucțiunile date în linia de comandă.

Fluxurile standard în Linux

În Linux, stdin este canalul standard pentru introducerea datelor. Acesta primește text ca input. Output-ul text de la o comandă către shell se realizează prin intermediul fluxului stdout (ieșire standard). Mesajele de eroare generate de comandă sunt transmise prin fluxul stderr (eroare standard).

Astfel, se observă că există două canale de ieșire, stdout și stderr, și un singur canal de intrare, stdin. Datorită faptului că erorile și ieșirea normală au fiecare propriul lor canal de transport către fereastra terminalului, acestea pot fi gestionate separat.

Fluxurile, tratate ca fișiere

În Linux, fluxurile – la fel ca majoritatea elementelor – sunt tratate ca și cum ar fi fișiere. Poți citi text dintr-un fișier și poți scrie text într-un fișier. Ambele operații implică un flux de date. Așadar, conceptul de a gestiona un flux de date ca fișier nu este deloc forțat.

Fiecărui fișier asociat unui proces i se atribuie un număr unic, denumit descriptor de fișier, pentru a fi identificat. Ori de câte ori este necesară o acțiune asupra unui fișier, descriptorul de fișier este cel care permite identificarea sa.

Următoarele valori sunt utilizate constant pentru stdin, stdout și stderr:

0: stdin
1: stdout
2: stderr

Adaptarea la conducte și redirecționări

Atunci când se introduce o idee nouă, deseori se prezintă o versiune simplificată a conceptului. De exemplu, în gramatică, se afirmă adesea că „i” apare înaintea lui „e”, cu excepția cazului în care urmează după „c”. Cu toate acestea, există mai multe excepții de la regulă decât exemple care o respectă.

Similar, când discutăm despre stdin, stdout și stderr, simplificăm uneori lucrurile, sugerând că unui proces nu îi pasă sau nu este conștient de destinația fluxurilor sale standard. Dar ar trebui oare să îi pese unui proces dacă output-ul său ajunge la terminal sau este direcționat către un fișier? Poate un proces să diferențieze între input-ul venit de la tastatură și cel venit printr-o conductă de la alt proces?

De fapt, un proces este conștient – sau cel puțin poate afla, dacă este programat să verifice – și își poate modifica comportamentul în consecință, în cazul în care dezvoltatorul a implementat această funcționalitate.

Această adaptare a comportamentului este ușor de observat. Încearcă aceste două comenzi:

ls

ls | cat

Comanda `ls` se comportă diferit atunci când output-ul său (stdout) este transmis către o altă comandă. `ls` este cea care trece la un output pe o singură coloană, nu `cat` realizează această conversie. Același lucru se întâmplă și când rezultatul lui `ls` este redirecționat:

ls > capture.txt

cat capture.txt

Redirecționarea fluxurilor stdout și stderr

Existența unui canal dedicat pentru transmiterea mesajelor de eroare prezintă un avantaj semnificativ. Acesta permite redirecționarea output-ului unei comenzi (stdout) către un fișier, în timp ce mesajele de eroare (stderr) continuă să fie vizibile în fereastra terminalului. Această abordare permite o reacție promptă la erori. De asemenea, previne contaminarea fișierului în care este redirecționat stdout cu mesajele de eroare.

Introdu următorul text într-un editor și salvează-l într-un fișier numit `error.sh`:

#!/bin/bash

echo "About to try to access a file that doesn't exist"
cat bad-filename.txt

Transformă scriptul în executabil cu următoarea comandă:

chmod +x error.sh

Prima linie a scriptului trimite text către fereastra terminalului, prin intermediul fluxului stdout. A doua linie încearcă să acceseze un fișier inexistent, generând un mesaj de eroare transmis prin stderr.

Rulează scriptul cu această comandă:

./error.sh

Se observă că ambele fluxuri de ieșire, stdout și stderr, au fost afișate în fereastra terminalului.

Acum, vom încerca să redirecționăm rezultatul către un fișier:

./error.sh > capture.txt

Mesajul de eroare, transmis prin stderr, continuă să fie afișat în fereastra terminalului. Putem verifica conținutul fișierului pentru a confirma că output-ul stdout a ajuns la destinație.

cat capture.txt

Output-ul de la stdin a fost redirecționat către fișier conform așteptărilor.

Simbolul de redirecționare `>` implicit redirecționează stdout. Poți utiliza descriptorii de fișiere numerici pentru a indica exact ce flux de ieșire dorești să redirecționezi.

Pentru a redirecționa explicit stdout, folosește următoarea instrucțiune de redirecționare:

1>

Pentru a redirecționa explicit stderr, folosește această instrucțiune de redirecționare:

2>

Să reluăm testul, folosind de această dată `2>`:

./error.sh 2> capture.txt

Mesajul de eroare este redirecționat, în timp ce mesajul stdout generat de `echo` este afișat în fereastra terminalului:

Mesajul stderr se află în `capture.txt`, conform așteptărilor.

Redirecționarea ambelor fluxuri stdout și stderr

Având posibilitatea de a redirecționa fie stdout, fie stderr către fișiere independente, ne întrebăm dacă am putea redirecționa ambele fluxuri simultan către două fișiere distincte? Răspunsul este afirmativ.

Următoarea comandă va redirecționa stdout către fișierul `capture.txt` și stderr către fișierul `error.txt`.

./error.sh 1> capture.txt 2> error.txt

Deoarece ambele fluxuri de ieșire – ieșirea standard și eroarea standard – sunt redirecționate către fișiere, nu va exista niciun output vizibil în fereastra terminalului. Revenim la promptul liniei de comandă ca și cum nimic nu s-ar fi întâmplat.

Vom verifica acum conținutul fiecărui fișier:

cat capture.txt
cat error.txt

Redirecționarea stdout și stderr către același fișier

După ce am explorat redirecționarea fiecărui flux de ieșire către fișiere dedicate, ne întrebăm ce se întâmplă dacă trimitem atât stdout cât și stderr către același fișier.

Acest lucru se poate realiza cu următoarea comandă:

./error.sh > capture.txt 2>&1

Să analizăm această comandă în detaliu:

`./error.sh`: Inițializează scriptul `error.sh`.
`> capture.txt`: Redirecționează fluxul stdout către fișierul `capture.txt`. `>` este prescurtarea pentru `1>`.
`2>&1`: Utilizează instrucțiunea de redirecționare `&>`. Această instrucțiune permite shell-ului să direcționeze un flux către aceeași destinație ca alt flux. În acest caz, instruim „redirecționează fluxul 2, stderr, către aceeași locație unde este direcționat fluxul 1, stdout”.

Nu există niciun output vizibil, ceea ce este încurajator.

Vom verifica acum fișierul `capture.txt` pentru a vedea ce conține.

cat capture.txt

Atât fluxurile stdout, cât și stderr au fost redirecționate către un singur fișier de destinație.

Pentru ca output-ul unui flux să fie redirecționat și eliminat în tăcere, acesta poate fi direcționat către `/dev/null`.

Detectarea redirecționării într-un script

Am discutat despre capacitatea unei comenzi de a detecta dacă vreunul dintre fluxuri este redirecționat și de a-și ajusta comportamentul în consecință. Putem implementa această funcționalitate și în propriile noastre scripturi? Răspunsul este afirmativ. Tehnica este simplă și ușor de înțeles și aplicat.

Introdu următorul text într-un editor și salvează-l ca `input.sh`:

#!/bin/bash

if [ -t 0 ]; then

  echo stdin coming from keyboard
 
else

  echo stdin coming from a pipe or a file
 
fi

Utilizează următoarea comandă pentru a face scriptul executabil:

chmod +x input.sh

Partea esențială este testul dintre parantezele drepte. Opțiunea `-t` (terminal) returnează adevărat (0) dacă fișierul asociat cu descriptorul se termină în fereastra terminalului. Am folosit descriptorul de fișier 0 ca argument pentru test, care reprezintă stdin.

În cazul în care stdin este conectat la o fereastră de terminal, testul va fi evaluat ca adevărat. Dacă stdin este conectat la un fișier sau la o conductă, testul va eșua.

Putem folosi orice fișier text pentru a oferi input scriptului. În exemplul de față, vom folosi fișierul numit `dummy.txt`.

./input.sh 

Output-ul indică faptul că scriptul recunoaște că input-ul nu provine de la tastatură, ci dintr-un fișier. Dacă se dorește, comportamentul scriptului poate fi modificat corespunzător.

Acesta a fost un exemplu cu redirecționarea unui fișier, acum vom încerca cu o conductă:

cat dummy.txt | ./input.sh

Scriptul recunoaște că input-ul său este introdus prin intermediul unei conducte. Mai exact, scriptul recunoaște că fluxul stdin nu este conectat la o fereastră de terminal.

Acum vom rula scriptul fără conducte sau redirecționări.

./input.sh

Fluxul stdin este conectat la fereastra terminalului, iar scriptul raportează acest lucru corespunzător.

Pentru a verifica același lucru cu fluxul de ieșire, avem nevoie de un nou script. Introdu următorul text într-un editor și salvează-l ca `output.sh`:

#!/bin/bash

if [ -t 1 ]; then

echo stdout is going to the terminal window
 
else

echo stdout is being redirected or piped
 
fi

Utilizează următoarea comandă pentru a face scriptul executabil:

chmod +x input.sh

Singura modificare semnificativă a acestui script este în testul dintre parantezele drepte. Utilizăm numărul 1 pentru a reprezenta descriptorul de fișier pentru stdout.

Îl vom testa. Vom transmite output-ul prin `cat`.

./output | cat

Scriptul recunoaște că output-ul său nu se îndreaptă direct către o fereastră de terminal.

De asemenea, putem testa scriptul prin redirecționarea output-ului către un fișier.

./output.sh > capture.txt

Nu există niciun output în fereastra terminalului, suntem returnați silențios la promptul de comandă, așa cum ne așteptăm.

Putem verifica conținutul fișierului `capture.txt` pentru a vedea ce a fost înregistrat. Utilizăm următoarea comandă pentru acest lucru.

cat capture.sh

Din nou, testul simplu din scriptul nostru detectează că fluxul stdout nu este trimis direct către o fereastră de terminal.

În cazul în care rulăm scriptul fără conducte sau redirecționări, acesta ar trebui să detecteze că stdout este livrat direct în fereastra terminalului.

./output.sh

Și exact asta observăm.

Conștientizarea fluxurilor

Cunoașterea modului de a identifica dacă scripturile tale sunt conectate la fereastra terminalului, la o conductă sau dacă sunt redirecționate, îți oferă posibilitatea de a le ajusta comportamentul în consecință.

Output-ul de înregistrare și diagnosticare poate fi detaliat în funcție de destinația sa, fie că este vorba de ecran sau de un fișier. Mesajele de eroare pot fi înregistrate într-un fișier diferit față de rezultatul normal al programului.

După cum se întâmplă adesea, mai multe cunoștințe oferă mai multe opțiuni.