Piping i redirekcija

Ovo je ono što od Linuxa pravi Linux. Nijedna komanda ne radi sve — ali se male komande spajaju u lanac koji rešava problem za koji na drugom sistemu treba program.

Cela ideja stoji na jednom pravilu: svaki program ima jedan ulaz i dva izlaza, i sva tri se mogu preusmeriti gde god hoćeš. Kad to razumeš, ostatak je sintaksa.

Tri toka

Kad se program pokrene, sistem mu automatski otvori tri kanala:

0stdinstandardni ulaz — podrazumevano tastatura
1stdoutstandardni izlaz — podrazumevano ekran
2stderrstandardna greška — takođe ekran

Brojevi nisu ukras; koristićeš ih doslovno u komandama. Zato 2> znači „preusmeri kanal broj 2", odnosno greške.

Zašto su izlaz i greška razdvojeni, iako oba završe na ekranu? Da bi rezultat mogao da se prosledi dalje, a poruke o problemu da i dalje stignu do tebe. To je razlika koju ćeš najlakše videti na primeru:

$ ls postoji.txt nepostoji.txt
ls: cannot access 'nepostoji.txt': No such file or directory
postoji.txt

$ ls postoji.txt nepostoji.txt > spisak.txt
ls: cannot access 'nepostoji.txt': No such file or directory

$ cat spisak.txt
postoji.txt

U fajl je otišao samo izlaz. Greška je ostala na ekranu jer je putovala drugim kanalom.

Izlaz u fajl: > i >>

$ echo "prvi red" > dnevnik.txt
$ echo "drugi red" > dnevnik.txt
$ cat dnevnik.txt
drugi red

> briše postojeći sadržaj. Za dodavanje na kraj koristi se >>:

$ echo "prvi red" > dnevnik.txt
$ echo "drugi red" >> dnevnik.txt
$ cat dnevnik.txt
prvi red
drugi red

Dve stvari koje ovde treba primetiti, jer se na njima gubi vreme:

Prvo: preusmeravanje ne radi komanda, nego shell — isto kao sa džokerima iz lekcije Džokeri i navodnici. Shell otvori fajl pre nego što pokrene komandu.

Drugo, i mnogo bolnije: pošto shell fajl otvori i isprazni pre pokretanja komande, ovo uništava podatke:

$ sort spisak.txt > spisak.txt
$ cat spisak.txt
$

Fajl je prazan. Shell ga je ispraznio, pa je sort dobio prazan ulaz i u njega upisao ništa. Nema oporavka. Ispravno je preko privremenog fajla:

$ sort spisak.txt > /tmp/sortirano.txt && mv /tmp/sortirano.txt spisak.txt

Neki alati imaju ugrađen izlaz iz ovog problema: sort -o spisak.txt spisak.txt ili sed sa opcijom -i.

Zaštita od slučajnog prepisivanja

$ set -o noclobber
$ echo test > dnevnik.txt
bash: dnevnik.txt: cannot overwrite existing file

Kad ti stvarno treba prepisivanje, koristi >|. Red set -o noclobber može trajno u ~/.bashrc, ali imaj u vidu da ćeš na tuđem serveru raditi bez te mreže.

Greške: 2>

$ find /etc -name "*.conf" 2> /dev/null
/etc/ssh/ssh_config
/etc/sysctl.conf
...

/dev/null je sistemska „kanta" — sve što u nju upišeš nestaje. Ovo je najčešća upotreba 2>: kad find pretražuje sistem kao običan korisnik, polovina ispisa je Permission denied i zatrpava rezultate.

Kad hoćeš suprotno — samo greške, bez rezultata:

$ find /etc -name "*.conf" > /dev/null

Greške u fajl, za kasniju analizu:

$ ./instalacija.sh 2> greske.log

Oba toka na isto mesto

Ovaj oblik je najkraći i radi u bash-u:

$ ./instalacija.sh &> sve.log
$ ./instalacija.sh &>> sve.log

Klasičan, prenosiv oblik koji ćeš sretati u tuđim skriptama:

$ ./instalacija.sh > sve.log 2>&1

2>&1 se čita kao „kanal 2 neka ide tamo gde trenutno ide kanal 1". Reč trenutno je ključna, jer redosled je bitan:

$ ./skripta.sh 2>&1 > sve.log

Ovo ne radi ono što izgleda. Kad se izvrši 2>&1, kanal 1 još uvek ide na ekran — pa greške odlaze na ekran. Tek posle toga se izlaz preusmerava u fajl. Rezultat: u fajlu je samo izlaz, greške su na ekranu.

