Linux și terminalul pentru constructori de agenți: de la comandă la sistem reproductibil

Învață Linux și linia de comandă pentru construirea agenților AI: shell, căi, ghilimele și expansiuni, pipe-uri, redirecționări, variabile de mediu, PATH, APT, systemd, SSH și loguri.

Lecția 06102 min de lectură

120–150 de minute · +60–90 de minute pentru exerciții · Nivel: începător · Recomandat înainte: Lecțiile 1–5

Lecția pe scurt

Întrebarea centrală: cum transformi o serie de comenzi tastate într-un terminal într-un mod de lucru pe care îl poți înțelege, verifica, repeta și folosi în siguranță atunci când construiești sau operezi un agent?

În Lecția 5 ai văzut ce se află sub o acțiune aparent simplă: un proces are PID, identitate, grupuri, un folder curent de lucru, descriptori de fișiere și permisiuni. Acum învățăm interfața prin care vei observa și controla toate aceste lucruri în cea mai mare parte a cursului: linia de comandă.

Terminalul nu este „Linux în modul text”, iar shell-ul nu este același lucru cu terminalul. O comandă nu este o propoziție trimisă direct kernelului. Între ceea ce tastezi și procesul care ajunge să ruleze există un interpretor — de obicei un shell — care citește textul, îl descompune, aplică regulile de folosire a ghilimelelor, face expansiuni și redirecționări, caută comanda și abia apoi execută ceea ce ai cerut.

Pentru constructorii de agenți, această distincție este esențială. Un agent de programare, un instrument care pornește subprocese, un serviciu systemd sau o sesiune SSH ajung toate să depindă de aceleași concepte: cale, folder curent, mediu, PATH, intrare și ieșire standard, cod de ieșire, serviciu și log (jurnal de execuție). Dacă nu le înțelegi, poți avea un program care „merge în terminal”, dar eșuează ca serviciu, un script care rulează alt executabil decât crezi, un secret ajuns în istoricul shell-ului sau un lanț de comenzi (pipeline) care pare reușit deși unul dintre pașii lui a eșuat.

Regula lecției

Nu memora comenzi ca pe formule magice. Pentru fiecare comandă trebuie să poți răspunde la cinci întrebări: unde rulează, ce intrare primește, ce proces pornește sau modifică, unde trimite ieșirea și cum afli dacă a reușit.

La finalul lecției vei putea

  1. să deosebești terminalul, shell-ul, linia de comandă și promptul shell-ului;
  2. să explici diferența dintre o cale absolută și una relativă și rolul lui /, ~, . și ..;
  3. să explici cum găsește shell-ul o comandă și ce rol are variabila PATH;
  4. să folosești corect ghilimelele, expansiunea variabilelor, expansiunea numelor de fișiere (globbing) și substituția de comandă;
  5. să diferențiezi stdin, stdout și stderr și să urmărești redirecționările;
  6. să explici un pipeline și să nu confunzi textul afișat cu succesul tehnic al întregului lanț;
  7. să folosești codurile de ieșire și operatorii && / || pentru a exprima dependențe între comenzi;
  8. să deosebești variabilele shell-ului de variabilele de mediu și să explici de ce source .venv/bin/activate modifică shell-ul curent;
  9. să explici diferența dintre APT, apt, apt-get și dpkg;
  10. să deosebești start, enable, restart, reload și daemon-reload în systemd;
  11. să citești logurile unui serviciu cu journalctl și să explici de ce logul nu este același lucru cu o urmă de execuție (trace);
  12. să explici arhitectura de bază SSH: client, server, cheie de gazdă (host key), cheie de utilizator, known_hosts și authorized_keys;
  13. să diagnostichezi de ce un program funcționează într-un terminal interactiv, dar eșuează când este pornit ca serviciu sau prin SSH;
  14. să proiectezi un spațiu de lucru pentru linia de comandă care poate fi recreat și înțeles de altcineva.
Schema „De la comandă la efect real”: utilizatorul tastează în aplicația de terminal, shell-ul Bash interpretează structura, ghilimelele și expansiunile, apoi pornește un builtin sau un executabil căutat în PATH ca proces Linux, cu stdin, stdout și stderr trimise către fișiere, alte procese, servicii sau rețea.
Drumul unei comenzi: de la textul tastat la procesul cu fluxuri și destinații reale.

1. Terminalul, shell-ul și linia de comandă nu sunt același lucru

Începem cu vocabularul care va reveni în aproape toate lecțiile tehnice următoare.

O aplicație de terminal9 (emulator de terminal) este programul care îți oferă fereastra, textul, cursorul, combinațiile de taste și legătura cu o sesiune de terminal. Konsole, GNOME Terminal și alte aplicații similare oferă interfața de terminal. Ele nu decid singure ce înseamnă cd, export sau |.

Un shell8 este interpretorul de comenzi. În exemplele acestei lecții folosim Bash, un shell foarte răspândit pe Ubuntu și Kubuntu. Alte sisteme pot folosi Zsh, Fish sau alte shell-uri, iar unele reguli de sintaxă diferă. De aceea este mai precis să spui „aceasta este sintaxă Bash” decât „așa funcționează terminalul9”.

Linia de comandă (command line) este modul de interacțiune în care formulezi comenzi textuale. Poți folosi o linie de comandă local, prin SSH25, într-un container, într-o consolă serială sau într-o sarcină automatizată. Nu presupune neapărat o fereastră grafică.

Promptul2 shell-ului8 este textul afișat înainte să tastezi comanda. În Bash, promptul principal este definit de variabila PS1. Nu îl confunda cu un prompt pentru un model lingvistic din Lecțiile 1–2. Sunt două sensuri diferite ale aceluiași cuvânt englezesc.

Continuitate cu Lecția 4: aici, shell este termenul generic pentru interpretorul de comenzi, precum Bash. OpenShell, întâlnit în Lecția 4, este numele unui produs pentru izolare și aplicarea politicilor asupra execuției agenților. Numele seamănă, dar conceptele nu sunt sinonime.

O analogie scurtă: pupitrul de operare

Poți privi terminalul9 ca pe un pupitru cu ecran și tastatură, iar shell-ul8 ca pe operatorul care interpretează instrucțiunile primite prin acel pupitru. Operatorul decide cum se separă cuvintele, ce înseamnă simbolurile speciale, unde găsește programul cerut și cum leagă ieșirea unei comenzi de intrarea alteia. Kernelul și procesele rămân mecanismele care execută operațiile reale.

Continuitate cu Lecția 5: când shell-ul8 pornește un program extern, apare un proces. Acel proces primește o identitate, un folder curent, descriptori standard și un mediu. Linia de comandă nu anulează nimic din ceea ce ai învățat despre procese și permisiuni.

2. Vocabularul minim al liniei de comandă

Nu ai nevoie de sute de comenzi. Ai nevoie mai întâi de o hartă a obiectelor și a fluxurilor pe care comenzile le manipulează.

TermenDefiniție de lucruLegătura cu lecțiile anterioare
shell8program care interpretează limbajul de comandă și pornește comenzi sau proceseeste componenta care lansează și coordonează direct comenzile pornite manual
builtincomandă implementată în shell8, nu neapărat un executabil separatcd trebuie să modifice chiar shell-ul8 curent
executabilprogram pe care sistemul îl poate pornipornirea lui produce un proces
argumentvaloare transmisă unei comenzidevine parte din argumentele procesului
opțiuneargument interpretat de program ca modificator de comportamentforma exactă aparține programului, nu shell-ului8
CWD16folderul curent de lucru al procesului (current working directory)ai întâlnit deja CWD16 în anatomia procesului din Lecția 5
cale absolutăcale pornită de la rădăcina sistemului de fișiere, /nu depinde de CWD16
cale relativăcale interpretată pornind din CWD16poate indica alt obiect dacă procesul pornește din alt folder
stdinintrarea standard, descriptorul 0unul dintre descriptorii standard ai procesului
stdoutieșirea standard, descriptorul 1poate merge în terminal9, fișier sau pipe13
stderrieșirea standard pentru diagnostic și erori, descriptorul 2este un flux separat de stdout11
pipe13canal (pipe13) care leagă ieșirea unei comenzi de intrarea alteiaeste un mecanism de comunicare între procese
redirecționareregulă prin care shell-ul8 leagă descriptorii de fișiere sau de alți descriptorimodifică traseul I/O înainte de execuția comenzii
cod de ieșire14număr prin care comanda indică succesul sau eșeculpoate fi folosit de alt software pentru control determinist
variabilă shell8nume și valoare existente în shell8nu este transmisă automat proceselor-copil
variabilă de mediu17variabilă transmisă, prin export, în mediul proceselor pornite ulteriorface parte din mediul procesului
PATHlistă ordonată de foldere în care shell-ul8 caută executabile atunci când numele comenzii nu conține o caleinfluențează ce executabil ajunge să fie pornit
serviciu22proces sau ansamblu de procese administrat ca funcție de sistemrulează cu propria identitate și propriul mediu
unitate systemd21obiect gestionat de systemd21: service, socket, timer, mount etc.descrie o resursă sau un serviciu22 administrat de systemd21
log (jurnal23 de execuție)înregistrare a unor evenimente produse de sistem sau aplicațieîn Lecția 3 l-ai separat de metrici, urme de execuție (traces), teste și evaluări
SSH25protocol și suită de instrumente3 pentru acces securizat la sisteme la distanțămută execuția pe alt sistem, fără să schimbe regulile de bază ale proceselor
MINI-GLOSAR · termeni pe care îi vei întâlni mai târziu
  • expansiune18 — înlocuire făcută de shell8 înainte de execuție, de exemplu $HOME cu valoarea variabilei;
  • TTY — interfață de terminal9 asociată unei sesiuni; numele vine din istoria teletipurilor;
  • grup de procese — mai multe procese pe care shell-ul8 le poate trata împreună, de exemplu într-un pipeline29;
  • PID 1 — primul proces din spațiul de procese al sistemului; pe Ubuntu obișnuit acesta este systemd21;
  • boot / pornirea sistemului — secvența prin care calculatorul ajunge de la pornire la un sistem Linux funcțional;
  • index de pachete — catalog local cu informații despre pachetele disponibile din sursele APT28;
  • amprentă (fingerprint) — rezumat scurt al unei chei criptografice, folosit pentru verificare;
  • alias — scurtătură definită în shell8, de exemplu alias ll='ls -l';
  • funcție de shell8 — un grup de comenzi cu un nume, definit chiar în shell și apelat ca o comandă.

3. Ce face Bash cu textul înainte să pornească programul

Multe dintre surprizele shell-ului8 apar înainte ca programul pe care îl chemi să înceapă să ruleze.

Cheia acestei secțiuni: programul extern nu primește neapărat textul exact pe care l-ai tastat. Bash interpretează mai întâi sintaxa și construiește argumentele, redirecționările și contextul execuției.

Ia o comandă simplă:

