“Pogledajte ovdje”, kaže, pokazujući na jedan od simbola na ekranu, “kladim se u svoj crveni šešir da ako ovdje dodamo ono što sam vam upravo poslao,” pokazujući na drugi dio koda, “pogreška više neće biti bit će prikazan."
Pomalo zbunjen i umoran, modificiram sed izraz na kojem smo radili neko vrijeme, spremim datoteku i pokrenem systemctl varnish reload
. Poruka o pogrešci je nestala...
“E-mailovi koje sam razmijenio s kandidatom,” nastavio je moj kolega, dok je njegov smiješak prerastao u iskren osmijeh radosti, “Odjednom mi je sinulo da je to potpuno isti problem!”
Kako je sve počelo
Članak pretpostavlja razumijevanje načina rada bash, awk, sed i systemd. Poželjno je poznavanje lakiranja, ali nije obavezno.
Vremenske oznake u isječcima su promijenjene.
Napisano sa
Ovaj tekst je prijevod originala objavljenog na engleskom prije dva tjedna; prijevod
Sunce sija kroz panoramske prozore još jednog toplog jesenskog jutra, šalica svježe pripremljenog napitka bogatog kofeinom odmara se podalje od tipkovnice, omiljena simfonija zvukova zvuči u vašim slušalicama, prigušujući šuštanje mehaničkih tipkovnica, a prvi unos na popisu zaostalih tiketa na Kanban ploči zaigrano svijetli sudbonosnim naslovom “Istražite varnishreload” sh: echo: I/O error in staging” (Istražite “varnishreload sh: echo: I/O error” in staging). Kod lakiranja nema i ne može biti mjesta greškama, čak i ako one ne stvaraju probleme kao u ovom slučaju.
Za one koji nisu upoznati sa
Kao što naslov ulaznice sugerira, greška se dogodila na jednom od servera na pozornici, a kako sam bio siguran da rutiranje laka na pozornici radi kako treba, pretpostavio sam da se radi o minornoj grešci. Dakle, samo poruka koja je završila u već zatvorenom izlaznom toku. Uzimam kartu za sebe, u punom uvjerenju da ću je označiti spremnom za manje od 30 minuta, potapšam se po leđima što sam očistio ploču od još jednog smeća i vratim se važnijim stvarima.
Zabijanje u zid brzinom od 200 km/h
Otvaranje datoteke varnishreload
, na jednom od poslužitelja koji pokreće Debian Stretch, vidio sam shell skriptu manju od 200 redaka.
Pregledavajući skriptu, nisam primijetio ništa što bi moglo rezultirati problemima pri pokretanju više puta izravno s terminala.
Ipak je ovo pozornica, ako i pukne, nitko se neće buniti, pa... ne previše. Pokrenem skriptu i vidim što će biti upisano na terminal, ali greške se više ne vide.
Još nekoliko pokretanja da se uvjerim da ne mogu reproducirati pogrešku bez ikakvih dodatnih napora i počinjem smišljati kako promijeniti ovu skriptu i učiniti da i dalje izbacuje pogrešku.
Može li skripta nadjačati STDOUT (pomoću > &-
)? Ili STDERR? Ni jedno ni drugo na kraju nije uspjelo.
Navodno systemd nekako mijenja okruženje pokretanja, ali kako i zašto?
Otvorim vim i uredim varnishreload
, dodajući set -x
točno ispod shebanga, nadajući se da će ispravljanje grešaka skripte baciti malo svjetla.
Datoteka je ispravljena, pa ponovno učitavam lak i vidim da je promjena skroz sve pokvarila... Auspuh je potpuna zbrka, u kojoj ima tone C-like koda. Čak ni listanje po terminalu nije dovoljno da biste pronašli gdje počinje. potpuno sam zbunjena. Može li način otklanjanja pogrešaka utjecati na rad programa pokrenutih u skripti? Ne, to su besmislice. Buba u ljusci? Nekoliko mogućih scenarija juri mi glavom poput žohara u različitim smjerovima. Šalica napitka s kofeinom se trenutno isprazni, brzi odlazak u kuhinju da obnovimo zalihe i... idemo. Otvaram skriptu i pobliže gledam shebang: #!/bin/sh
.
/bin/sh
- ovo je samo simbolička veza za bash, tako da se skripta tumači u POSIX-kompatibilnom načinu, zar ne? Ne tako! Zadana ljuska na Debianu je dash, i točno tako izgleda. /bin/sh
.
# ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Jan 24 2017 /bin/sh -> dash
Kao test, promijenio sam shebang u #!/bin/bash
, izbrisano set -x
i pokušao ponovno. Konačno, nakon naknadnog ponovnog pokretanja lakiranja, u izlazu se pojavila prihvatljiva pogreška:
Jan 01 12:00:00 hostname varnishreload[32604]: /usr/sbin/varnishreload: line 124: echo: write error: Broken pipe
Jan 01 12:00:00 hostname varnishreload[32604]: VCL 'reload_20190101_120000_32604' compiled
Linija 124, evo je!
114 find_vcl_file() {
115 VCL_SHOW=$(varnishadm vcl.show -v "$VCL_NAME" 2>&1) || :
116 VCL_FILE=$(
117 echo "$VCL_SHOW" |
118 awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}' | {
119 # all this ceremony to handle blanks in FILE
120 read -r DELIM VCL_SHOW INDEX SIZE FILE
121 echo "$FILE"
122 }
123 ) || :
124
125 if [ -z "$VCL_FILE" ]
126 then
127 echo "$VCL_SHOW" >&2
128 fail "failed to get the VCL file name"
129 fi
130
131 echo "$VCL_FILE"
132 }
Ali kako se pokazalo, linija 124 je prilično prazna i ne predstavlja interes. Mogao sam samo pretpostaviti da se pogreška dogodila kao dio višerednog niza koji počinje u retku 116.
Što se na kraju upisuje u varijablu? VCL_FILE
kao rezultat izvršavanja gornje pod-ljuske?
Na početku šalje sadržaj varijable VLC_SHOW
, stvoren na liniji 115, slijedeći naredbu kroz cijev. I što se onda tamo događa?
Prvo, tamo se koristi varnishadm
, koji je dio instalacijskog paketa lakiranja, za postavljanje lakiranja bez ponovnog pokretanja.
Podtim vcl.show -v
koristi se za izlaz cijele VCL konfiguracije navedene u ${VCL_NAME}
, na STDOUT.
Za prikaz trenutne aktivne VCL konfiguracije, kao i nekoliko prethodnih verzija konfiguracija usmjeravanja laka koje su još u memoriji, možete koristiti naredbu varnishadm vcl.list
, čiji će izlaz biti sličan donjem:
discarded cold/busy 1 reload_20190101_120000_11903
discarded cold/busy 2 reload_20190101_120000_12068
discarded cold/busy 16 reload_20190101_120000_12259
discarded cold/busy 16 reload_20190101_120000_12299
discarded cold/busy 28 reload_20190101_120000_12357
active auto/warm 32 reload_20190101_120000_12397
available auto/warm 0 reload_20190101_120000_12587
Varijabilna vrijednost ${VCL_NAME}
instaliran je u drugom dijelu skripte varnishreload
na naziv trenutno aktivnog VCL-a, ako postoji. U ovom slučaju to će biti "reload_20190101_120000_12397".
Super, promjenjivo ${VCL_SHOW}
sadrži kompletnu konfiguraciju za lak, za sada jasno. Sada konačno razumijem zašto je ispis crtice set -x
pokazalo se tako pokvarenim - uključivalo je sadržaj rezultirajuće konfiguracije.
Važno je razumjeti da se kompletna VCL konfiguracija često može sastaviti iz nekoliko datoteka. Komentari u stilu C koriste se za prepoznavanje gdje su određene konfiguracijske datoteke uključene u druge, a to je ono o čemu se radi u sljedećem retku isječka koda.
Sintaksa za komentare koji opisuju uključene datoteke je u sljedećem formatu:
// VCL.SHOW <NUM> <NUM> <FILENAME>
Brojevi u ovom kontekstu nisu bitni, zanima nas naziv datoteke.
Što se na kraju događa u močvari naredbi koje počinju na liniji 116?
Idemo vidjeti.
Tim se sastoji od četiri dijela:
- jednostavan
echo
, koji ispisuje vrijednost varijable${VCL_SHOW}
echo "$VCL_SHOW"
awk
, koji traži liniju (zapis) gdje je prvo polje, nakon razbijanja teksta, “//”, a drugo je “VCL.SHOW”.
Awk će ispisati prvi redak koji odgovara ovim uzorcima i zatim odmah zaustaviti obradu.awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
- Blok koda koji pohranjuje vrijednosti polja u pet varijabli, odvojenih razmacima. Peta varijabla FILE prima ostatak retka. Na kraju, posljednji echo ispisuje sadržaj varijable
${FILE}
.{ read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
- Budući da su svi koraci od 1 do 3 zatvoreni u podljusku, ispis vrijednosti
$FILE
bit će zapisano u varijabluVCL_FILE
.
Kao što sugerira komentar na retku 119, ovo služi jedinoj svrsi pouzdanog rukovanja slučajevima u kojima će VCL referencirati datoteke s razmacima u njihovim nazivima.
Komentirao sam izvornu logiku obrade za ${VCL_FILE}
i pokušao promijeniti redoslijed naredbi, ali to nije dovelo ni do čega. Kod mene je sve radilo u redu, ali kada sam pokrenuo servis, javila se greška.
Čini se da se pogreška jednostavno ne može reproducirati prilikom ručnog pokretanja skripte, dok je navodnih 30 minuta već šest puta isteklo, a uz to se pojavio zadatak višeg prioriteta, gurajući druge stvari u stranu. Ostatak tjedna bio je ispunjen raznim zadacima, a tek malo razvodnjen reportažom o sed-u i razgovorom s kandidatom. Problem s greškom u varnishreload
bio nepovratno izgubljen u pijesku vremena.
Tvoj takozvani sed-fu... zapravo je... smeće
Sljedeći tjedan sam imao jedan prilično slobodan dan, pa sam se odlučio ponovno uhvatiti u koštac s ovom kartom. Nadao sam se da je u mom mozgu neki pozadinski proces sve ovo vrijeme tražio rješenje za ovaj problem, a ovaj put ću definitivno shvatiti što se događa.
Budući da jednostavno mijenjanje koda nije pomoglo prošli put, samo sam ga odlučio prepisati počevši od retka 116. U svakom slučaju, postojeći kod je bio glup. I nema apsolutno nikakve potrebe da ga koristite read
.
Ponovno gledanje pogreške:
sh: echo: broken pipe
— echo se pojavljuje na dva mjesta u ovoj naredbi, ali pretpostavljam da je prvo vjerojatniji krivac (ili barem suučesnik). Ni Awk ne ulijeva povjerenje. A u slučaju da doista jest awk | {read; echo}
dizajn dovodi do svih ovih problema, zašto ga ne zamijeniti? Ova jednolinijska naredba ne koristi sve mogućnosti awk-a, pa čak ni ovu dodatnu read
u Dodatku.
Od prošlog tjedna bilo je izvješće o sed
, htio sam isprobati svoje novostečene vještine i pojednostaviti echo | awk | { read; echo}
u razumljiviji echo | sed
. Iako ovo definitivno nije najbolji pristup identificiranju buga, mislio sam barem isprobati svoj sed-fu i možda naučiti nešto novo o problemu. Usput sam zamolio kolegu, autora govora o sed-u, da mi pomogne smisliti učinkovitiju skriptu za sed.
Ispustio sam sadržaj varnishadm vcl.show -v "$VCL_NAME"
u datoteku, kako bih se mogao usredotočiti na pisanje sed skripte bez gnjavaže ponovnog pokretanja usluge.
Kratak opis kako točno sed obrađuje unos može se pronaći u n
eksplicitno naveden kao razdjelnik retka.
U nekoliko prolaza i uz preporuke mog kolege, napisali smo sed skriptu koja je dala isti rezultat kao cijela originalna linija 116.
Ispod je primjer datoteke s ulaznim podacima:
> cat vcl-example.vcl
Text
// VCL.SHOW 0 1578 file with 3 spaces.vcl
More text
// VCL.SHOW 0 1578 file.vcl
Even more text
// VCL.SHOW 0 1578 file with TWOspaces.vcl
Final text
Ovo možda nije očito iz gornjeg opisa, ali nas zanima samo prvi komentar // VCL.SHOW
, a može ih biti više u ulaznim podacima. Zbog toga izvorni awk završava nakon prvog podudaranja.
# шаг первый, вывести только строки с комментариями
# используя возможности sed, определяется символ-разделитель с помощью конструкции '#' вместо обычно используемого '/', за счёт этого не придётся экранировать косые в искомом комментарии
# определяется регулярное выражение “// VCL.SHOW”, для поиска строк с определенным шаблоном
# флаг -n позаботится о том, чтобы sed не выводил все входные данные, как он это делает по умолчанию (см. ссылку выше)
# -E позволяет использовать расширенные регулярные выражения
> cat vcl-processor-1.sed
#// VCL.SHOW#p
> sed -En -f vcl-processor-1.sed vcl-example.vcl
// VCL.SHOW 0 1578 file with 3 spaces.vcl
// VCL.SHOW 0 1578 file.vcl
// VCL.SHOW 0 1578 file with TWOspaces.vcl
# шаг второй, вывести только имя файла
# используя команду “substitute”, с группами внутри регулярных выражений, отображается только нужная группa
# и это делается только для совпадений, ранее описанного поиска
> cat vcl-processor-2.sed
#// VCL.SHOW# {
s#.* [0-9]+ [0-9]+ (.*)$#1#
p
}
> sed -En -f vcl-processor-2.sed vcl-example.vcl
file with 3 spaces.vcl
file.vcl
file with TWOspaces.vcl
# шаг третий, получить только первый из результатов
# как и в случае с awk, добавляется немедленное завершения после печати первого найденного совпадения
> cat vcl-processor-3.sed
#// VCL.SHOW# {
s#.* [0-9]+ [0-9]+ (.*)$#1#
p
q
}
> sed -En -f vcl-processor-3.sed vcl-example.vcl
file with 3 spaces.vcl
# шаг четвертый, схлопнуть всё в однострочник, используя двоеточия для разделения команд
> sed -En -e '#// VCL.SHOW#{s#.* [0-9]+ [0-9]+ (.*)$#1#p;q;}' vcl-example.vcl
file with 3 spaces.vcl
Dakle, sadržaj skripte varnishreload izgledat će otprilike ovako:
VCL_FILE="$(echo "$VCL_SHOW" | sed -En '#// VCL.SHOW#{s#.*[0-9]+ [0-9]+ (.*)$#1#p;q;};')"
Gornja logika može se ukratko izraziti na sljedeći način:
Ako niz odgovara regularnom izrazu // VCL.SHOW
, zatim pohlepno proždirite tekst koji uključuje oba broja u ovom retku i spremite sve što ostane nakon ove operacije. Emitira pohranjenu vrijednost i završava program.
Jednostavno, zar ne?
Bili smo zadovoljni sed skriptom i činjenicom da je zamijenila sav izvorni kod. Svi moji testovi dali su željene rezultate, pa sam promijenio "varnishreload" na poslužitelju i ponovno ga pokrenuo systemctl reload varnish
. Gadna pogreška echo: write error: Broken pipe
opet nam se smijao u lice. Kursor koji namiguje čekao je unos nove naredbe u mračnoj praznini terminala...
Izvor: www.habr.com