Pravilo koje vredi zapamtiti: 2>&1 uvek ide posle preusmeravanja izlaza.

Ulaz iz fajla: <

$ wc -l < spisak.txt
42

Razlika u odnosu na wc -l spisak.txt je suptilna ali korisna: kad se koristi <, komanda ne zna ime fajla, pa ga ne ispisuje uz rezultat. Zgodno kad ti treba samo broj, na primer u skripti.

Heredoc — više redova teksta u komandu

$ cat > podesavanja.conf << EOF
Port 2222
PermitRootLogin no
EOF

$ cat podesavanja.conf
Port 2222
PermitRootLogin no

Sve do reda koji sadrži samo EOF ide na ulaz komande. Oznaka ne mora biti baš EOF, to je samo običaj. Ako ne želiš da shell dira promenljive unutar bloka, stavi oznaku pod navodnike:

$ cat << 'EOF'
Cena je $5, a putanja je $HOME
EOF
Cena je $5, a putanja je $HOME

Herestring — jedan red

$ grep -c "linux" <<< "linux je linux i ostaje linux"
1

Cev: |

Do sada smo tokove vezivali za fajlove. Cev radi nešto drugo — spaja izlaz jedne komande sa ulazom druge, bez fajla između:

$ ps aux | grep nginx

Nema privremenog fajla, nema čekanja da prva komanda završi. Podaci teku kroz memoriju dok obe komande rade uporedo. Zato ovo radi trenutno i na ogromnom fajlu:

$ cat /var/log/syslog | head -3

head uzme tri reda i završi, a cat dobije signal da prestane. Nikad se ne pročita ceo fajl.

Lanac može biti proizvoljno dugačak. Klasičan primer — deset IP adresa sa najviše neuspelih prijava:

$ grep "Failed password" /var/log/auth.log | awk '{print $11}' | sort | uniq -c | sort -rn | head
    847 203.0.113.45
    312 198.51.100.7
     28 192.0.2.19

Pet komandi, svaka radi jednu stvar: grep izdvaja redove, awk uzima jedanaestu kolonu, sort grupiše iste, uniq -c broji, drugi sort ređa opadajuće po broju.

Savet iz prakse: ovakav lanac se ne piše odjednom. Pokreni prvu komandu, pogledaj ispis, dodaj drugu, pogledaj ponovo. Kad nešto ne radi u lancu od pet komandi, greška je skoro uvek u drugoj ili trećoj — a mnogo je brže naći je odmah nego kasnije.

Šta se dešava sa greškama u cevi

Kroz cev prolazi samo stdout. Greške i dalje idu na ekran:

$ ls postoji.txt nepostoji.txt | wc -l
ls: cannot access 'nepostoji.txt': No such file or directory
1

Ako hoćeš da i greške prođu kroz cev, spoji ih pre nje:

$ ls postoji.txt nepostoji.txt 2>&1 | wc -l
2

U bash-u postoji i skraćenica |&, koja znači isto.

Izlazni status lanca

Ovo redovno ujeda ljude koji pišu skripte:

$ nepostojeca-komanda | tee izlaz.log
bash: nepostojeca-komanda: command not found
$ echo $?
0

Nula znači uspeh — iako komanda nije ni postojala. Lanac vraća status poslednje komande, a tee je uredno uspeo. Rešenje na vrhu skripte:

set -o pipefail
$ set -o pipefail
$ nepostojeca-komanda | tee izlaz.log
bash: nepostojeca-komanda: command not found
$ echo $?
127

Statuse svih članova lanca pojedinačno drži niz PIPESTATUS:

$ echo "${PIPESTATUS[@]}"
127 0

tee — i na ekran i u fajl

Cev ima jedan izlaz. Kad ti treba i da vidiš ispis i da ga sačuvaš, tu je tee:

$ ./instalacija.sh | tee instalacija.log
Provera zavisnosti...
Instalacija u toku...
Gotovo.

$ ls -l instalacija.log
-rw-r--r-- 1 alen alen 58 Aug 22 12:04 instalacija.log

Opcija -a dodaje umesto da prepisuje. A tee može i usred lanca, kao mesto na kome „prisluškuješ" međurezultat:

$ grep ERROR app.log | tee greske.txt | wc -l
17

Najvažnija upotreba je pisanje u sistemski fajl. Ovo ne radi:

$ sudo echo "127.0.0.1 test.local" >> /etc/hosts
bash: /etc/hosts: Permission denied