grep -i "$TERMEN" logs/*.log

Nu toate caracterele ajung neschimbate la grep. Bash trebuie mai întâi să interpreteze linia. Pentru un începător este util următorul model mental. Nu este o listă exactă a tuturor etapelor interne din Bash, dar păstrează ordinea conceptuală care te ajută să prezici rezultatul:

Shell-ul recunoaște structuraSepară cuvintele și operatorii precum pipe sau redirecționare.
Ține cont de ghilimele și caracterele protejateGhilimelele fac parte din sintaxa pe care Bash o recunoaște și influențează ce expansiuni sunt permise; caracterele de ghilimele sunt eliminate ulterior înainte ca programul extern să primească argumentele.
Face expansiunileDe exemplu $TERMEN, ~, $(...) și modele de nume de fișiere.
Aplică redirecționărileLeagă descriptorii standard conform operatorilor din comandă.
Stabilește ce comandă trebuie executatăAliasurile sunt tratate mai devreme, la citirea comenzii; aici Bash poate ajunge la o funcție, o comandă internă (builtin) sau un executabil găsit prin PATH.
ExecutăUn builtin poate rula în shell; un program extern este pornit ca proces.
Primește codul de ieșireShell-ul îl poate folosi pentru &&, ||, scripturi și automatizare.

Exemplu concret. Să presupunem că TERMEN conține textul eroare gravă, iar în folderul logs există a.log și b.log. După ce Bash termină interpretarea, grep primește patru argumente: -i, eroare gravă, logs/a.log și logs/b.log. Ghilimelele au dispărut, $TERMEN a fost înlocuit cu valoarea lui, iar logs/*.log a devenit lista fișierelor găsite.

Fiecare pas este explicat mai jos: căutarea comenzii în secțiunea 5, ghilimelele și expansiunile în secțiunea 6, redirecționările în secțiunea 8, codul de ieșire în secțiunea 9.

Această ordine explică de ce două comenzi care arată aproape la fel pot avea efecte diferite. Programul extern vede argumentele după ce shell-ul8 a terminat o parte importantă din interpretare.

Modelul mental al interpretării unei comenzi de Bash în șapte pași: structura, ghilimelele, expansiunile, redirecționările, rezolvarea comenzii în PATH, execuția și codul de ieșire, programul primind argumentele după interpretarea shell-ului.
Bash interpretează textul în șapte pași înainte ca programul să primească argumentele.

4. Căile: cum spune un proces „unde”

Lecția 5 a arătat că accesul depinde de obiect și de folderele traversate. Acum învățăm să descriem acea poziție fără ambiguitate.

Sistemul de fișiere Linux are o rădăcină reprezentată prin /. O cale absolută pornește de la această rădăcină:

/home/ana/ai-agents-zero-to-expert/configs/app.json

O cale relativă este interpretată față de folderul curent de lucru:

configs/app.json
/
├── home/
│   └── ana/
│       └── ai-agents-zero-to-expert/   ← CWD când lucrezi în proiect
│           └── configs/
│               └── app.json
└── tmp/                                ← CWD dacă programul pornește de aici

Dacă CWD16 este /home/ana/ai-agents-zero-to-expert, cele două pot indica același obiect. Dacă procesul pornește din /tmp, calea relativă indică alt loc sau nu există.

Patru simboluri pe care trebuie să le citești fără ezitare

SimbolÎnțelesExemplu
/rădăcina sistemului de fișiere când apare la începutul căii/var/log
~în Bash, expansiune18 către folderul personal al utilizatorului~/project
.folderul curent./script.sh
..folderul părinte../configs

Consecința practică: ~ funcționează când tastezi în Bash, dar un program care primește textul ~/project, de exemplu dintr-un fișier de configurare, nu îl transformă automat în folderul tău personal.

Folderul curent face parte din starea procesului

pwd afișează folderul curent, iar cd îl schimbă pentru shell-ul8 curent. cd este un builtin important tocmai pentru că un program extern nu poate schimba permanent CWD-ul16 procesului-părinte. Dacă ai porni un executabil separat care își schimbă propriul folder curent, schimbarea s-ar aplica acelui proces, nu shell-ului care l-a pornit.

Aceeași regulă explică multe diferențe între rularea manuală și rularea ca serviciu22. Un program care deschide ./config.json depinde de CWD16. Un serviciu poate porni din alt folder decât terminalul9 tău, deci o cale relativă care „merge la mine” poate eșua imediat.

Regulă pentru agenți1: pentru resurse critice, nu presupune CWD-ul16. Definește explicit rădăcina proiectului sau construiește căile în mod controlat în aplicație.

Din Lecția 5 știi deja că o legătură simbolică (symbolic link) poate indica altă cale. În terminal9, comenzi precum readlink și realpath te ajută să vezi legătura sau să normalizezi o cale. Pentru un agent1 care scrie sau șterge fișiere, diferența contează: validarea unui șir de caractere nu este întotdeauna echivalentă cu validarea obiectului final pe care îl va atinge operația.

5. Comandă, argumente, opțiuni și PATH

Shell-ul8 trebuie să răspundă la două întrebări diferite: „ce argumente îi dau programului?” și „ce program înseamnă primul cuvânt?”.

În comanda:

ls -lah docs/lessons

ls este numele comenzii, iar -lah și docs/lessons sunt argumente. Faptul că -lah poate fi interpretat de ls ca mai multe opțiuni scurte grupate este o regulă a programului ls, nu o regulă universală a shell-ului8.

Înainte de execuție, Bash poate extinde un alias încă din etapa de citire a comenzii. După ce comanda a fost analizată, dacă numele nu conține /, Bash caută în mod simplificat în această ordine: funcție de shell8, comandă internă (builtin), apoi executabil în PATH. Două comenzi utile sunt:

type cd
command -v python
cd is a shell builtin
/home/ana/ai-agents-zero-to-expert/.venv/bin/python

A doua linie este un exemplu după activarea mediului virtual într-un proiect aflat în folderul personal al utilizatorului ana; calea ta va fi diferită.

type explică ce vede Bash, iar command -v este potrivit pentru a verifica programatic cum va fi rezolvat un nume de comandă.

Cum afli singur ce face o comandă: man ls deschide manualul complet (ieși cu tasta q), ls --help afișează rezumatul opțiunilor; help cd explică o comandă internă Bash. Când nu știi o opțiune, citește ajutorul înainte s-o cauți pe internet.

Cum funcționează PATH

PATH este o listă de foldere separate prin :. Când tastezi un nume de comandă fără slash, Bash caută un executabil potrivit în aceste foldere, în ordine. De aceea activarea unui mediu virtual Python poate face ca python să indice alt executabil fără să schimbi comanda tastată.

ANALOGIE · PATH ca o listă de sertare

Imaginează-ți că PATH este o listă de sertare pe care shell-ul8 le deschide în ordine. Când ceri python, shell-ul caută în primul folder, apoi în al doilea și tot așa. Primul executabil găsit „câștigă”. De aceea ordinea din PATH schimbă programul care pornește.

Un detaliu important: o componentă goală din PATH poate reprezenta folderul curent. Din motive de siguranță, nu te baza pe această formă.

printf '%s\n' "$PATH"
command -v python

Dacă numele conține slash, de exemplu ./tool sau /usr/bin/python3, ai indicat deja calea și nu mai depinzi de căutarea obișnuită prin PATH.

Ordinea din PATH15 este parte din suprafața de securitate. Dacă un folder controlat de altcineva este căutat înaintea folderelor de sistem, un executabil local cu un nume familiar poate fi pornit în locul programului pe care îl așteptai. Nu adăuga la întâmplare folderul curent la începutul PATH-ului.
Cum găsește shell-ul comanda python: caută în ordine folderele din PATH, precum .venv/bin, /usr/local/bin, /usr/bin și /bin, și pornește prima potrivire utilizabilă, în timp ce o cale explicită precum ./python nu mai depinde de căutarea în PATH.
Ordinea din PATH15 decide ce executabil pornește; o cale cu slash scutește căutarea.

6. Ghilimele și expansiuni: shell-ul vede mai mult decât textul afișat

Când o comandă primește fișiere, variabile sau date de intrare generate de agent1, folosirea corectă a ghilimelelor (quoting) devine o regulă de corectitudine și securitate, nu un detaliu estetic.

Ghilimele simple, ghilimele duble și backslash

În Bash, ghilimelele simple păstrează conținutul literal:

printf '%s\n' '$HOME'

Comanda va afișa $HOME, nu valoarea variabilei.

$HOME

Ghilimelele duble permit în continuare expansiuni18 precum $VAR și $(...), dar protejează spațiile și multe caractere de separare:

printf '%s\n' "$HOME"
cd "$HOME/Proiect cu spații"
/home/ana

În exemplu presupunem că utilizatorul se numește ana. Comanda cu "$HOME" afișează valoarea variabilei, de exemplu /home/ana. Comanda cd nu afișează nimic dacă folderul există. Dacă folderul nu există, Bash afișează o eroare de tip „No such file or directory”.

Ce se întâmplă fără ghilimele:

cd $HOME/Proiect cu spații
bash: cd: too many arguments

Bash a împărțit valoarea în trei cuvinte: /home/ana/Proiect, cu și spații. Comanda cd a primit trei argumente în loc de unul.

Backslash-ul \ poate proteja caracterul următor în anumite contexte. Pentru valori și căi, ghilimelele sunt de obicei mai ușor de citit decât o serie lungă de backslash-uri.

Expansiunea numelor de fișiere (globbing): * nu este filtrul programului

În:

ls logs/*.log

modelul *.log este, în mod obișnuit, expandat de Bash înainte să pornească ls. Programul primește lista de nume rezultată. Această distincție devine importantă când modelul provine dintr-o variabilă sau când numele fișierelor conțin spații.

Dacă logs conține a.log și b.log, ls primește logs/a.log și logs/b.log. Dacă nu există niciun fișier .json, ls logs/*.json primește chiar textul logs/*.json și răspunde că nu îl găsește.

În configurația implicită Bash, un model glob fără potriviri poate rămâne text literal; opțiunile shell-ului8 pot schimba acest comportament. De aceea nu construi logică critică presupunând că *.json devine întotdeauna o listă nevidă.

Substituția de comandă $(...)

Expresia:

ROOT="$(pwd)"

rulează pwd și folosește ieșirea sa standard ca valoare. Ai folosit deja această tehnică în laboratoare. Ghilimelele din jurul expresiei păstrează rezultatul ca un singur argument chiar dacă ar conține spații. Bash elimină însă liniile noi de la finalul rezultatului substituției de comandă20; de aceea $(...) nu este o metodă potrivită pentru a păstra identic orice flux de octeți.

Regulă practică: când o variabilă reprezintă o singură cale, un singur nume sau un singur argument, folosește în mod normal "$VAR", nu $VAR. Expansiunea18 neprotejată poate permite separarea în mai multe cuvinte și expandarea neintenționată a numelor de fișiere.

7. Trei fluxuri standard: stdin, stdout și stderr

În Lecția 5 ai întâlnit descriptori de fișiere. Aici folosim cei trei descriptori pe care aproape orice proces de linie de comandă îi primește la pornire.

DescriptorNumeRol uzual
0stdindate de intrare
1stdoutrezultat normal
2stderrdiagnostic și erori

Într-o sesiune interactivă, toate trei pot fi conectate la terminal9. Asta face să pară că „totul este același text”. Nu este. Shell-ul8 poate conecta fiecare descriptor separat.

De exemplu, un program poate scrie un rezultat JSON pe stdout11 și un mesaj de progres pe stderr12. Alt program poate consuma stdout fără ca mesajele de diagnostic să intre în date.

Legătura cu agenții1: când un instrument3 returnează date structurate prin stdout11 și erori prin stderr12, controllerul6 poate trata cele două fluxuri diferit. Dacă le amesteci fără motiv, devine mai greu să separi rezultat, diagnostic și eșec.
Cele trei fluxuri standard ale unui proces Linux: stdin, descriptorul 0, ca intrare, stdout, descriptorul 1, pentru rezultatul normal și stderr, descriptorul 2, pentru diagnostic, fiecare putând ajunge la terminal, în fișier sau într-un pipe către alt proces.
Trei descriptori, trei fluxuri separate: intrarea, rezultatul și diagnosticul.

8. Redirecționări și pipe-uri: compunerea comenzilor

Puterea liniei de comandă nu vine numai din numărul de utilitare, ci din faptul că le poți compune prin fluxuri bine definite.

Redirecționările mută descriptorii

Operatorul > redirecționează stdout11 către un fișier și, în mod obișnuit, îl creează sau îl golește înainte să scrie:

În exemplele de mai jos, comanda_ta ține locul oricărei comenzi. Nu tasta cuvântul command: în Bash este chiar o comandă internă, cu alt rol.

comanda_ta > output.txt

>> adaugă la final:

comanda_ta >> output.txt

2> redirecționează stderr12:

comanda_ta 2> errors.txt

Pentru a trimite ambele fluxuri în același loc:

comanda_ta > all.txt 2>&1

Ordinea contează. Bash aplică redirecționările de la stânga la dreapta. 2>&1 înseamnă „fă descriptorul 2 să indice unde indică descriptorul 1 în acel moment”. De aceea mutarea operatorilor poate schimba rezultatul. Comparația: comanda_ta > all.txt 2>&1 trimite ambele fluxuri în fișier. comanda_ta 2>&1 > all.txt trimite stdout11 în fișier, dar stderr12 rămâne pe ecran, pentru că în momentul în care Bash a aplicat 2>&1, descriptorul 1 încă indica terminalul9.

Pipe-ul transmite stdout către stdin

Continuitate cu Lecția 1: acolo, pipeline era folosit în sens larg pentru o succesiune de etape prin care trec datele. În Bash, un pipeline are un sens mai precis: un lanț de comenzi conectate prin operatori precum | sau |&. În această secțiune folosim sensul Bash.
printf '%s\n' error ok error | sort | uniq -c
      2 error
      1 ok

Ieșirea standard a primei comenzi intră în a doua, iar ieșirea celei de-a doua intră în a treia. Un pipe13 Bash obișnuit | nu include automat stderr12. Bash are și operatorul |&, care conectează stdout11 și stderr ale comenzii din stânga la stdin-ul10 următoarei, dar este mai clar să îl folosești numai când chiar vrei ambele fluxuri.

Un utilitar precum tee poate copia același flux spre un fișier și, simultan, spre stdout11:

comanda_ta | tee run.log

Este util pentru observație, dar nu transforma orice pipeline29 într-un sistem de logare. Serviciile au mecanisme dedicate, iar logurile pot conține date sensibile.

Un pipe13 poate transporta și cod, nu doar date. O construcție care descarcă un script de pe internet și îl trimite direct într-un shell8 execută conținutul înainte să îl poți revizui. Pentru software necunoscut, descarcă, inspectează, verifică sursa și abia apoi execută.

9. Codul de ieșire: cum comunică o comandă succesul

Textul afișat este pentru om; codul de ieșire este semnalul simplu pe care îl poate folosi alt software.

Prin convenție, 0 indică succes, iar o valoare nenulă indică un tip de eșec sau o stare4 specială definită de program.

ls fisier-inexistent
printf '%s\n' "$?"
ls: cannot access 'fisier-inexistent': No such file or directory
2

Mesajul exact de eroare poate varia în funcție de limba sistemului. Pe Ubuntu, GNU ls folosește codul 2 pentru o eroare serioasă de acest tip.

$? conține codul ultimei comenzi sau al ultimei structuri evaluate de shell8. Trebuie citit imediat; următoarea comandă îl înlocuiește.

Operatorii &&, || și ;

OperatorComportamentUtilizare
A && Brulează B numai dacă A a reușitnu continua dacă verificarea a eșuat
A || Brulează B dacă A a eșuatvariantă de rezervă sau mesaj de eroare
A ; Brulează B indiferent de codul lui Asecvență în care pașii nu depind unul de altul

Pentru automatizări, && exprimă intenția mai bine decât două comenzi independente atunci când pasul al doilea depinde de primul.

Capcana lanțului de comenzi (pipeline)

În Bash, fără opțiunea pipefail, codul de ieșire al unui pipeline29 este în mod normal codul ultimei comenzi. Asta înseamnă că prima comandă poate eșua, iar ultima poate ieși cu 0.

În scripturi în care succesul întregului lanț contează, poți activa:

set -o pipefail

Cu pipefail, un eșec din pipeline29 poate produce un cod nenul pentru întregul pipeline: Bash folosește codul ultimei comenzi din lanț care a ieșit cu o valoare nenulă. Dacă toate comenzile reușesc, rezultatul este 0.

Nu există o opțiune magică pentru scripturi „sigure”. Setări precum pipefail și set -e schimbă semantica shell-ului8 și trebuie înțelese și testate. În laborator vom folosi explicit numai comportamentele pe care le putem explica.

10. Variabile shell, variabile de mediu și export

Nu confunda două sensuri ale cuvântului „mediu”. În Lecția 2, mediul agentului însemna lumea externă cu care agentul poate interacționa. Aici, mediul procesului (environment) este colecția de perechi nume=valoare transmisă procesului, de exemplu PATH sau LOG_LEVEL. Sunt două concepte diferite.

Multe configurații ale agenților1 — adrese de serviciu22 (endpointuri), nivel de logare, căi, comutatoare de funcționalități (feature flags) — ajung în mediu. Dar „este într-o variabilă” nu ne spune încă dacă un subproces o va primi.

În Bash:

MODEL_URL="http://127.0.0.1:8000"

creează o variabilă a shell-ului8. Pentru a o include în mediul proceselor executate ulterior:

export MODEL_URL

sau direct:

export MODEL_URL="http://127.0.0.1:8000"

Poți furniza o variabilă numai pentru o singură comandă:

LOG_LEVEL=debug comanda_ta

În acest caz, valoarea este disponibilă comenzii executate, fără să devină neapărat o setare permanentă pentru comenzile următoare.

Mediul se transmite către subprocese (procese-copil), nu înapoi către părinte

Un subproces (proces-copil) primește la pornire mediul exportat de procesul-părinte. Dacă procesul-copil își schimbă propriul mediu, acea schimbare nu rescrie mediul părintelui.

ANALOGIE · mediul ca un rucsac

Procesul-părinte îi dă procesului-copil un „rucsac” cu variabilele exportate. Copilul poate schimba ce are în propriul rucsac, dar nu poate pune acele schimbări înapoi în rucsacul părintelui.

Aceasta explică de ce mediul virtual Python este activat astfel:

source .venv/bin/activate

source este builtin Bash și execută fișierul în shell-ul8 curent. Scriptul de activare modifică PATH și alte variabile ale acelei sesiuni. Dacă l-ai porni într-un proces separat, schimbările de mediu nu s-ar întoarce în shell-ul părinte.

Un fișier .env nu este încă o variabilă de mediu

Este o confuzie frecventă în proiectele web și AI. Bash nu încarcă automat un fișier numit .env doar pentru că acesta există. Anumite framework-uri și biblioteci îl citesc explicit. Alte aplicații nu îl cunosc deloc.

Prin urmare, propoziția „cheia este în .env” nu răspunde la întrebarea „cum ajunge cheia în proces?”. Trebuie să știi cine citește fișierul, când îl citește și cu ce permisiuni.

Variabila de mediu nu este un seif de secrete. Este un mecanism de configurare și transport al valorilor către procese. Secretele pot ajunge accidental în ieșiri de depanare, capturi de memorie (dump-uri), loguri sau instrumente3 de inspecție. În producție vei folosi mecanisme de gestionare a secretelor și privilegiul minim din Lecția 5, nu simpla presupunere că „dacă este într-o variabilă de mediu17, este sigur”.
Ce moștenește procesul-copil: shell-ul părinte are variabile locale și exportate, copilul primește doar mediul exportat, schimbările lui nu urcă în părinte, iar comanda source rulează un script chiar în shell-ul curent și îi poate modifica mediul.
Copilul moștenește doar variabilele exportate și nu poate modifica mediul părintelui.

11. Managerii de pachete: instalează software-ul în stratul potrivit

În Lecția 3 ai separat runtime-ul, framework-ul și aplicația. Managerii de pachete respectă aceeași idee: nu toate pachetele aparțin aceluiași strat și nu toate trebuie instalate cu același instrument3.

APT și dpkg pe Ubuntu/Kubuntu

APT28 este sistemul de administrare a pachetelor Debian folosit de Ubuntu. Comanda apt oferă o interfață comodă pentru utilizator:

sudo apt update
sudo apt install jq
apt show jq

apt update actualizează indexul local al pachetelor disponibile. Nu actualizează pachetele deja instalate. apt upgrade este operația care încearcă să actualizeze pachetele instalate în limitele politicii APT28.

Documentația Ubuntu recomandă apt pentru utilizare interactivă, iar apt-get pentru scripturi și automatizări unde este de preferat o interfață mai stabilă. Pentru multe operații de bază, comenzile seamănă, dar scopul interfețelor nu este identic.

dpkg lucrează la un nivel mai jos cu pachetele Debian locale și baza de date a pachetelor. Poate instala un fișier .deb, dar nu are singur comportamentul APT28 de a descărca și rezolva automat toate dependențele din surse de pachete.

apt

Interfață orientată către utilizator pentru surse de pachete, instalare, actualizare și inspecție.

apt-get

Interfață APT28 potrivită pentru scripting și automatizare.

dpkg

Operații asupra pachetelor Debian locale și bazei dpkg; nu înlocuiește rezolvarea dependențelor din surse de pachete.

Pachet de sistem versus pachet al limbajului

Mai târziu, în Lecția 7, vei folosi managerul de pachete al ecosistemului Python. Asta nu înseamnă că APT28 și pip sunt două nume pentru același lucru. APT administrează pachete integrate în sistemul Ubuntu; pip administrează distribuții Python într-un mediu Python.

Pentru proiectele reproductibile, regula sănătoasă este să alegi conștient stratul: instrument3 de sistem prin managerul sistemului, dependență Python în mediul Python, dependență Node în proiectul Node etc. Evită să rezolvi orice eroare de instalare prin creșterea privilegiilor.

12. Prim-plan, fundal și sarcini: cum organizează shell-ul procesele pornite de tine

Lecția 5 a tratat procesul ca obiect al sistemului de operare. Aici adăugăm perspectiva shell-ului8 interactiv.

Același cuvânt, alt mecanism: în Lecția 1 ai întâlnit expresia job cron, adică o sarcină programată să pornească la un anumit moment. În expresia job control din Bash, job înseamnă o sarcină urmărită de shell-ul interactiv. Cele două folosesc același cuvânt englezesc, dar nu sunt același mecanism.

În mod normal, când pornești o comandă în prim-plan (foreground), shell-ul8 așteaptă terminarea ei înainte să îți redea promptul2. Dacă adaugi &, o pornești ca sarcină în fundal (background) al shell-ului:

long_command &

jobs arată sarcinile urmărite de shell-ul8 curent, iar fg și bg le pot muta între prim-plan și fundal. Identificatorul unei sarcini de shell nu este același lucru cu PID-ul: o singură sarcină poate cuprinde unul sau mai multe procese.

Fundalul (background) nu înseamnă serviciu22. Un proces pornit cu & este doar o sarcină a sesiunii shell8. Pentru procese care trebuie administrate, repornite și pornite controlat la pornirea sistemului (boot), un manager de servicii precum systemd21 este instrumentul3 potrivit.

13. systemd: de la proces lansat manual la serviciu administrat

Pe Ubuntu și Kubuntu actuale, systemd21 este managerul de sistem și servicii22. Ca manager de sistem, rulează ca PID 1; există și instanțe systemd pentru servicii ale utilizatorului.

systemd21 nu administrează doar servicii22. Conceptul general este unitatea (unit): un obiect precum .service, .socket, .timer, .mount sau .target. În această lecție ne concentrăm pe unități de tip .service.

Pornirea (start) nu este același lucru cu activarea (enable)

ComandăCe faceCe NU înseamnă
systemctl start app.servicepornește unitatea acumnu o configurează automat pentru activare viitoare
systemctl stop app.serviceoprește unitatea acumnu îi schimbă automat configurația de activare
systemctl enable app.servicecreează legăturile definite de configurația de instalare pentru activare viitoarenu pornește automat unitatea în acel moment
systemctl enable --now app.serviceconfigurează activarea și pornește unitatea acumnu dovedește că aplicația este configurată corect
systemctl restart app.serviceoprește și pornește din nou unitateanu este același lucru cu reload
systemctl reload app.servicecere serviciului să își reîncarce propria configurație, dacă suportă operațianu recitește fișierul de definiție al unității systemd21
systemctl daemon-reloadcere managerului systemd21 să recitească definițiile unitățilornu reîncarcă automat configurația internă a aplicației

Această separare este explicită în documentația systemd21: enable și start sunt operații distincte. Un serviciu22 poate fi pornit acum, dar disabled pentru activare la pornirea sistemului (boot); poate fi enabled, dar momentan oprit.

Comanda status oferă o fotografie de moment, nu un diagnostic complet

systemctl status app.service

arată starea4 unității și câteva informații recente. Este un punct bun de început, dar nu este un substitut pentru loguri, fișierul de definiție al unității și configurația aplicației.

Pentru servicii22 ale utilizatorului există și:

systemctl --user status app.service

Acesta comunică cu instanța systemd21 a utilizatorului, nu cu managerul de sistem. Separarea contează pentru identitate, permisiuni și locurile din care sunt încărcate unitățile.

systemd: starea acum, pornită sau oprită prin start și stop, este separată de activarea la pornire, enabled sau disabled prin enable și disable, enable --now le atinge pe ambele, iar reload reîncarcă configurația aplicației, diferit de daemon-reload, care recitește definițiile unităților.
Starea4 curentă a serviciului și activarea lui la pornire sunt două axe separate.

14. Logurile: urmărește ce s-a întâmplat, nu ghici

În Lecția 3 ai separat logurile de metrici, urmele de execuție (traces), teste și evaluări. Acum folosim logurile sistemului pentru a vedea ce a făcut un serviciu22.

Jurnalul23 (journal) este colecția de loguri pe care o păstrează journald.

systemd21-journald colectează mesaje din mai multe surse, inclusiv mesaje ale kernelului, syslog și stdout11/stderr12 ale serviciilor systemd. journalctl interoghează acest jurnal23 structurat.

Comenzi de orientare:

journalctl -u app.service
journalctl -u app.service -n 50
journalctl -u app.service -f
journalctl -u app.service --since "10 minutes ago"
journalctl -b
journalctl -p err

-u filtrează după unitate; -f urmărește intrările noi; -b limitează rezultatul la o pornire a sistemului (boot); --since restrânge intervalul; -p filtrează după prioritate.

Logurile pot fi persistente sau volatile

De aceea nu presupune că „journalctl24 are sigur logurile de ieri”. Mai întâi verifică politica de stocare și perioada pentru care există date.

Log versus trace

Un log consemnează un eveniment: serviciul22 a pornit, un fișier lipsește, o cerere a eșuat. O urmă de execuție (trace) leagă pașii aceleiași operații prin mai multe componente. Un agent1 poate avea loguri excelente și totuși să fie greu de urmărit de la un capăt la altul dacă nu există un identificator sau alt mecanism de corelare între pași.

Logurile pot deveni o scurgere de date. Nu scrie automat prompturi2 complete, tokenuri de acces, chei API5, documente sensibile sau întregul mediu al procesului în log. Observabilitatea bună păstrează suficient context pentru diagnostic fără să transforme jurnalul23 într-o copie a secretelor.
De la stdout și stderr ale unui serviciu systemd, colectate de systemd-journald într-un jurnal structurat, interogabil cu journalctl după unitate, timp, prioritate și urmărire în timp real, cu diferența dintre log, adică evenimente individuale, și urma de execuție, care urmărește aceeași cerere prin client, proxy, aplicație și bază de date.
Logul consemnează evenimente; urma de execuție leagă pașii aceleiași cereri între componente.

15. SSH: execuția se mută pe alt sistem, regulile nu dispar

SSH25 este una dintre principalele căi prin care vei opera servere, sisteme cu GPU, mașini virtuale și alte medii în care rulează agenți1.

În forma cea mai simplă:

ssh user@host
Cum citești forma user@host: user este numele contului de pe sistemul la distanță, iar host este numele sau adresa acelui sistem. Cele două nu descriu calculatorul local.

Clientul SSH25 local creează o conexiune criptată către serverul SSH de pe sistemul la distanță. După autentificare, serverul poate porni un shell8 interactiv sau o comandă specifică.

ssh user@host 'systemctl --user status agent.service'

A doua formă nu înseamnă neapărat „deschid un terminal9 complet”. Serverul execută acea comandă într-o sesiune la distanță. În mod obișnuit, comanda ajunge la shell-ul8 utilizatorului într-un context neinteractiv, iar fișierele de inițializare și mediul pot diferi de cele ale unei sesiuni interactive. De aceea o comandă care merge după autentificare interactivă poate eșua când este trimisă direct prin ssh host 'comandă'.

Două verificări diferite: serverul și utilizatorul

„Este acesta serverul corect?”

Clientul verifică cheia de gazdă26 (host key) a serverului. Cheile cunoscute sunt păstrate de obicei în ~/.ssh/known_hosts.

„Are acest utilizator voie să intre?”

Serverul autentifică utilizatorul. În autentificarea cu cheie publică, cheia publică autorizată este listată de obicei în ~/.ssh/authorized_keys pe sistemul la distanță, iar cheia privată rămâne la client.

Cheia privată este autoritate

Cheia privată nu pleacă niciodată de pe calculatorul tău. Serverul are nevoie numai de cheia publică autorizată; clientul dovedește că deține cheia privată fără să o trimită.

Legătura cu Lecția 5: după conectare, comenzile tale sunt executate de procese pe sistemul la distanță, cu UID/GID, grupuri, CWD16, PATH15 și permisiuni de acolo. SSH25 transportă sesiunea; nu îți transferă automat privilegiile locale.
SSH: două verificări, două roluri — clientul local verifică identitatea serverului după cheia de gazdă din known_hosts, serverul la distanță autorizează utilizatorul după cheia publică din authorized_keys, cheia privată rămâne la client, iar după cele două verificări se creează o sesiune cu un shell sau proces la distanță, cu UID și GID proprii.
Verificarea merge în ambele sensuri: clientul verifică serverul, serverul verifică utilizatorul.

16. Exemplu complet — „merge în terminal, dar serviciul eșuează”

Acesta este unul dintre cele mai utile exerciții de diagnostic pentru un constructor de agenți1, deoarece leagă căile, mediul, PATH15, permisiunile, serviciile și logurile într-o singură problemă.

Presupunem că ai un program fictiv document-agent. Pornit manual din proiect, funcționează:

cd ~/ai-agents-zero-to-expert
source .venv/bin/activate
export MODEL_URL="http://127.0.0.1:8000"
python src/document_agent.py

Îl transformi într-un serviciu22 systemd21 și primești o eroare. În loc să adaugi sudo sau să reinstalezi pachete, urmărești diferențele de context.

Verifică procesul și identitateaServiciul rulează sub același utilizator sau sub un cont dedicat? Are permisiunile necesare?
Verifică folderul curentCodul folosește ./configs/app.json? Este definit WorkingDirectory?
Verifică executabilulpython din terminal venea din .venv/bin? Serviciul folosește aceeași cale?
Verifică mediulMODEL_URL era exportat numai în shell-ul interactiv? Unitatea îl primește explicit?
Verifică logurilejournalctl -u document-agent.service arată un fișier lipsă, un executabil greșit sau o variabilă absentă?
Verifică definiția unitățiiDacă ai schimbat fișierul de definiție al unității, daemon-reload îl face pe systemd să îl recitească înainte de restart.
Repară cauza, nu simptomulDefinește explicit calea, mediul și identitatea; nu transforma serviciul în root doar ca să pornească.

Observă că niciuna dintre aceste cauze nu este „AI”. Sunt proprietăți ale procesului și mediului Linux. Tocmai de aceea fundațiile software apar înainte de Python, API-uri5 și framework-uri pentru agenți1 în planul cursului.

Diagnostic în limbaj natural

Simptom: manual funcționează; serviciul22 nu găsește executabilul Python așteptat.

Ipoteză: shell-ul8 interactiv avea .venv/bin în PATH15 după source .venv/bin/activate; serviciul22 nu primește automat același PATH.

Dovadă: compari command -v python în shell8 cu ExecStart și mediul definit pentru unitate.

Corecție: folosești o cale explicită, precum /cale/proiect/.venv/bin/python, în configurația serviciului sau definești mediul într-un mod controlat.

Comparație între contextul care merge în terminal, cu utilizatorul curent, un folder de lucru cunoscut, un PATH bogat, variabile de mediu prezente și ieșiri direct în terminal, și contextul care eșuează ca serviciu, cu alt utilizator, alt CWD, PATH minim, variabile lipsă și ieșiri în jurnalul serviciului, cu concluzia de a defini explicit User, WorkingDirectory, Environment și ExecStart.
Când același program merge în terminal9 dar eșuează ca serviciu22, compară contextul de execuție.

17. Securitate și confidențialitate în linia de comandă

Pentru un agent1, shell-ul8 este un instrument3 foarte puternic tocmai pentru că poate porni aproape orice program accesibil identității procesului. Puterea trebuie delimitată.

Nu pune secretele în locuri pe care instrumentele de diagnostic le pot expune

O comandă tastată poate ajunge în istoricul shell-ului8. Argumentele unui proces pot fi vizibile prin instrumente3 de inspecție, în funcție de permisiuni și configurația sistemului. Logurile pot înregistra comenzi sau valori din mediu. De aceea:

  • nu pune chei API5 direct în comenzi pe care le vei copia în chat, tichet de lucru sau jurnal23;
  • nu presupune că export SECRET=... este management de secrete;
  • nu tipări întregul mediu al procesului pentru diagnostic pe un sistem cu credențiale;
  • nu salva ieșirea de depanare fără s-o inspectezi înainte de commit sau încărcare.

Datele de intrare provenite din surse care nu sunt de încredere nu trebuie transformate în sintaxă shell

Dacă un agent1 primește un nume de fișier sau o propoziție de la utilizator și construiește o comandă shell8 lipind textul primit direct în comandă (concatenare), caracterele speciale pot fi reinterpretate de shell. În cod, preferă API-uri5 care transmit argumentele ca valori separate și evită shell-ul atunci când nu ai nevoie de sintaxa lui.

Exemplu: agentul1 construiește comanda cat + numele primit. Dacă numele este raport.txt; echo SURPRIZĂ, shell-ul8 vede două comenzi și o execută și pe a doua. Cu un text rău intenționat, a doua comandă ar putea șterge sau trimite date.

În Python vom reveni la acest lucru cu exemple concrete. Pentru moment reține principiul: datele utilizatorului sunt date; nu le transforma accidental în cod sau sintaxă de shell8.

Sudo rămâne o limită de privilegiu

Lecția 5 a stabilit deja că sudo nu este remediul universal pentru „Permission denied”. În linia de comandă, regula rămâne aceeași: verifică identitatea, proprietarul, grupul și operația înainte să crești privilegiul.

Un agent1 care poate executa sudo fără o politică strictă nu mai are doar acces la shell8; are și o cale de creștere a privilegiilor. Dacă o operație administrativă este necesară, proiectează o interfață îngustă, validată și auditată.

Accesul la shell8 oferă aproape întotdeauna posibilitatea de a produce efecte reale. Tratează un instrument3 de shell ca pe o interfață către procese, sistemul de fișiere, rețea și servicii22 — nu ca pe o simplă funcție de text.

18. Harta instrumentelor: ce responsabilitate are fiecare

Ca în Lecția 4, nu memorăm nume fără categorie. În această lecție categoria este mai importantă decât numele utilitarului.

Ce este: Bash — shell8 și limbaj de comandă.

Ce problemă rezolvă: interpretează textul tastat sau primit: expandează variabilele, rezolvă căile, pornește programele și leagă fluxurile lor.

Când ajungi la el: când orice comandă trebuie pornită și compusă — în terminal9 sau din scripturile agentului1.

Comandă exemplu și ce arată: echo $0 afișează shell-ul8 care rulează acum (real: -bash într-o sesiune de login, bash într-un terminal9 obișnuit deschis din interfața grafică; într-un script, $0 este numele scriptului); confirmă cine interpretează comenzile tale.

Ce NU este și cu ce se confundă: nu este aplicația de terminal9 (cea care desenează fereastra) și nu este kernelul; se confundă cu ambele pentru că „se văd în aceeași fereastră”.

Ce este: GNU Coreutils — utilitare fundamentale precum ls, cp, mv, sort, wc.

Ce problemă rezolvă: operațiile elementare peste fișiere și text, cu interfețe stabile și documentate — cărămizile fiecărui script.

Când ajungi la el: când sarcina cere listare, copiere, sortare sau numărare — înainte să scrii cod propriu pentru ceva ce există deja.

Comandă exemplu și ce arată: printf '%s ' a b c | wc -l afișează 3 — real, rulat pe Ubuntu 24.04: printf scrie trei linii, wc -l le numără; arată compunerea a două utilitare prin pipe13.

Ce NU este și cu ce se confundă: nu este manager de servicii22; se confundă cu shell-ul8 — utilitarele sunt pornite de shell, nu fac parte din el.

Ce este: findutils / find — căutare și selecție de obiecte din sistemul de fișiere.

Ce problemă rezolvă: găsirea fișierelor după nume, tip, vârstă sau dimensiune, cu acțiuni opționale asupra rezultatelor.

Când ajungi la el: când cauți „fișierele create ieri în folderul X” — o interogare pe structura discului, nu pe conținut.

Comandă exemplu și ce arată: find /tmp/l5q -name 'document' — real, pe structura din Lecția 5: /tmp/l5q/x/y/document; arată calea completă a obiectului găsit.

Ce NU este și cu ce se confundă: nu este index semantic — caută nume și atribute, nu sens; se confundă cu căutarea „în conținut”, care este treaba lui grep sau a unui motor de retrieval.

Ce este: grep — căutare de modele de text, linie cu linie.

Ce problemă rezolvă: găsirea rapidă a liniilor care se potrivesc unui tipar, în fișiere sau în fluxuri primite prin pipe13.

Când ajungi la el: când vrei să filtrezi ieșirea unei comenzi sau să cauți un cuvânt în loguri — de pildă erorile dintr-un jurnal23.

Comandă exemplu și ce arată: echo "text demo" | grep -c t afișează 1 — real pe Ubuntu 24.04: grep -c numără liniile cu potriviri; arată filtrarea prin pipe13.

Ce NU este și cu ce se confundă: nu este analizor universal pentru JSON sau loguri structurate; se confundă cu un parser — tiparele de text nu înțeleg structura.

Ce este: less — program de paginare pentru citirea ieșirii lungi.

Ce problemă rezolvă: citirea confortabilă a textului care depășește ecranul, cu navigare și căutare, fără să modifice nimic.

Când ajungi la el: când o ieșire (de pildă journalctl sau un fișier lung) este mai mare decât ecranul și vrei să o parcurgi.

Comandă exemplu și ce arată: journalctl | less — deschide jurnalul23 în paginare: primele rânduri ale jurnalului apar, iar q iese; arată că ieșirea unei comenzi poate deveni intrarea alteia.

Ce NU este și cu ce se confundă: nu modifică sursa — este doar vizualizator; se confundă cu un editor, dar ce vezi în less nu se salvează nicăieri.

Ce este: APT28 — administrarea pachetelor Debian și a surselor de pachete pe Ubuntu.

Ce problemă rezolvă: instalarea, actualizarea și scoaterea pachetelor de sistem, cu rezolvarea dependențelor dintre ele.

Când ajungi la el: când un instrument3 de sistem lipsește — graphviz, acl, pciutils — și trebuie instalat la nivelul potrivit al stivei.

Comandă exemplu și ce arată: apt list --upgradable 2>/dev/null | head -3 — real, pe Ubuntu 24.04: listează pachetele cu versiuni noi după apt update; arată că indexul și pachetele sunt operații separate.

Ce NU este și cu ce se confundă: nu este managerul dependențelor Python; se confundă cu pip — APT28 instalează pachete de sistem, pip pachete de limbaj în mediu virtual.

Ce este: dpkg — operații asupra pachetelor Debian locale și a bazei dpkg.

Ce problemă rezolvă: inspecția și manipularea pachetelor deja prezente: ce este instalat, ce fișiere aparțin unui pachet, ce versiuni există.

Când ajungi la el: când vrei să vezi ce pachet a instalat un fișier sau ce versiune exactă este pe sistem — fără descărcări.

Comandă exemplu și ce arată: dpkg -l | grep pciutils — real, pe Ubuntu 24.04: ii pciutils …; ii înseamnă pachet instalat corect, iar numele arată că utilitarul lspci provine de aici.

Ce NU este și cu ce se confundă: nu rezolvă singur toate dependențele din surse de pachete; se confundă cu APT28 — dpkg lucrează pe pachete locale, APT le aduce și le rezolvă.

Ce este: systemd21 / systemctl — management de servicii22 și alte unități.

Ce problemă rezolvă: transformarea unui proces oarecare într-un serviciu22 administrat: pornire la boot, repornire la eșec, stare4, jurnal23.

Când ajungi la el: când agentul1 nu mai este experiment în terminal9 și trebuie să fie pornit, supravegheat și oprit ca serviciu22 (secțiunea 13).

Comandă exemplu și ce arată: systemctl is-enabled agent.service și systemctl is-active agent.service — două comenzi care răspund la două întrebări diferite: configurare pentru boot și stare4 curentă (real: enabled și inactive până la primul start).

Ce NU este și cu ce se confundă: nu este shell8; se confundă cu comenzile manuale — systemd21 programează și supraveghează, shell-ul doar pornește odată.

Ce este: journald / journalctl — colectare și interogare de loguri structurate.

Ce problemă rezolvă: adunarea a ceea ce scriu serviciile (inclusiv stdout11/stderr12) plus metadate (dată, unitate, PID), căutabile ulterior.

Când ajungi la el: când agentul1 e servit de systemd21 și vrei să vezi ce a scris, căutat pe unitate sau pe interval de timp.

Comandă exemplu și ce arată: journalctl -u agent.service afișează doar înregistrările unității respective — fiecare rând are dată, unitate și text; căutarea pe unitate înlocuiește scroll-ul prin tot jurnalul23.

Ce NU este și cu ce se confundă: nu este un sistem complet de urmărire distribuită (distributed tracing); se confundă cu trace-ul — jurnalul23 înregistrează evenimente locale, nu traseul unei cereri prin mai multe componente.

Ce este: OpenSSH / ssh — conectare și execuție securizată la distanță.

Ce problemă rezolvă: mutarea execuției pe alt sistem, cu autentificare și criptare: comenzi, fișiere și porturi, în ambele direcții controlate.

Când ajungi la el: când agentul1 sau scripturile trebuie să ruleze pe server, iar contextul de execuție de acolo (secțiunea 16) trebuie inspectat pe loc.

Comandă exemplu și ce arată: ssh host 'command -v python3' arată ce rezolvare de Python are sistemul la distanță — adesea diferită de cea locală; confirmă contextul, nu-l presupune.

Ce NU este și cu ce se confundă: nu transferă automat identitatea și mediul local; se confundă cu sesiunea locală — la distanță, PATH-ul15, CWD-ul16 și variabilele sunt ale sistemului de acolo.

19. Trei familii de greșeli care apar repetat

Greșelile sunt grupate după cauză, nu după numele comenzii. Astfel le poți recunoaște și într-un instrument3 nou.

Fiecare card pornește de la o propoziție pe care o spune des cineva care învață. Citește-o, încearcă să vezi singur problema, apoi citește explicația.

19.1 Greșeli de context: calea, CWD-ul și mediul sunt presupuse

Ce spune cineva: „Fișierul există, deci serviciul22 îl va găsi.”

De ce este greșit: o cale relativă depinde de CWD16. Întrebarea corectă: din ce folder rulează procesul și ce cale finală încearcă să deschidă?

Ce se întâmplă de fapt: calea relativă se interpretează pornind din CWD-ul16 procesului. Comenzi reale care arată diferența, rulate pe Ubuntu 24.04:

pwd
/tmp/serviciu-simulat
ls -l configs/app.json
-rw-r--r-- 1 ai ai 0 oct 17 10:20 configs/app.json

Fișierul există la /tmp/serviciu-simulat/configs/app.json; un serviciu22 pornit din /var/lib/agent va căuta /var/lib/agent/configs/app.json — inexistent. Confirmarea se face cu pwd în contextul procesului și cu ls -l pe calea completă rezultată.

Ce spune cineva: „Am exportat variabila în terminal9, deci serviciul22 o are.”

De ce este greșit: mediul shell-ului8 tău nu este automat mediul unui serviciu22 systemd21. Întrebarea corectă: cine pornește procesul și ce mediu îi furnizează?

Ce se întâmplă de fapt: mediul merge numai de la părinte la copil. Exportul din terminalul9 tău ajunge în procesele pornite de acel terminal; un serviciu22 pornit de systemd21 primește mediul definit în fișierul lui unit. Confirmarea reală se face pe proces: tr "\0" "\n" < /proc/PID/environ afișează mediul efectiv al procesului — ce vezi acolo este exact ce are, nu ce ai exportat tu în altă fereastră.

Ce spune cineva: „python este întotdeauna același executabil.”

De ce este greșit: PATH15 și mediile virtuale pot schimba rezolvarea. Întrebarea corectă: ce arată command -v python în contextul care contează?

Ce se întâmplă de fapt: rezolvarea numelui depinde de PATH15 și de mediile virtuale. Confirmare reală pe Ubuntu 24.04:

command -v python3
/usr/bin/python3
.venv/bin/python3 --version
Python 3.12.3

Același nume cere două executabile diferite în două contexte — /usr/bin/python3 pentru sistem, .venv/bin/python3 pentru mediul virtual activ. Întrebarea corectă se răspunde cu command -v rulat în contextul procesului care contează.

19.2 Greșeli de flux: stdout, stderr și codul de ieșire sunt confundate

Ce spune cineva: „Nu a afișat nimic, deci a reușit.”

De ce este greșit: succesul este comunicat prin codul de ieșire, iar ieșirea poate fi redirecționată. Întrebarea corectă: ce cod de ieșire14 a returnat?

Ce se întâmplă de fapt: codul de ieșire spune rezultatul, nu tăcerea. Demonstrație reală:

ls fisier-inexistent 2> /dev/null; echo $?
2

Terminalul9 a rămas complet curat — eroarea a fost trimisă în /dev/null —, dar codul de ieșire 2 arată eșecul. Confirmarea este echo $? imediat după comandă.

Ce spune cineva: „Pipe-ul13 trimite automat și erorile.”

De ce este greșit: | conectează stdout11 la stdin10; stderr12 rămâne separat dacă nu îl redirecționezi. Întrebarea corectă: ce descriptor vreau să conectez?

Ce se întâmplă de fapt: erorile țin de fluxul 2, care nu intră în pipe13. Demonstrație reală:

ls fisier-inexistent | wc -l
ls: cannot access 'fisier-inexistent': No such file or directory
0

Eroarea apare în terminal9 (nu a intrat în pipe13), iar wc -l a primit zero linii. Confirmarea: aceeași comandă cu 2>&1 | wc -l — fluxurile combinate produc o altă numărare, arătând că descriptorii merg pe drumuri diferite.

Ce spune cineva: „Ultima comandă din pipeline29 a reușit, deci tot pipeline-ul a reușit.”

De ce este incomplet: fără pipefail, Bash raportează în mod normal statusul ultimei comenzi. Întrebarea corectă: cum tratează scriptul eșecurile intermediare?

Ce se întâmplă de fapt: fără set -o pipefail, Bash raportează statusul ultimei comenzi din pipe13. Confirmare reală: grep -c potrivire fisier-inexistent | wc -l — grep eșuează (fișier inexistent), dar wc -l reușește și pipeline-ul29 raportează succesul ultimei comenzi, mascând eșecul din amonte. Cu set -o pipefail înainte, același lanț raportează eșecul primului pas.

19.3 Greșeli de operare: pachet, serviciu și acces la distanță

Ce spune cineva: „apt update mi-a actualizat toate programele.”

De ce este greșit: actualizează indexul local al surselor de pachete. Întrebarea corectă: am actualizat indexul sau pachetele instalate?

Ce se întâmplă de fapt: apt update actualizează numai indexul — lista „ce există și de unde se descarcă”. Confirmare reală pe Ubuntu 24.04:

apt list --upgradable 2>/dev/null | head -3
Se înnoiesc…

Comanda arată ce pachete au versiuni noi după actualizarea indexului — instalarea lor este apt upgrade, o operație separată. Confirmarea: apt policy nume-pachet arată versiunea instalată versus candidat.

Ce spune cineva: „Am dat enable, deci serviciul22 rulează.”

De ce este greșit: enable și start descriu lucruri diferite. Întrebarea corectă: este unitatea activă acum și este configurată pentru activare viitoare (enabled)?

Ce se întâmplă de fapt: enable programează pornirea la boot; starea4 curentă se citește separat. Confirmare reală cu systemctl is-enabled agent.service (răspunde enabled) și systemctl is-active agent.service (răspunde inactive până la primul start) — două comenzi, două întrebări diferite.

Ce spune cineva: „Cheia de gazdă26 s-a schimbat; șterg avertismentul și continui.”

De ce este periculos: schimbarea poate fi legitimă sau poate indica impersonare. Întrebarea corectă: pot verifica printr-un canal de încredere amprenta (fingerprint) noii chei de gazdă26?

Ce se întâmplă de fapt: cheia de gazdă26 este amprenta serverului; schimbarea ei merită confirmată printr-un canal de încredere (consolă de la furnizor, un coleg cu acces) înainte de a continua. Ștergerea liniei din known_hosts cu ssh-keygen -R host este legitimă doar după confirmarea motivului — altfel avertismentul următor (posibil o impersonare) este tratat la fel ca primul.

20. Găsește greșeala — „DevOps pentru agenți în 30 de secunde”

Exercițiul combină confuziile lecției într-un singur „tutorial” intenționat prost.

„Dacă agentul1 nu pornește, fă așa: intră cu SSH25, ignoră avertismentul despre cheia de gazdă26, rulează tot cu sudo27, pune folderul curent la începutul PATH15-ului, exportă toate cheile API5 într-o singură variabilă, apoi execută apt update ca să actualizezi toate programele. Dă systemctl enable agent.service și serviciul22 va porni imediat. Dacă nu vezi nimic în terminal9, înseamnă că a mers. Pentru depanare, salvează întregul mediu al procesului și toate prompturile în log.”

Cheia instructorului

1. „Dacă agentul1 nu pornește, fă așa: intră cu SSH25…” SSH (Secure Shell8) este protocolul prin care execuți comenzi pe alt sistem de la distanță (secțiunea 15). Conexiunea la distanță în sine este legitimă — problema nu este SSH-ul, ci tot ce urmează în aceeași listă: fiecare pas de mai jos așază o decizie periculoasă în locul unei verificări.

2. „…ignoră avertismentul despre cheia de gazdă26…” Cheia de gazdă (host key) este amprenta publică prin care serverul își dovedește identitatea la prima conexiune; avertismentul apare exact când amprenta nu se potrivește cu cea cunoscută (secțiunea 15.1). Ignorat, avertismentul deschide ușa unui server fals care îți captează tot ce trimiți — inclusiv cheile și secretele. Corect: verifici amprenta printr-un canal de încredere, apoi o accepți conștient.

3. „…rulează tot cu sudo27…” sudo ridică privilegiul unei comenzi la root — identitatea care ocolește majoritatea verificărilor (secțiunea 17.3). „Tot cu sudo” înseamnă că orice greșeală de logică sau orice document ostil7 acționează asupra întregului sistem. Corect: fiecare operație primește exact privilegiul de care are nevoie, justificat separat.

4. „…pune folderul curent la începutul PATH15-ului…” PATH-ul este lista de foldere în care shell-ul8 caută comenzi (secțiunea 5.1); el caută în ordine și se oprește la prima potrivire. Un folder la începutul listei îți suprascrie comenzile de sistem: un fișier numit apt în folderul curent devine comanda pe care o rulează agentul1. Corect: folderele de încredere stau în față, iar folderul curent nu intră deloc în PATH.

5. „…exportă toate cheile API5 într-o singură variabilă…” Variabila de mediu este valoare transmisă proceselor-copil (secțiunea 10). O singură variabilă cu toate cheile înseamnă că orice proces copil — și orice scurgere de date — expune tot setul deodată, iar o actualizare atinge toate serviciile odată. Corect: câte o variabilă separată per serviciu22, cu dreptul minim (secțiunea 17.1).

6. „…apoi execută apt update ca să actualizezi toate programele.” apt update actualizează numai indexul pachetelor — lista de ce există și de unde se descarcă (secțiunea 11.1). Instalarea și înnoirea efectivă sunt apt upgrade, iar pachetele de limbaj (pip, npm) nu sunt gestionate de apt28 deloc. Corect: actualizezi indexul, apoi upgrade-ul, conștient de fiecare strat.

7. „Dă systemctl enable agent.service și serviciul22 va porni imediat.” enable configurează pornirea automată la boot; start pornește serviciul acum (secțiunea 13.1). Sunt două operații diferite, care se pot face în orice ordine — dar una fără alta lasă fie un serviciu care nu pornește niciodată singur, fie unul pornit odată și dispărut la repornire.

8. „Dacă nu vezi nimic în terminal9, înseamnă că a mers.” Tăcerea terminalului nu este un cod de ieșire14. Starea4 reală se citește din $? — codul de ieșire al ultimei comenzi (secțiunea 9) — și din jurnalele serviciului cu journalctl (secțiunea 14). Un serviciu22 poate rula în fundal, poate eșua imediat sau poate aștepta — doar starea și codul de ieșire spun care variantă este reală.

9. „Pentru depanare, salvează întregul mediu al procesului și toate prompturile în log.” Mediul procesului conține cheile API5, iar prompturile pot conține date confidențiale ale utilizatorilor; un fișier de log cu tot acest conținut devine o copie a secretelor, citibilă oricui are acces la loguri (secțiunea 17.1). Corect: logurile conțin evenimente, coduri de ieșire14 și valori secretizate, nu secretele propriu-zise.

Concluzie. „Tutorialul” vinde viteza prin eliminarea fiecărei verificări care te-ar încetini o secundă — și construiește exact agenții1 care cad cel mai greu de depanat: identitate neconfirmată, privilegii maxime, PATH15 slăbit, secrete împrăștiate, stări interpretate după tăcere. Lecția arată contrariul: fiecare mecanism (SSH25, sudo27, PATH, variabile de mediu17, apt28, systemd21, coduri de ieșire14, loguri) are un rol precis, iar verificările care par să încetinească sunt cele care fac sistemul reparabil, nu doar pornibil.

Ilustrație „DevOps pentru agenți în 30 de secunde — găsește greșeala”: sudo peste tot, ignorarea cheii de gazdă, enable considerat echivalent cu rularea acum, apt update înțeles ca actualizare a tuturor programelor și scrierea tuturor secretelor în log, fiecare dintre ele marcată cu semnul întrebării.
Scurtăturile care „fac eroarea să dispară” pot ascunde exact problema de înțeles.

21. Aceleași concepte în cinci tipuri de agenți

Linux și shell-ul8 nu sunt „subiecte DevOps” separate de agent1. Ele apar oriunde agentul atinge fișiere, procese sau servicii22.

Agent de programare

Rulează teste, compilări și verificatoare automate de cod (linters). Are nevoie de PATH15 reproductibil, ghilimele folosite corect pentru căi, coduri de ieșire14 și separare între stdout11 și stderr12.

Agent pentru documente

Procesează foldere mari de fișiere. Are nevoie de căi clare, expansiune18 controlată a numelor de fișiere (globbing19), lanțuri de comenzi (pipeline-uri29) care nu ascund erori și loguri care nu publică documentele-sursă.

Agent de voce

Poate rula ca serviciu22 permanent. systemd21, mediul de execuție, politica de repornire și journalctl24 devin mai importante decât shell-ul8 interactiv în care l-ai testat prima dată.

Agent de infrastructură

Folosește SSH25 și comenzi la distanță. Verificarea cheii de gazdă, conturile de serviciu22 și privilegiul minim devin parte din proiectarea instrumentelor3.

Agent pentru media sau ML local

Depinde de executabile, modele și lanțuri de instrumente3 (toolchains) care trebuie găsite prin PATH15 și instalate în stratul potrivit. Un mediu diferit poate schimba complet runtime-ul care ajunge să fie folosit.

22. Fișa unui spațiu de lucru reproductibil de linie de comandă

În laborator vei construi un spațiu de lucru reproductibil: un proiect pe care altcineva îl poate recrea și înțelege. Înainte de asta, stabilim ce înseamnă „reproductibil” la acest nivel.

Întrebarea: Unde este rădăcina proiectului?

De ce contează: Rădăcina este punctul de referință al tuturor căilor relative: dacă ea se mișcă, config-urile, datele și scripturile „dispar” fără să se fi schimbat nimic în ele.

Cum afli răspunsul: Din comanda de pornire a proiectului și din pwd rulat în folderul din care lucrezi — calea afișată este rădăcina efectivă, nu cea pe care o presupui.

Un răspuns bun și unul slab: Bun: „o cale clară, de pildă ~/ai-agents-zero-to-expert.” Slab: „folderul în care eram când a mers” — o rădăcină pe care nimeni nu o poate recrea mâine.

Întrebarea: Ce shell8 presupun exemplele?

De ce contează: Comenzi identice se comportă diferit între shell-uri8: sintaxa variabilelor, expansiunile și opțiunile interne diferă între Bash și alte shell-uri.

Cum afli răspunsul: Din echo $0 rulat în sesiunea în care vei folosi exemplele — afișează shell-ul8 care le va interpreta.

Un răspuns bun și unul slab: Bun: „Bash, pe Ubuntu 24.04.” Slab: „orice shell8” — sintaxa din exemple nu este portabilă fără specificare.

Întrebarea: Ce comenzi externe sunt necesare?

De ce contează: Fiecare comandă externă este o dependență: dacă nu există sau există altfel, scriptul tău se rupe în punctul acela, la orice rulare nouă.

Cum afli răspunsul: command -v nume-comandă confirmă prezența, iar nume-comandă --version arată versiunea — ambele rulate pe mașina țintă, nu pe a ta.

Un răspuns bun și unul slab: Bun: „jq, verificat cu command -v jq și jq --version.” Slab: „folosim jq” — fără verificare, dependența nu poate fi confirmată de altcineva.

Întrebarea: Ce vine din APT28?

De ce contează: Pachetele de sistem instalate prin APT28 (secțiunea 11.1) trebuie diferențiate de pachetele limbajului: se instalează altfel, se actualizează altfel și se pierd altfel la reconstruire.

Cum afli răspunsul: Din lista de instalare a proiectului: sudo apt install … cu numele exacte, iar ce este deja instalat se confirmă cu dpkg -l | grep nume.

Un răspuns bun și unul slab: Bun: „graphviz și acl, instalate prin APT28.” Slab: „diverse unelte de sistem” — o listă pe care nimeni nu o poate recrea.

Întrebarea: Ce variabile de mediu17 sunt obligatorii?

De ce contează: Variabilele lipsesc sau diferă între contexte — exact greșeala din secțiunea 19.1 —, iar numele lor trebuie scrise fără secretele propriu-zise, care stau în alt loc.

Cum afli răspunsul: Din program: fiecare citire de mediu (de pildă API_URL) este o variabilă obligatorie; numele intră în documentație, valorile în variabilele de sesiune sau într-un fișier local ignorat de Git.

Un răspuns bun și unul slab: Bun: „API_URL — din .env local, ignorat de Git; fără valori scrise în documentație.” Slab: „API5_URL=cheia-mea-secretă” — un secret publicat în fișa de proiect.

Întrebarea: Ce CWD16 presupune programul?

De ce contează: Căile relative se interpretează din CWD16 (secțiunea 4.2) — exact greșeala „merge în terminal9, eșuează ca serviciu22” din secțiunea 16.

Cum afli răspunsul: Din comenzile programului: dacă citește configs/app.json fără cale absolută, CWD-ul16 este o dependență; ideal, o elimini ancorând căile la rădăcina proiectului calculată în cod.

Un răspuns bun și unul slab: Bun: „rulabil din orice folder; căile sunt ancorate la rădăcina proiectului în cod.” Slab: „trebuie rulat din folderul X, dar nu scriem asta nicăieri” — o dependență ascunsă.

Întrebarea: Unde merg stdout11 și stderr12?

De ce contează: Cele două fluxuri sunt separate (secțiunea 7); dacă nu spui unde merg, sistemul decide pentru tine — iar un serviciu22 pornit de systemd21 le trimite implicit în jurnal23.

Cum afli răspunsul: Din modul de pornire: terminal9 — apare direct; fișier — redirecționare > și 2>; serviciu22 — journald (secțiunea 14), citibil cu journalctl -u nume.

Un răspuns bun și unul slab: Bun: „stdout11 în fișierul de rulare, stderr12 în terminal9; ca serviciu22, ambele în journal.” Slab: „se văd în terminal” — fals pentru orice proces pornit de altcineva sau programat.

Întrebarea: Cum este detectat eșecul?

De ce contează: Tăcerea nu dovedește succesul (secțiunea 9): doar codul de ieșire spune sistemului cum s-a terminat procesul, iar regulile pentru coduri trebuie să fie clare.

Cum afli răspunsul: Din program: ce cod întoarce la succes, ce la eșec și cum le citește cine le apelează — echo $? după rulare arată codul real.

Un răspuns bun și unul slab: Bun: „0 = succes; 1 = date invalide; 2 = lipsă configurare; apelantul verifică $?.” Slab: „programul plânge dacă nu merge” — mesajele nu sunt coduri.

Întrebarea: Rulează manual sau ca serviciu22?

De ce contează: Modul de rulare decide cine pornește procesul, cu ce mediu și ce se întâmplă la reboot — diferența din secțiunea 13 dintre pornirea manuală și serviciu22 administrat.

Cum afli răspunsul: Din frecvența și durata sarcinii: rulări ocazionale — manual; proces permanent sau programat — serviciu22, cu fișier unit și comenzi systemctl.

Un răspuns bun și unul slab: Bun: „manual în faza de test; ca serviciu22 agent.service după stabilizare.” Slab: „depinde” — o dependență de rulare care nu poate fi documentată.

Întrebarea: Rulează local sau la distanță?

De ce contează: La distanță, execuția se mută pe alt sistem (secțiunea 15): alt PATH15, alt CWD16, alte reguli — iar autentificarea trebuie făcută fără publicarea secretelor.

Cum afli răspunsul: Din locul unde trebuie să ruleze modelul și datele; accesul se face prin SSH25 cu chei, iar cheile private rămân în afara proiectului și a documentației.

Un răspuns bun și unul slab: Bun: „local pe laptop; la distanță pe server, cu cheie SSH25 dedicată agentului1, cheia privată în afara proiectului.” Slab: „ne conectăm cu parola” — o parolă tastată de fiecare dată este o invitație la automatizare fragilă.

Întrebarea: Unde verifici logurile?

De ce contează: Depanarea (secțiunea 14) începe din loguri: dacă nu știi unde sunt, incidentul devine ghicire — și logurile care conțin secrete devin ele însele un incident.

Cum afli răspunsul: Din modul de rulare: serviciu22 — journalctl -u nume; rulare manuală cu redirecționare — fișierul în care au fost scrise stdout11 și stderr12.

Un răspuns bun și unul slab: Bun: „journalctl -u agent.service pentru serviciu22; logs/ ignorat de Git pentru rulările manuale.” Slab: „în terminal9, undeva” — adică nicăieri.

Întrebarea: Ce fișiere sunt sursă și ce fișiere sunt generate?

De ce contează: Sursele se editează și intră în Git; generatele se produc și de obicei se ignoră — amestecarea lor duce la versionarea rezultatelor și la pierderea surselor (pregătește lecția despre Git).

Cum afli răspunsul: Pentru fiecare fișier te întrebi: îl scriu eu sau îl produce o comandă? Sursa intră în Git, generatul primește regulă în .gitignore.

Un răspuns bun și unul slab: Bun: „surse: catalogul JSON și scripturile; generate: diagrama PNG și inventarul.” Slab: „toate sunt importante, le ținem pe toate” — fără separare, istoricul Git devine neutilizabil.

Reproductibil nu înseamnă neapărat „identic bit cu bit pe orice computer”. Înseamnă că dependențele, contextul și pașii esențiali sunt expliciți suficient de bine încât altcineva să poată recrea mediul și să știe unde să caute dacă rezultatul diferă.

23. Exerciții practice

Exercițiile verifică modelul mental înainte de laboratorul în care vei construi spațiul de lucru complet.

Exercițiul 1 — Terminal sau shell?

Pentru fiecare situație spune ce componentă investighezi prima: fontul nu afișează corect caractere; cd are comportament neașteptat; un proces nu are drept de scriere; $VAR nu se expandează.

Răspuns

Fontul și randarea aparțin aplicației de terminal9; cd și expansiunea18 $VAR aparțin shell-ului8; dreptul de scriere aparține procesului și permisiunilor sistemului de fișiere. Terminalul este aplicația care desenează textul și trimite tastele, shell-ul este programul care interpretează comenzi (secțiunea 1), iar procesul este execuția vie, cu identitate și drepturi proprii (Lecția 5, secțiunea 4).

Explicațiile, în ordine: caracterele prost randate sunt un lucru pe care îl desenează fereastra — problema e în font sau în randarea emulatorului. cd este o comandă internă a shell-ului8, iar $VAR este expansiune18 făcută de shell înainte să pornească orice program (secțiunea 6) — ambele aparțin shell-ului. Dreptul de scriere este verificat de kernel pentru procesul care cere operația, cu credențialele lui — deci proces, plus sistemul de fișiere.

Capcana: toate patru „se văd în terminal9”, dar trei componente diferite le produc. Pentru constructorul de agenți1, această triere este primul pas de depanare: fiecare componentă are alte comenzi de verificare — font în emulator, cd și echo $VAR în shell8, ls -l și ps pentru proces și permisiuni.

Exercițiul 2 — Cale relativă sau absolută?

Programul deschide configs/app.json și funcționează numai când este pornit din rădăcina proiectului. Ce dependență ascunsă are?

Răspuns

Programul depinde de folderul curent de lucru (CWD16): calea relativă configs/app.json este interpretată pornind din folderul din care a fost pornit procesul. CWD (current working directory) este parte din starea4 procesului (secțiunea 4.2), iar o cale relativă pornește de la el, nu de la rădăcina sistemului.

Exemplu concret: același script pornit din rădăcina proiectului deschide configs/app.json cu succes; pornit din /tmp, încearcă /tmp/configs/app.json — un fișier care probabil nu există. Comanda pwd rulată în fiecare context arată cele două foldere diferite.

Capcana: „merge când pornesc de aici” este un simptom clasic al dependenței de CWD16, nu o dovadă de corectitudine. Pentru constructorul de agenți1, regula este: căile relative ale agentului se fac explicite sau se ancorează la rădăcina proiectului, iar contextul de execuție se verifică înainte de a suspecta codul.

Exercițiul 3 — De ce "$FILE"?

Explică de ce o variabilă care conține o singură cale ar trebui transmisă de regulă între ghilimele duble.

Răspuns

Valorile se transmit între ghilimele duble pentru că ele păstrează calea ca un singur argument și împiedică împărțirea în cuvinte și expansiunea18 numelor de fișiere — deși valoarea variabilei se înlocuiește în continuare. Expansiunea este înlocuirea $FILE cu valoarea lui de către shell8, înainte ca programul să pornească (secțiunea 6.1).

Exemplu concret: dacă FILE conține raport final.txt, comanda cp "$FILE" backup/ copiază un singur fișier cu numele complet; fără ghilimele, shell-ul8 livrează două argumente — „raport” și „final.txt” — și expandează orice * din nume în liste de fișiere.

Capcana: ghilimelele simple opresc și expansiunea18 — shell-ul8 ar transmite literal $FILE. Pentru constructorul de agenți1, ghilimelele duble în jurul fiecărei variabile sunt regula care separă „am șters raport final.txt” de „am șters raport și orice alt fișier care se potrivea cu tiparul”.

Exercițiul 4 — Unde merge eroarea?

Ai producer | consumer. producer scrie un avertisment pe stderr12. Ajunge avertismentul în stdin-ul10 lui consumer?

Răspuns

Nu prin operatorul | obișnuit: el conectează stdout-ul11 lui producer la stdin-ul10 lui consumer, iar stderr12 rămâne un flux separat, care apare direct în terminal9. Pipe-ul13 este canalul dintre două procese (secțiunea 8.2); stderr este fluxul 2, cel pentru erori, distinct de fluxul 1 (stdout).

Exemplu concret: grep error fisier.log | wc -l trimite rezultatele lui grep către wc -l; dacă grep scrie și un avertisment pe stderr12, avertismentul sare peste pipe13 și apare în terminal9 — de aceea nu este numărat de wc.

Capcana: să crezi că avertismentul „a dispărut” în pipe13 — el a luat alt descriptor. Pentru constructorul de agenți1, regula dă control fin asupra fluxurilor: dacă vrei erorile în analiza ta, le redirecționezi explicit cu 2>&1; dacă nu, le lași separate ca să le vezi în terminal9.

Exercițiul 5 — Variabilă locală sau variabilă de mediu?

De ce API_URL=value și export API_URL=value nu sunt echivalente pentru un program pornit după aceea?

Răspuns

API_URL=value creează sau modifică o variabilă a shell-ului8 curent, iar export API_URL=value o marchează și pentru includerea în mediul proceselor pornite ulterior de shell. Variabila de shell există doar în interiorul shell-ului; variabila de mediu este copiată în fiecare proces-copil la pornire (secțiunea 10).

Exemplu concret: fără export, programul pornit de shell8 după aceea primește un mediu în care API_URL nu există deloc — de aceea citirea ei în program întoarce gol. Cu export, copilul o primește.

Capcana: să crezi că mediul „se transmite înapoi” — el merge numai de la părinte la copil, niciodată invers (secțiunea 10.1). Pentru constructorul de agenți1, distincția explică de ce un export făcut în terminal9 nu ajunge niciodată la un agent pornit de systemd21 — serviciul22 are propriul mediu, definit în fișierul lui unit.

Exercițiul 6 — Enabled, dar inactive

Un serviciu22 apare enabled și inactive. Este contradicție?

Răspuns

Nu — starea4 enabled descrie configurarea pentru pornirea automată la boot, iar active/inactive descrie starea curentă a serviciului; cele două stări descriu lucruri diferite. enabled înseamnă că systemd21 a creat legătura care pornește serviciul22 la fiecare pornire a sistemului; active/inactive spun dacă procesul rulează acum (secțiunea 13.1).

Exemplu concret: un serviciu22 enabled și inactive este perfect legitim: va porni la următorul boot, dar nu a fost încă pornit manual. Comanda systemctl enable --now agent.service le combină: configurează pornirea automată și pornește serviciul imediat.

Capcana: exact confuzia din secțiunea 20 — „enable” citit ca „pornește acum”. Pentru constructorul de agenți1, distincția explică de ce agentul „nu pornește” după enable: comanda programează viitorul, iar pornirea imediată este o a doua comandă sau opțiunea --now.

Exercițiul 7 — Două chei, două întrebări

Explică diferența dintre intrarea din known_hosts și cheia publică din authorized_keys.

Răspuns

known_hosts îl ajută pe client să verifice identitatea serverului, iar authorized_keys îi spune serverului ce chei publice pot autentifica utilizatorul — două verificări în direcții opuse. known_hosts este fișierul clientului cu amprentele (host key) serverelor de încredere; authorized_keys este fișierul serverului cu cheile publice care pot deschide un cont (secțiunea 15).

Povestea, în ordine: serverul își prezintă cheia de gazdă26, clientul o compară cu known_hosts — dacă se potrivește, serverul e cel de la ultima conectare; apoi utilizatorul își dovedește identitatea cu cheia privată, iar serverul acceptă dacă cheia publică potrivită figurează în authorized_keys.

Capcana: să crezi că „serverul e verificat” înseamnă „sunt autorizat” sau invers. Pentru constructorul de agenți1, ambele fișiere țin securitatea conexiunii agentului: primul protejează agentul de un server fals, al doilea protejează serverul de agenți neinvitați.

Exercițiul 8 — Merge manual, eșuează la distanță

Un script rulează local, dar ssh host 'script' spune command not found. Enumeră trei ipoteze care nu presupun că scriptul este „stricat”.

Răspuns posibil

Trei ipoteze fără să atingi scriptul: PATH-ul15 sesiunii la distanță este diferit; mediul virtual nu este activ; comanda există numai local sau la o altă cale pe sistemul la distanță. PATH-ul este lista de foldere în care shell-ul8 caută comenzi (secțiunea 5.1), iar o sesiune SSH25 non-interactivă pornește cu un PATH adesea mai scurt decât sesiunea ta interactivă.

Exemplu concret: ssh host 'command -v python3' poate întoarce /usr/bin/python3 chiar dacă sesiunea ta interactivă pe același host folosește .venv/bin/python3 — aceeași comandă, alt rezultat, pentru că mediile diferă.

Capcana: să concluzionezi „scriptul e stricat” — el rulează, dar în alt context de execuție (secțiunea 16). Pentru constructorul de agenți1, exercițiul fixează lista de verificare a execuției la distanță: identitate, CWD16, PATH15 și mediu — fiecare inspectată cu command -v, pwd și echo $VAR rulate pe sistemul la distanță, nu local.

Exercițiul 9 — Prezice înainte să rulezi

Scrie pe hârtie ce afișează fiecare linie, apoi verifică în terminal9:

printf '%s\n' a b c | wc -l
ls fisier-inexistent 2> /dev/null; echo "$?"
X=1; bash -c 'echo "[$X]"'
Răspuns

Cele trei linii afișează 3, apoi 2, apoi [] — și fiecare rezultat are un motiv distinct. Prima linie: printf '%s ' a b c | wc -l — printf scrie trei linii (a, b, c), iar wc -l le numără și afișează 3.

A doua linie: ls fisier-inexistent 2> /dev/null; echo "$?" — ls eșuează, dar erorile ei (descriptorul 2) sunt trimise în /dev/null, „coșul” care înghite tot ce primește; terminalul9 rămâne curat, iar echo "$?" afișează 2 — codul de ieșire al lui ls (secțiunea 9). Tăcerea nu înseamnă succes: codul o spune.

A treia linie: X=1; bash -c 'echo "[$X]"' — X=1 este variabilă de shell8, nu exportată, deci procesul-copil bash -c primește un mediu fără ea și afișează [] (secțiunea 10). Cu export X înainte, copilul ar fi afișat [1].

Capcana: să crezi că variabila „ajunge” la copil pentru că e vizibilă în shell8. Pentru constructorul de agenți1, exercițiul arată cele trei lecții deodată: numărarea prin pipe13, separarea erorilor de rezultate și transmiterea mediului numai către copil.

24. Quiz cu răspunsuri explicate

Răspunde înainte să deschizi explicația. Încearcă să justifici prin context, flux sau responsabilitate, nu doar prin memorarea comenzii.

1

Terminalul9 și Bash sunt același program?

Deschide răspunsul

Nu — terminalul9 este aplicația care desenează fereastra de text, iar Bash este shell-ul8 care rulează în interiorul ei și interpretează limbajul de comandă. Terminalul (emulatorul de terminal) este aplicația grafică: desenează literele, trimite tastele apăsate mai departe și nu înțelege nimic din ce scrii. Shell-ul este programul care primește textul, îl interpretează ca limbaj de comandă și pornește programe (secțiunea 1).

Povestea, în ordine: deschizi fereastra terminalului; terminalul9 pornește un shell8 în interior — pe Ubuntu, de regulă Bash; tu scrii „ls” în fereastră, terminalul transmite textul shell-ului, Bash îl interpretează, pornește programul ls și trimite rezultatul înapoi, iar fereastra doar îl desenează.

Cele două se pot schimba independent: poți rula același Bash într-un alt emulator de terminal9, sau poți pune alt shell8 (de pildă zsh) în aceeași fereastră. Iar Bash poate rula și fără nicio fereastră: serviciile pornite de systemd rulează shell-uri și scripturi fără niciun terminal atașat — exact situația din secțiunea 16, „merge în terminal, dar serviciul22 eșuează”.

Capcana: să configurezi „terminalul9” când de fapt configurezi shell-ul8, sau invers — variabilele de mediu, PATH-ul15 și promptul2 aparțin shell-ului, fereastra doar le arată. Pentru constructorul de agenți1, distincția explică de ce un agent sau un serviciu22 nu are terminal: terminalul este o comoditate pentru oameni, iar bucla agentului poate rula orbește, fără nicio interfață desenată.

2

Ce face pwd?

Deschide răspunsul

pwd (print working directory) afișează folderul curent de lucru al procesului shell8 — calea în care te afli acum. Folderul curent de lucru este parte din starea4 procesului (secțiunea 4.2): orice cale relativă tastată pornește de la ea, iar procesele pornite de shell o moștenesc.

Demonstrație reală pe Ubuntu 24.04, rulată în folderul proiectului:

pwd
/home/ana/ai-agents-zero-to-expert

Comanda nu primește argumente relevante — ea citește starea4 procesului propriu și scrie calea; fiecare parte a numelui afișat este un folder traversat.

Capcana: să confunzi folderul curent cu folderul unde se află programul rulat — un script pornit din altă parte rulează cu folderul curent al celui care l-a pornit, nu cu al lui. Pentru constructorul de agenți1, asta explică o familie întreagă de erori din secțiunea 16: același agent citește „config.json” cu succes în terminal9 și eșuează ca serviciu22, pentru că procesul de serviciu are un alt folder curent de lucru.

3

De ce ./tool și tool pot porni programe diferite?

Deschide răspunsul

./tool numește explicit programul din folderul curent, prin cale relativă, iar tool lasă shell-ul8 să caute comanda în folderele din PATH — două programe diferite pot răspunde la cele două comenzi. Calea relativă pornește din folderul curent (secțiunea 4), iar PATH-ul15 este lista ordonată de foldere în care shell-ul caută comenzi fără cale (secțiunea 5.1).

Demonstrație reală pe Ubuntu 24.04, în folderul /tmp/l6demo cu un program numit tool care scrie „din folderul curent” și altul instalat în /tmp/l6demo/bin/tool care scrie „versiunea din PATH15”:

PATH="/tmp/l6demo/bin:$PATH" tool
versiunea din PATH
./tool
din folderul curent

Același nume tastat, două programe diferite: tool a rezolvat prin PATH15 spre /tmp/l6demo/bin/tool, iar ./tool a rulat fișierul din folderul curent.

Capcana: shell-ul8 caută intenționat numai în folderele din PATH15, nu și în folderul curent — o convenție de securitate, fiindcă un fișier ostil7 numit ls în folderul curent ar deveni comanda ta. Pentru constructorul de agenți1, regula arată de ce scripturile laboratorului se pornesc cu ./src/…: calea relativă spune explicit „rulează acest fișier de aici”, nu „caută un program cu numele ăsta undeva în sistem”.

4

Ce protejează ghilimelele duble în "$FILE"?

Deschide răspunsul

Ghilimelele duble păstrează rezultatul expansiunii $FILE ca un singur argument, chiar dacă conține spații — împiedică împărțirea în cuvinte și expansiunea18 numelor de fișiere, deși valoarea variabilei se înlocuiește în continuare. Expansiunea este înlocuirea $FILE cu valoarea lui de către shell8, înainte ca programul să pornească (secțiunea 6).

Exemplu concret: dacă FILE conține raport final.txt, comanda cp "$FILE" backup/ primește un singur argument cu spațiul lui — iar fără ghilimele shell-ul8 îl rupe în două argumente, „raport” și „final.txt”, și expandează orice * din nume în liste de fișiere.

Capcana: ghilimelele simple '…' opresc și expansiunea18 — shell-ul8 ar transmite literal $FILE, nu valoarea. Regula practică: ghilimele duble în jurul fiecărei variabile din comenzi. Pentru constructorul de agenți1, asta este diferența dintre un agent care șterge „raport final.txt” și unul care șterge din greșeală „raport” și „final.txt” — și orice alt fișier care se potrivește cu tiparul.

5

Ce descriptor este stdout11?

Deschide răspunsul

stdout11 este descriptorul 1 — fluxul standard prin care procesul scrie datele sale de ieșire. Descriptorii de fișier sunt numerele mici prin care procesul referă obiectele deschise (secțiunea 7): 0 este stdin10 (intrarea), 1 este stdout (ieșirea normală), 2 este stderr12 (ieșirea pentru erori).

Exemplu concret: comanda echo text scrie „text” pe descriptorul 1; redirecționarea echo text > fișier.txt mută acel flux 1 în fișier, iar 2> mută separat fluxul 2. De aceea poți separa rezultatele de erori: ele sunt două fluxuri diferite, cu numere diferite.

Capcana: să crezi că erorile apar „în același loc” cu rezultatele — în terminal9 ambele sunt desenate la fel, dar sunt fluxuri separate. Pentru constructorul de agenți1, distincția decide ce ajunge unde: rezultatele pe stdout11 pentru alte programe, erorile pe stderr12 pentru oameni și jurnale23 — amestecarea lor strică pipe13-urile și logurile.

6

A | B trimite stderr-ul12 lui A către B?

Deschide răspunsul

Nu — pipe-ul13 obișnuit conectează stdout-ul11 lui A la stdin-ul10 lui B; stderr-ul12 lui A nu trece prin el. Pipe-ul (secțiunea 8.2) este canalul dintre două procese: ce scrie A pe descriptorul 1 ajunge direct ca intrare (descriptorul 0) la B.

Exemplu concret: grep error fisier.log | wc -l trimite rezultatele lui grep către wc -l, care le numără; dacă grep scrie și o eroare pe stderr12, aceasta sare peste pipe13 și apare direct în terminal9 — de aceea erorile nu sunt „numărate” din greșeală.

Capcana: să crezi că erorile „au dispărut” când canalizezi ieșirea — ele doar au luat alt drum, cel al descriptorului 2. Pentru constructorul de agenți1, asta dă controlul fin: 2>&1 combină erorile cu rezultatele când vrei să le prindeți împreună, iar separate, lăsate în afara pipe13-ului, când vrei să le vezi separat.

7

Ce înseamnă, în mod convențional, codul de ieșire 0?

Deschide răspunsul

Codul de ieșire 0 înseamnă, prin convenție, succes; orice valoare nenulă semnalează un eșec sau o stare4 specială definită de program. Codul de ieșire este numărul pe care procesul îl întoarce sistemului la terminare (secțiunea 9) — este canalul prin care o comandă comunică rezultatul, fără text.

Exemplu concret: grep întoarce 0 când a găsit potriviri, 1 când nu a găsit și 2 la eroare — trei rezultate diferite, fără niciun cuvânt afișat. De aceea operatorii && și || funcționează: ei citesc codul, nu textul (secțiunea 9.1).

Capcana: să interpretezi „nu a scris nimic” ca „a reușit” — exact greșeala din secțiunea 20. Pentru constructorul de agenți1, regula decide cum îți verifici propriile comenzi: după $? și după codurile documentate, nu după lipsa mesajelor de eroare.

8

De ce este util &&?

Deschide răspunsul

&& exprimă o dependență: comanda din dreapta rulează numai dacă cea din stânga a reușit — adică numai dacă codul ei de ieșire a fost 0. Operatorul citește codul de ieșire al comenzii din stânga (secțiunea 9.1) și decide dacă lanțul continuă; perechea lui, ||, rulează dreapta numai la eșec.

Exemplu concret: apt update && apt upgrade — actualizarea indexului rulează întâi, iar upgrade-ul pornește numai dacă indexul s-a actualizat cu succes; dacă primul pas eșuează, al doilea nu mai pornește deloc.

Capcana: ; separă comenzi care rulează întotdeauna, una după alta, indiferent de rezultat — de aceea „comandă 1; comandă 2” și „comandă 1 && comandă 2” nu sunt echivalente. Pentru constructorul de agenți1, && este forma cea mai simplă a dependenței pe care lecția o generalizează: pasul următor pornește numai dacă precedentul a reușit.

9

De ce folosim source .venv/bin/activate?

Deschide răspunsul

source .venv/bin/activate rulează scriptul în interiorul shell-ului8 curent, astfel încât el să modifice mediul acestuia — în special variabila PATH, care primește folderul .venv/bin în față. source (sau .) nu pornește un proces separat: el citește fișierul și îl execută în shell-ul existent (secțiunea 10.1).

Exemplu concret: după activare, which python3 afișează .venv/bin/python3 — interpretul mediului virtual —, iar pip instalează în mediul proiectului, nu în sistem. Un proces separat (de pildă bash activate) ar modifica mediul propriu și s-ar termina, lăsând shell-ul8 tău neatins: exact de ce activarea trebuie făcută cu source, nu prin execuție directă.

Capcana: să crezi că mediul „se transmite înapoi” către shell-ul8 părinte — el se transmite numai de la părinte către copil (secțiunea 10.1). Pentru constructorul de agenți1, asta explică de ce activarea mediului nu „strică” procesul agentului și de ce un serviciu22 systemd21 își construiește mediul prin fișierul unit, nu prin activare în terminal9.

10

Ce face apt update?

Deschide răspunsul

apt update actualizează indexul local al pachetelor disponibile, pornind de la sursele de pachete configurate — și nu actualizează pachetele instalate. Indexul este lista locală „ce pachete există, în ce versiuni, de unde se descarcă”; sursele sunt adresele de unde se citește lista (secțiunea 11.1).

Exemplu concret: după apt update, apt list --upgradable arată ce pachete au versiuni noi; instalarea lor efectivă se face cu apt upgrade — o comandă separată, care descarcă și înnoiește pachetele propriu-zise.

Capcana: să rulezi doar apt update și să crezi că sistemul „e la zi” — ai actualizat doar lista, nu pachetele. Pentru constructorul de agenți1, separarea celor două comenzi este un model: o comandă citește starea4 lumii externe, alta aplică schimbări — exact distincția dintre observare și acțiune din Lecția 2.

11

De ce este apt-get preferabil lui apt în scripturi?

Deschide răspunsul

apt-get este preferabil în scripturi pentru că interfața lui este gândită pentru programe: comportamentul și afișajul lui rămân stabile între versiuni, pe când apt este gândit pentru oameni. apt28 este comanda modernă, prietenoasă — cu progres animat și mesaje prietenoase — dar compatibilitatea ieșirii ei între versiuni nu este garantată; apt-get păstrează contractul său mai îngust, dar stabil (secțiunea 11.1).

Exemplu concret: un script care parsa ieșirea apt poate rupe la o versiune nouă a managerului, deși pachetele funcționează identic; același script pe apt-get rămâne valabil.

Capcana: să alegi comanda „mai nouă și mai frumoasă” pentru automatizare. Pentru constructorul de agenți1, regula este generală: instrumentele3 cu care interacționează un agent — nu un om — trebuie să aibă interfețe stabile, documentate și previzibile, iar cele prietenoase cu omul rămân pentru terminalul9 tău.

12

systemctl enable pornește serviciul22 imediat?

Deschide răspunsul

Nu — systemctl enable configurează doar pornirea automată la boot; serviciul22 nu pornește acum; dacă intenția ta este asta, folosești enable --now. enable creează legătura care pornește serviciul la fiecare pornire a sistemului; start îl pornește imediat, o singură dată (secțiunea 13.1).

Exemplu concret: systemctl enable agent.service lasă serviciul22 neatins acum, dar pornit la următorul boot; systemctl start agent.service îl pornește acum, dar dispare la reboot; systemctl enable --now agent.service face ambele.

Capcana: exact greșeala din secțiunea 20 — „enable” citit ca „pornește acum”. Pentru constructorul de agenți1, distincția decide ce vezi după comandă: dacă agentul „nu a pornit” după enable, nu e o eroare — e comportamentul corect al unei comenzi care programează viitorul, nu prezentul.

13

Ce poate colecta journald de la un serviciu22 systemd21?

Deschide răspunsul

journald poate colecta stdout11 și stderr12 ale unităților de serviciu22 systemd21, împreună cu metadate structurate despre fiecare înregistrare. journald este serviciul de jurnal23 din systemd (secțiunea 14): el primește ce scriu procesele pe descriptorii 1 și 2 și adaugă lângă fiecare înregistrare câmpuri structurate — dată, unitate, PID, cod de prioritate.

Exemplu concret: un agent1 servit de systemd21 care scrie „verificare terminată” pe stdout11 ajunge în jurnal23 cu ora, numele unității și PID-ul atașate — căutabil cu journalctl -u agent.service, fără fișiere de log gestionate manual.

Capcana: să crezi că jurnalul23 conține doar textul primit — metadatele structurate sunt ce îl fac căutabil și corelabil. Pentru constructorul de agenți1, asta înseamnă că agentul servit de systemd21 primește jurnal automat: nu trebuie să construiești logarea, ci să scrii în stdout11/stderr12 informații care merită găsite mai târziu.

14

Ce verifică known_hosts?

Deschide răspunsul

known_hosts verifică dacă serverul SSH25 este cel pe care clientul îl cunoaște deja, după cheia lui de gazdă — nu verifică dacă cheia utilizatorului este autorizată pe server. Cheia de gazdă26 (host key) este amprenta publică a serverului; known_hosts este fișierul în care clientul SSH păstrează amprentele serverelor de încredere (secțiunea 15.1).

Povestea celor două verificări, în ordine: la conectare, serverul își prezintă cheia de gazdă26, iar clientul o compară cu intrarea din known_hosts — asta dovedește serverul; apoi utilizatorul se autentifică cu cheia lui privată, iar serverul verifică dacă cheia publică potrivită e autorizată — asta dovedește utilizatorul. Două verificări diferite, în direcții diferite.

Capcana: să confunzi „serverul e cel bun” cu „sunt eu cel bun pentru el”. Pentru constructorul de agenți1, asta decide ce securizezi: known_hosts protejează agentul de un server fals, iar autorizarea cheii pe server protejează serverul de agenți neinvitați — ambele trebuie să fie corecte.

15

Care este primul lucru pe care îl compari când un program merge manual, dar nu ca serviciu22?

Deschide răspunsul

Primul lucru comparat este contextul de execuție: identitatea procesului, folderul curent, executabilul și PATH-ul15, variabilele de mediu, permisiunile — apoi logurile, care arată diferența concretă. Contextul de execuție este setul de condiții sub care rulează procesul: cine (UID/GID și grupuri), unde (CWD16), ce programe poate găsi (PATH), ce variabile primește (secțiunea 16).

Exemplu concret din lecție: agentul1 citește config.json cu succes în terminal9 și eșuează ca serviciu22 — pentru că serviciul are alt folder curent de lucru. Comanda pwd rulată în ambele contexte arată diferența imediat, iar journalctl arată exact ce a încercat procesul de serviciu să deschidă.

Capcana: să compari „comenzile” în loc de „contextele” — aceleași comenzi cu alte credențiale și căi dau alte rezultate. Pentru constructorul de agenți1, lista aceasta este o listă de verificare: fiecare element poate fi inspectat cu o comandă, iar diferența găsită acolo este cauza reală, nu ghicirea.

25. Recapitulare — de la text tastat la proces controlabil

Lecția nu este o listă de comenzi. Este un model al drumului dintre intenție și execuție.

Terminalul9 afișează; shell-ul8 interpretează

Nu atribui terminalului regulile de folosire a ghilimelelor, rezolvarea prin PATH15 sau variabilele shell-ului8.

Procesul are context

CWD16, UID/GID, PATH15, mediu și descriptorii schimbă comportamentul aceleiași aplicații.

Calea relativă depinde de CWD16

Un serviciu22 sau o sesiune SSH25 poate porni din alt loc decât terminalul9 tău.

Ghilimelele separă datele de sintaxă

Un argument trebuie să rămână un argument, nu să fie reinterpretat accidental de shell8.

stdin10/stdout11/stderr12 sunt fluxuri separate

Pipe-ul13 obișnuit transmite stdout11; redirecționările controlează descriptorii.

Codul de ieșire este semnalul programatic

Automatizarea trebuie să urmărească succesul, nu doar textul afișat.

Mediul se transmite explicit

export, serviciile și sesiunile la distanță determină ce variabile vede procesul.

Instalarea are straturi

APT28/dpkg administrează pachete de sistem; managerii de pachete ai ecosistemelor de limbaj au altă responsabilitate.

Start ≠ enable

Starea4 curentă a serviciului și activarea lui viitoare sunt axe separate.

Logurile sunt dovadă operațională

Folosește journalctl24 și filtre, dar nu transforma logurile în depozit de secrete.

SSH25 verifică două identități

Cheia de gazdă26 pentru server, autentificare separată pentru utilizator.

Reproductibilitatea începe cu context explicit

Alt utilizator trebuie să poată afla de unde pornește, ce dependențe are și unde caută eroarea.

Formula de o pagină: shell-ul8 interpretează textul într-un context. Contextul include CWD16, PATH15 și mediul procesului. Comenzile primesc stdin10 și argumente, produc stdout11/stderr12 și un cod de ieșire14. Pipe13-urile și redirecționările compun aceste fluxuri. APT28 instalează software de sistem, systemd21 administrează servicii22, journald păstrează evenimente, iar SSH25 mută execuția pe alt sistem. În toate cazurile, procesul și permisiunile din Lecția 5 rămân fundamentul.

Ce simplifică intenționat lecția: folosim numai Bash; Zsh și Fish au reguli de sintaxă proprii. Nu intrăm în scripturi lungi, în detaliile lui set -e, în scrierea fișierelor de unitate systemd21 sau în configurarea serverului SSH25. Scopul Lecției 6 este modelul: context → interpretare → proces → fluxuri → cod de ieșire14 → serviciu22 → loguri. Lecțiile următoare adaugă detalii peste el, fără să îl înlocuiască.
Recapitularea Lecției 6 ca hartă în jurul shell-ului Bash: căi și CWD, ghilimele și expansiuni, PATH, stdin, stdout și stderr, pipe-uri și redirecționări, cod de ieșire, mediu și export, APT, systemd și journald, SSH, cu ideea că contextul explicit produce o execuție reproductibilă.
Zece concepte în jurul aceluiași principiu: context explicit, execuție reproductibilă.

Vocabular esențial

Recitește definițiile după parcurgerea lecției — acum fiecare termen are context. Sunt grupate în trei familii: termenii moșteniți din Lecțiile 1–4, componentele liniei de comandă și mecanismele de operare. Apasă pe numărul de lângă un termen ca să ajungi la explicația lui; butonul Înapoi al browserului te duce înapoi unde citeai.

Vocabular din Lecțiile 1–4

TermenCe înseamnă
1 AgentSistem care urmărește un obiectiv, păstrează stare și alege dinamic acțiunile următoare, până la o condiție de oprire. Procesele din această lecție sunt materiile prime din care se construiește. Detaliat în Lecția 1.
2 PromptInstrucțiunea trimisă modelului. Logarea ei este utilă pentru reconstituirea deciziilor, dar promptul nu este log și nu trebuie să conțină secrete. Detaliat în Lecția 1.
3 InstrumentCapacitate pe care agentul o poate apela: o comandă, un script, un API. În această lecție, instrumentele sunt comenzi de shell. Detaliat în Lecția 1.
4 StareInformația păstrată despre sarcina curentă: CWD, variabile exportate, fișiere deschise. Secțiunea 4.2 arată că folderul curent face parte din starea procesului. Detaliat în Lecția 1.
5 APIInterfața prin care un program cere date sau acțiuni altui program. Cheile API autentifică aceste cereri — și nu trebuie să ajungă în loguri. Detaliat în Lecția 1.
6 ControllerComponenta care coordonează bucla agentului: menține starea, alege pașii, aplică permisiunile. În laboratoarele 2 și 5 controllerul a fost construit direct în Python. Detaliat în Lecția 2.
7 Prompt injectionSituația în care datele citite de agent conțin text care încearcă să se comporte ca instrucțiune. Logarea completă a unui mediu ostil publicează exact textul acela. Detaliat în Lecția 2.

Componentele liniei de comandă

TermenCe înseamnă
8 ShellProgramul care interpretează limbajul de comandă: citește textul, îl expandează și pornește programele. Ca traducătorul dintre tine și sistem: tu scrii, el traduce în acțiuni ale sistemului. Shell-ul nu este fereastra în care scrii — aceea este terminalul.
9 TerminalAplicația grafică care desenează fereastra de text și trimite tastele apăsate către shell. Ca ecranul și tastatura unui bancomat: transportă mesajele, nu le interpretează. Terminalul nu decide nimic — toate deciziile sunt ale shell-ului și ale programelor pornite.
10 StdinFluxul standard de intrare al unui proces — descriptorul 0: de aici primesc datele. Ca țeava de intrare a unui filtru de apă. stdin-ul nu este tastatura neapărat — poate fi ieșirea altei comenzi sau un fișier.
11 StdoutFluxul standard de ieșire — descriptorul 1: aici procesul scrie rezultatele. Ca tubul de ieșire al filtrului: apa limpede iese pe aici. stdout-ul nu este ecranul neapărat — poate fi redirecționat într-un fișier sau într-un pipe.
12 StderrFluxul standard pentru erori — descriptorul 2: separat de stdout exact pentru ca erorile să nu se amestece cu rezultatele. Ca tubul roșu al filtrului: alte ape, alt drum. stderr-ul nu este doar „mesaje pentru oameni” — alte programe îl pot citi la fel.
13 PipeCanalul care conectează stdout-ul unei comenzi la stdin-ul alteia: primul scrie, al doilea citește, simultan. Ca o țeavă între două filtre: apa curge în lanț. Pipe-ul nu transportă stderr — acela are propriul drum, dacă îl redirecționezi explicit.
14 Cod de ieșireNumărul pe care procesul îl întoarce sistemului la terminare: 0 prin convenție înseamnă succes, nenul înseamnă eșec sau stare specială. Ca bonul unui aparat: spune dacă tranzacția a trecut. Codul de ieșire nu este textul afișat — tăcerea nu înseamnă succes.
15 PATHLista ordonată de foldere în care shell-ul caută comenzi fără cale. Ca agenda de numere a unui interfon: numele caută în ordine, iar primul găsit câștigă. PATH-ul nu include folderul curent — tocmai de aceea scripturile se pornesc cu ./.

Operare: pachete, servicii, loguri și acces la distanță

TermenCe înseamnă
16 CWDCurrent working directory: folderul din care procesul interpretează căile relative. Ca locul în care stai când dai indicații: „a doua ușă pe stânga” înseamnă altceva din fiecare cameră. CWD-ul nu este rădăcina proiectului — e doar de unde pornește procesul acum.
17 Variabilă de mediuValoare numită care se copiază din shell în fiecare proces-copil la pornire — de pildă PATH sau API_URL. Ca rucsacul primit la plecare: pleacă de la părinte la copil, niciodată înapoi. Variabilele shell neexportate nu ajung în mediu.
18 ExpansiuneÎnlocuirea făcută de shell înainte ca programul să pornească: variabile devin valori, tipare devin liste de fișiere. Ca traducerea textului înainte de trimitere: programul nu vede niciodată $FILE, ci rezultatul expansiunii. Expansiunea nu este magie de afișare — se face complet, înainte de execuție.
19 GlobbingExpansiunea numelor de fișiere: shell-ul transformă tipare precum *.txt în lista fișierelor care se potrivesc. Ca secretarul care desface un plic adresat „tuturor din etajul 2” în plicuri individuale. Globbing-ul nu este filtrul programului — programul primește deja lista desfăcută.
20 Substituție de comandăMecanismul prin care ieșirea unei comenzi devine parte din linia altei comenzi: $(comandă). Ca notița pe care o scrii cu rezultatul unei întrebări și o lipești în următoarea cerere. Substituția nu este pipe — una compune text, cealaltă conectează fluxuri.
21 SystemdManagerul de servicii și alte unități din Ubuntu: pornește procese la boot, le supraveghează și le ține jurnalul. Ca administrația blocului care aprinde scările și ține registrul de service. systemd nu este shell — el gestionează procese, nu interpretează comenzi.
22 ServiciuProces administrat de systemd printr-un fișier unit: pornire la boot, repornire la eșec, jurnal centralizat. Ca un chelner cu ture programate: nu depinde de cineva care stă în terminal. Un serviciu nu are terminal — și tocmai de aceea contextul lui de execuție trebuie definit explicit.
23 JurnalÎnregistrarea a ceea ce a scris procesul: evenimente, erori, stări. Ca fișa medicală: reconstituie ce s-a întâmplat și când. Jurnalul nu este un seif — nu trebuie să conțină chei API sau date personale.
24 JournalctlComanda prin care interoghezi jurnalul systemd: filtrezi după unitate, timp sau text. Ca motorul de căutare al fișei medicale: cauți pe pacient, pe dată, pe simptom. journalctl nu modifică nimic — doar citește.
25 SSHSecure Shell: protocolul prin care execuți comenzi pe alt sistem, cu autentificare și criptare. Ca linia telefonică criptată către alt birou: vorbirea e securizată, dar regulile biroului rămân ale lui. SSH-ul nu transferă automat mediul tău local — la distanță, PATH și CWD sunt ale sistemului de acolo.
26 Cheie de gazdăAmprenta publică prin care serverul SSH își dovedește identitatea la conectare. Ca holograma unui card bancar: confirmă că instituția e reală. Cheia de gazdă nu este cheia ta privată — una dovedește serverul, alta te dovedește pe tine.
27 SudoMecanismul care poate porni o comandă sub altă identitate — de obicei root — conform unei politici scrise. Ca portarul care verifică lista înainte să deschidă o ușă. sudo nu este „permisiunea root” și nu face automat o acțiune corectă.
28 AptManagerul de pachete Debian/Ubuntu: instalează, actualizează și scoate software-ul de sistem, cu rezolvarea dependențelor. Ca piața centrală a pachetelor: listează ce există și de unde vine. apt update nu este apt upgrade — primul actualizează lista, al doilea pachetele.
29 PipelineLanțul de comenzi conectate prin pipe-uri, în care ieșirea fiecăreia devine intrarea următoarei. Ca linia de montaj: produsul trece prin toate posturile. Fără pipefail, starea raportată este cea a ultimei comenzi — nu a întregului lanț.
30 MonitorizareUrmărirea continuă a stării și a comportamentului unui sistem: jurnale, metrici, alerte. Ca camerele de supraveghere și senzorii unui bloc: înregistrează ce se întâmplă, nu doar ce se pretinde. Monitorizarea nu este același lucru cu securitatea — o ajută, dar nu înlocuiește permisiunile.

26. Referințe și verificare tehnică

Afirmațiile tehnice din această lecție au fost verificate în documentația oficială și în manualele listate mai jos, cu o reverificare în octombrie 2026. Pentru mecanismele care produc frecvent confuzii am comparat documentația proiectului cu documentația Ubuntu sau cu standardul POSIX, după caz.

27. Ce urmează

Lecția 6 ți-a dat limbajul operațional al sistemului: poți descrie unde rulează o comandă, cum ajung datele în proces, cum găsește executabilul, cum este transmis mediul, cum este administrat un serviciu22 și unde îi citești logurile.

Lecția 7 — Python, de la zero la util va muta aceeași disciplină într-un limbaj de programare: tipuri, funcții, clase, module, excepții, medii și pachete. Când vei scrie primul instrument3 tipizat, vei înțelege deja de unde vine procesul Python, ce executabil rulează, ce mediu primește și cum îi interpretezi codul de ieșire.