, pasi kishte rrahur tastet për 20 minutat e fundit sikur jeta t’i varej prej kësaj, kthehet nga unë me një shprehje gjysmë të egër në sy dhe një buzëqeshje dinake — «O burrë, mendoj se e kuptova.»
«Shiko këtu,» — thotë ai, duke treguar një nga simbolet në ekran — «Vë bast kapelen time të kuqe që, nëse shtojmë këtu atë që sapo të dërgova» — duke treguar një pjesë tjetër të kodit — «gabimi nuk do të shfaqet më.»
Paksa i hutuar dhe i lodhur, ndryshoj shprehjen sed me të cilën kishim punuar prej një kohe, e ruaj skedarin dhe ekzekutoj systemctl varnish reload. Mesazhi i gabimit u zhduk…
«Emailet që po shkëmbeja me kandidatin,» vazhdoi kolegu im, ndërsa buzëqeshja e tij kthehej në një të qeshur të sinqertë plot gëzim, «papritur kuptova se ishte pikërisht i njëjti problem!»
Si nisi gjithçka
Artikulli supozon njohuri mbi parimet e funksionimit të bash, awk, sed dhe systemd. Njohuritë për varnish janë të mirëpritura, por jo të domosdoshme.
Shenjat kohore në snippet-et janë ndryshuar.
Shkruar së bashku me .
Ky tekst është përkthim i origjinalit, të botuar në anglisht dy javë më parë; përkthimi .
Dielli depërton përmes dritareve panoramike në një tjetër mëngjes të ngrohtë vjeshte, një filxhan me pijen e sapopërgatitur plot kafeinë pushon pranë tastierës, në kufje tingëllon simfonia e preferuar e zërave që mbulon zhurmën e tastierave mekanike, dhe hyrja e parë në listën e tiketave të backlog-ut në tabelën kanban ndriçon me lojë me titullin vendimtar “Investigate varnishreload sh: echo: I/O error in staging” (Hetoni “varnishreload sh: echo: I/O error” në staging). Kur bëhet fjalë për varnish, nuk ka dhe nuk mund të ketë vend për gabime, edhe nëse ato nuk shkaktojnë probleme, si në këtë rast.
Për ata që nuk e njohin , ky është një skript i thjeshtë shell që përdoret për të ringarkuar konfigurimin e — i njohur gjithashtu si VCL.
Siç sugjeron edhe titulli i tiketës, gabimi u shfaq në një nga serverët në staging dhe, meqë isha i bindur se routimi i Varnish në staging po funksiononte siç duhet, mendova se bëhej fjalë për një problem të vogël. Diçka e thjeshtë, si një mesazh që kishte përfunduar në një stream daljeje tashmë të mbyllur. E marr tiketin për vete, plotësisht i bindur se do ta shënoja si të zgjidhur për më pak se 30 minuta, do t’i jepja vetes një trokitje në shpatull për pastrimin e board-it nga një tjetër mbeturinë dhe do t’u kthehesha punëve më të rëndësishme.
Duke u përplasur me murin me 200 km/orë
Kur hapa skedarin varnishreload, në një nga serverët me Debian Stretch, pashë një shell script me më pak se 200 rreshta.
Pasi e kalova me sy skriptin, nuk vura re asgjë që mund të kthehej në problem nëse ekzekutohej disa herë rresht direkt nga terminali.
Në fund të fundit, ky është staging; edhe nëse prishet, askush nuk do të ankohet, mirë... jo shumë veta. E nis skriptin dhe shoh çfarë do të shfaqet në terminal, por tashmë as gabimet nuk duken më.
Pas edhe disa ekzekutimeve të tjera, vetëm për t’u siguruar se nuk po arrija ta riprodhoja gabimin pa ndonjë përpjekje shtesë, fillova të mendoj si ta ndryshoja këtë skript që ta detyroja më në fund të nxirrte gabimin.
Ndoshta t’i bllokojmë skriptit STDOUT-in (me > &-)? Apo STDERR-in? Në fund, asnjëra nuk funksionoi.
Qartazi, systemd po e ndryshon disi mjedisin e ekzekutimit, por si dhe pse?
Hap vim dhe redaktoj varnishreload, duke shtuar set -x menjëherë poshtë shebang-ut, me shpresën se output-i i debug-ut të skriptit do të hidhte sadopak dritë mbi problemin.
Skedari u ndryshua, kështu që rinisa Varnish dhe pashë se ndryshimi e kishte prishur krejtësisht gjithçka… Output-i ishte kaos i plotë, i mbushur me tonelata kodi të ngjashëm me C. Madje as scroll-i i terminalit nuk mjaftonte për të gjetur se ku fillonte. U hutova plotësisht. A mund të ndikojë vërtet mënyra e debug-ut te programet që nisen nga skripti? Jo, absurde. Bug në shell? Në kokën time po vraponin disa skenarë të mundshëm si buburreca në drejtime të ndryshme. Një filxhan me pije plot kafeinë u zbraz menjëherë, një udhëtim i shpejtë në kuzhinë për furnizim dhe… le të vazhdojmë. Hap skriptin dhe i hedh një sy më nga afër shebang-ut: #!/bin/sh.
/bin/sh — por kjo është thjesht një symlink për te bash, kështu që skripti interpretohet në mënyrën e përputhshme me POSIX, apo jo? Aspak! Shell-i i parazgjedhur në Debian është dash, dhe pikërisht aty /bin/sh.
# ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Jan 24 2017 /bin/sh -> dashPër provë, e ndryshova shebang-un në #!/bin/bash, hoqa set -x dhe provova edhe një herë. Më në fund, gjatë rinisjes së radhës të varnish-it, në dalje u shfaq një gabim i kuptueshëm:
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' compiledRreshti 124, ja ku qenka!
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 }Por siç doli, rreshti 124 është goxha bosh dhe nuk paraqet ndonjë interes. Mund të supozoja vetëm se gabimi kishte lindur si pjesë e bllokut shumëreshtor që nis në rreshtin 116.
Çfarë shkruhet përfundimisht në variablën VCL_FILE si rezultat i ekzekutimit të subshell-it të përmendur më sipër?
Në fillim, ai dërgon përmbajtjen e variablës VLC_SHOW, të krijuar në rreshtin 115, te komanda pasuese përmes një pipe. Po çfarë ndodh aty më pas?
Së pari, aty përdoret varnishadm, i cili është pjesë e paketës së instalimit të varnish-it, për të konfiguruar varnish-in pa rinisje.
Nënkomanda vcl.show -v përdoret për të shfaqur të gjithë konfigurimin VCL të specifikuar në ${VCL_NAME}, në STDOUT.
Për të shfaqur konfigurimin VCL aktualisht aktiv, si edhe disa versione të mëparshme të konfigurimit të routimit të varnish-it që ende ndodhen në memorie, mund të përdorni komandën varnishadm vcl.list, dalja e së cilës do të jetë e ngjashme me shembullin më poshtë:
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_12587Vlera e variablit ${VCL_NAME} caktohet në një pjesë tjetër të skriptit varnishreload si emri i VCL-së aktualisht aktive, nëse ekziston. Në këtë rast, ai do të jetë “reload_20190101_120000_12397”.
Shkëlqyeshëm, variabla ${VCL_SHOW} përmban konfigurimin e plotë për varnish-in, deri këtu është e qartë. Tani më në fund e kuptova pse dalja e dash me set -x doli kaq e dëmtuar — ajo përfshinte përmbajtjen e konfigurimit të krijuar.
Është e rëndësishme të kuptohet se konfigurimi i plotë i VCL shpesh mund të ndërtohet nga disa skedarë. Komentet në stilin C përdoren për të treguar se ku një skedar konfigurimi është përfshirë në një tjetër, dhe pikërisht për këtë bëhet fjalë në rreshtin e mëposhtëm të fragmentit të kodit.
Sintaksa e komenteve që përshkruajnë skedarët e përfshirë ka këtë format:
// VCL.SHOW <NUM> <NUM> <FILENAME>Numrat në këtë kontekst nuk kanë rëndësi; ajo që na intereson është emri i skedarit.
Pra, çfarë po ndodh në atë grumbull komandash që fillon te rreshti 116?
Le ta shqyrtojmë.
Komanda përbëhet nga katër pjesë:
- E thjeshtë
echo, e cila shfaq vlerën e variablës${VCL_SHOW}echo "$VCL_SHOW" awk, i cili kërkon rreshtin (regjistrimin) ku fusha e parë, pas ndarjes së tekstit, do të jetë “//”, ndërsa e dyta — «VCL.SHOW».
Awk do të nxjerrë rreshtin e parë që përputhet me këto modele dhe më pas do ta ndërpresë menjëherë përpunimin.awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'- Blloku i kodit që ruan në pesë variabla vlerat e fushave të ndara me hapësira. Variabla e pestë FILE merr pjesën e mbetur të rreshtit. Në fund, echo i fundit shfaq përmbajtjen e variablës
${FILE}.{ read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" } - Meqenëse të gjithë hapat nga 1 deri në 3 janë vendosur brenda një subshell-i, dalja e vlerës
$FILEdo të ruhet në variablënVCL_FILE.
Siç del nga komenti në rreshtin 119, kjo shërben për një qëllim të vetëm: të trajtojë në mënyrë të besueshme rastet kur VCL do t'u referohet skedarëve me hapësira në emër.
E komentova logjikën fillestare të përpunimit për ${VCL_FILE} dhe u përpoqa të ndryshoja rendin e komandave, por kjo nuk solli rezultat. Gjithçka funksiononte pastër tek unë, ndërsa gjatë nisjes së shërbimit dilte një gabim.
Duket se gabimi thjesht nuk riprodhohej kur skripti nisej manualisht, ndërkohë ato 30 minutat e supozuara kishin mbaruar tashmë të paktën gjashtë herë dhe, për më tepër, u shfaq një detyrë me prioritet më të lartë që shtyu gjithçka tjetër në plan të dytë. Pjesa e mbetur e javës u mbush me lloj-lloj detyrash dhe u ndërpre vetëm pak nga një prezantim për sed dhe një intervistë me një kandidat. Problemi me gabimin në varnishreload ishte humbur pa kthim në rërën e kohës.
I ashtuquajturi sed-fu juaj… në fakt… është i tmerrshëm
Javën e ardhshme pata një ditë mjaft të lirë, ndaj vendosa t’i rikthehem sërish këtij tiketimi. Shpresoja që në trurin tim, ndonjë proces në sfond gjatë gjithë kësaj kohe të kishte kërkuar zgjidhjen e këtij problemi dhe që këtë herë më në fund do ta kuptoja ku qëndronte çështja.
Meqë herën e kaluar një ndryshim i thjeshtë në kod nuk ndihmoi, vendosa ta rishkruaj duke nisur nga rreshti 116. Në çdo rast, kodi ekzistues ishte i çuditshëm. Dhe nuk ka absolutisht asnjë arsye për të përdorur read.
Duke e parë edhe një herë gabimin:
sh: echo: broken pipe — në këtë komandë echo shfaqet në dy vende, por dyshoj se i pari është fajtori më i mundshëm (ose të paktën bashkëfajtor). Edhe Awk nuk më frymëzon besim. Dhe nëse vërtet është awk | {read; echo} konstruksioni që i shkakton të gjitha këto probleme, pse të mos zëvendësohet? Kjo komandë me një rresht nuk i përdor aspak të gjitha mundësitë e awk, dhe për më tepër ky read i tepërt.
Meqë javën e kaluar pati një prezantim për sed, doja të provoja aftësitë e mia të sapofituara dhe të thjeshtoja echo | awk | { read; echo} në një echo | sed. Edhe pse kjo nuk është aspak qasja më e mirë për të gjetur gabimin, mendova se të paktën do të provoja sed-fu tim dhe ndoshta do të mësoja diçka të re për problemin. Gjatë procesit, i kërkova kolegut tim, autorit të prezantimit për sed, të më ndihmonte të krijonim një skript sed më efikas.
E ruajta përmbajtjen e varnishadm vcl.show -v "$VCL_NAME" në një skedar, që të mund të përqendrohesha te shkrimi i skriptit sed pa telashet që lidhen me rinisjet e shërbimit.
Një përshkrim i shkurtër se si saktësisht sed përpunon të dhënat hyrëse mund të gjendet në . Në kodin burimor të sed, simboli n është përcaktuar qartë si ndarës i rreshtave.
Pas disa iterimeve dhe me sugjerimet e kolegut tim, shkruam një skript sed që jepte të njëjtin rezultat si i gjithë rreshti origjinal 116.
Më poshtë është një shembull i skedarit me të dhënat hyrëse:
< 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 textKjo mund të mos jetë e dukshme nga përshkrimi i mësipërm, por neve na intereson vetëm komenti i parë // VCL.SHOW, megjithëse në të dhënat hyrëse mund të ketë disa të tillë. Pikërisht për këtë arsye awk origjinal e përfundon punën pas përputhjes së parë.
# шаг первый, вывести только строки с комментариями
# используя возможности 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.vclPra, përmbajtja e skriptit varnishreload do të duket afërsisht kështu:
VCL_FILE="$(echo "$VCL_SHOW" | sed -En '#\/\/ VCL.SHOW#{s#.*[0-9]+ [0-9]+ (.*)$#1#p;q;};')"Logjika e mësipërme mund të shprehet shkurt kështu:
Nëse rreshti përputhet me shprehjen e rregullt // VCL.SHOW, atëherë përpije në mënyrë greedy tekstin që përfshin të dy numrat në atë rresht dhe ruaj gjithçka që mbetet pas këtij veprimi. Shfaq vlerën e ruajtur dhe përfundo programin.
E thjeshtë, apo jo?
Ishim të kënaqur me skriptin sed dhe me faktin që ai zëvendësonte të gjithë kodin origjinal. Të gjitha testet e mia dhanë rezultatet e dëshiruara, ndaj ndryshova “varnishreload” në server dhe ekzekutova sërish systemctl reload varnish. Gabimi i mallkuar echo: write error: Broken pipe na u tall sërish para syve. Kursori që pulsonte priste futjen e një komande të re në zbrazëtinë e errët të terminalit…
Burimi: habr.com