Razlog: preusmeravanje obavlja tvoj shell, pre nego što sudo uopšte krene. Sudo se odnosi na echo, a echo nije problem. Rešenje je da sa root pravima radi ono što piše u fajl:

$ echo "127.0.0.1 test.local" | sudo tee -a /etc/hosts > /dev/null

Na kraju je > /dev/null samo zato što bi tee inače ponovio red i na ekranu.

Komande koje ne čitaju sa ulaza

$ ls *.txt | rm
rm: missing operand

rm ne čita stdin — očekuje imena kao argumente. Cev ne pomaže. Za takve komande postoji xargs, koji ono što stigne sa ulaza pretvara u argumente:

$ find . -name "*.tmp" | xargs rm

Bezbednija varijanta, koja preživljava imena sa razmacima:

$ find . -name "*.tmp" -print0 | xargs -0 rm

Isto pravilo važi i za echo: echo ne čita ulaz, pa ls | echo ispiše prazan red. Ako ti treba da vidiš šta prolazi kroz cev, koristi cat ili tee.

Sitnice koje zbunjuju

Boje nestanu kad se ispis preusmeri

$ grep --color=auto ERROR app.log | less

Boje su nestale. Alati proveravaju da li pišu na terminal i sami gase bojenje kad ne pišu. Ako ih hoćeš i kroz cev:

$ grep --color=always ERROR app.log | less -R

-R govori pejdžeru da propusti kontrolne znakove — o čemu ima više na stranici less i more.

Ispis kroz cev kasni

$ tail -f app.log | grep ERROR

Ništa se ne pojavljuje minutima, iako greške stižu. Kad piše na terminal, grep šalje red po red; kad piše u cev, čeka da se napuni bafer od nekoliko kilobajta. Rešenje:

$ tail -f app.log | grep --line-buffered ERROR

Opštije, za alate bez te opcije, postoji stdbuf -oL. Više o praćenju logova na stranici tail.

Promenljiva iz cevi „nestane"

$ cat spisak.txt | while read red; do broj=$((broj+1)); done
$ echo $broj

Svaki član lanca radi u zasebnom potprocesu, pa promenljiva postavljena unutra ne postoji napolju. Zaobilaženje je preusmeravanje umesto cevi:

$ while read red; do broj=$((broj+1)); done < spisak.txt
$ echo $broj
42

cat koji ništa ne radi

$ cat app.log | grep ERROR      # nepotrebno
$ grep ERROR app.log            # isto, jedan proces manje

Prvi oblik nije greška i vidi se svuda, ali grep i skoro svi alati za obradu teksta i sami umeju da otvore fajl.

Kratka referenca

  • > fajl — izlaz u fajl, prepisuje
  • >> fajl — izlaz u fajl, dodaje na kraj
  • 2> fajl — greške u fajl
  • 2> /dev/null — odbaci greške
  • &> fajl — i izlaz i greške u fajl
  • > fajl 2>&1 — isto, prenosivi oblik (redosled bitan)
  • < fajl — ulaz iz fajla
  • << EOF — više redova teksta na ulaz
  • <<< "tekst" — jedan red na ulaz
  • k1 | k2 — izlaz prve na ulaz druge
  • k1 2>&1 | k2 — kroz cev prolaze i greške
  • | tee fajl — i na ekran i u fajl
  • | sudo tee -a fajl — dopisivanje u sistemski fajl
  • | xargs komanda — ulaz pretvoren u argumente
  • set -o pipefail — lanac prijavi grešku bilo kog člana

Vežba

$ cd /var/log

Bez gledanja u rešenje, sastavi komande za sledeće:

  1. Prebroj koliko ukupno redova ima syslog, bez ispisivanja imena fajla.
  2. Pronađi deset najvećih fajlova u /var/log, a poruke o nedostatku dozvola sakrij.
  3. Izlistaj sve različite programe koji su pisali u syslog danas, sortirane po broju poruka. (Peta kolona je obično ime procesa — proveri sa cut ili awk.)
  4. Isto kao pod 3, ali rezultat neka istovremeno ode i na ekran i u fajl ~/izvestaj.txt.
  5. Dopiši red # provera na kraj fajla /etc/hosts, kao običan korisnik.

Peti zadatak ima tačno jedno ispravno rešenje i cela lekcija vodi ka njemu.


Komande koje se najčešće nalaze u lancima obrađene su na stranicama grep, sort, cut, awk i tee.

Cela lekcija je deo vodiča Linux osnove.

Comments

Popular posts from this blog

Početak u Linuxu — šta je i zašto se uči

Konverzija tipova podataka u Pythonu

groupadd