Falling Down the Rabbit Hole: Historia e një gabimi të rindezjes së llakut - Pjesa 1

ghostinushanka, pasi kishte goditur me çekan butonat për 20 minutat e mëparshme sikur jeta e tij të varej prej saj, kthehet nga unë me një vështrim gjysmë të egër në sytë e tij dhe një buzëqeshje dinake - "Shoku, mendoj se e kuptova."

"Shiko këtu," thotë ai, duke treguar një nga simbolet në ekran, "Vë bast kapelën time të kuqe që nëse shtojmë këtu atë që sapo ju dërgova," duke treguar një pjesë tjetër të kodit, "gabimi nuk do të jetë më do të shfaqet."

Pak i hutuar dhe i lodhur, modifikoj shprehjen sed me të cilën kemi punuar për një kohë, ruaj skedarin dhe ekzekutoj systemctl varnish reload. Mesazhi i gabimit është zhdukur...

“Emailet që kam shkëmbyer me kandidatin,” vazhdoi kolegu im, ndërsa buzëqeshja e tij u shndërrua në një buzëqeshje të vërtetë gëzimi, “Papritmas m’u duk se ky ishte pikërisht i njëjti problem!”

Si filloi gjithçka

Artikulli supozon një kuptim se si funksionojnë bash, awk, sed dhe systemd. Preferohet njohja e llakut, por jo e detyrueshme.
Vula kohore në fragmente janë ndryshuar.
Shkruar me ghostinushanka.
Ky tekst është një përkthim i origjinalit të botuar në anglisht dy javë më parë; përkthimi boikoden.

Dielli shkëlqen nëpër dritaret panoramike në një mëngjes tjetër të ngrohtë vjeshte, një filxhan pije e sapopërgatitur e pasur me kafeinë qëndron larg tastierës, simfonia juaj e preferuar e tingujve tingëllon në kufjet tuaja, duke mbytur shushurimën e tastierës mekanike dhe hyrja e parë në listën e biletave të prapambetura në tabelën e Kanbanit shkëlqen plot shaka me titullin fatal “Investigate varnishreload” sh: echo: I/O error in staging” (Investigate “varnishreload sh: echo: I/O error” në skenë). Kur bëhet fjalë për llakun, ka dhe nuk mund të ketë vend për gabime, edhe nëse nuk sjellin ndonjë problem si në këtë rast.

Për ata që nuk janë të njohur me mbushje me llak, ky është një skrip i thjeshtë shell i përdorur për të ringarkuar konfigurimin llak - i quajtur gjithashtu VCL.

Siç sugjeron titulli i biletës, gabimi ndodhi në një nga serverët në skenë dhe duke qenë se isha i sigurt se kalimi i llakut në skenë po funksiononte siç duhet, supozova se ky do të ishte një gabim i vogël. Pra, vetëm një mesazh që përfundoi në një rrjedhë dalëse tashmë të mbyllur. E marr biletën për vete, me besim të plotë se do ta shënoj gati në më pak se 30 minuta, e përkëdhela veten pas shpine për të pastruar dërrasën nga një tjetër mbeturinë dhe i kthehem çështjeve më të rëndësishme.

Përplasja me një mur me shpejtësi 200 km/h

Hapja e skedarit varnishreload, në një nga serverët që drejtonte Debian Stretch, pashë një skrip shell më pak se 200 rreshta të gjatë.

Pasi kalova skenarin, nuk vura re asgjë që mund të rezultojë në probleme gjatë ekzekutimit të tij disa herë direkt nga terminali.

Në fund të fundit, kjo është një fazë, edhe nëse prishet, askush nuk do të ankohet, mirë... jo shumë. Unë ekzekutoj skriptin dhe shoh se çfarë do të shkruhet në terminal, por gabimet nuk janë më të dukshme.

Disa vrapime të tjera për t'u siguruar që nuk mund ta riprodhoj gabimin pa ndonjë përpjekje shtesë, dhe po filloj të kuptoj se si ta ndryshoj këtë skript dhe ta bëj atë ende të hedhë një gabim.

A mundet skripti të anashkalojë STDOUT (duke përdorur > &-)? Apo STDERR? Asnjëra nga këto nuk funksionoi në fund.

Me sa duket systemd modifikon disi mjedisin e fillimit, por si dhe pse?
Unë hap vim dhe edit varnishreload, duke shtuar set -x pikërisht nën shebang, duke shpresuar se dalja e korrigjimit të skenarit do të hedhë pak dritë.

Skedari është korrigjuar, kështu që unë rifreskoj llakun dhe shoh që ndryshimi theu plotësisht gjithçka... Shkarja është një rrëmujë e plotë, në të cilën ka mijëra kode të ngjashme me C-në. Edhe lëvizja në terminal nuk mjafton për të gjetur se ku fillon. Unë jam plotësisht i hutuar. A mund të ndikojë mënyra e korrigjimit në funksionimin e programeve të nisura në një skenar? Jo, është e pakuptimtë. Bug në guaskë? Disa skenarë të mundshëm po vrapojnë nëpër kokën time si kacabu në drejtime të ndryshme. Kupa e pijes me kafeinë zbrazet menjëherë, një udhëtim i shpejtë në kuzhinë për të rimbushur rezervat dhe... nisemi. Unë hap skenarin dhe i hedh një vështrim më të afërt shebangut: #!/bin/sh.

/bin/sh - kjo është vetëm një lidhje simbolike për bash, kështu që skripti interpretohet në modalitetin e pajtueshëm me POSIX, apo jo? Jo ashtu! Predha e paracaktuar në Debian është dash, dhe kjo është pikërisht ajo që duket. referohet /bin/sh.

# ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Jan 24  2017 /bin/sh -> dash

Si provë, e ndryshova shebangun në #!/bin/bash, i fshirë set -x dhe u përpoq përsëri. Më në fund, pas rindezjes së mëvonshme të llakut, u shfaq një gabim i tolerueshëm në dalje:

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

Rreshti 124, ja ku është!

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ç rezulton, rreshti 124 është mjaft bosh dhe nuk paraqet asnjë interes. Mund të supozoja vetëm se gabimi ndodhi si pjesë e një vargu me shumë rreshta që fillon në rreshtin 116.
Çfarë i shkruhet në fund variablit? VCL_FILE si rezultat i ekzekutimit të nën-predhës së mësipërme?

Në fillim, ai dërgon përmbajtjen e ndryshores VLC_SHOW, krijuar në rreshtin 115, duke ndjekur komandën përmes tubit. Dhe pastaj çfarë ndodh atje?

Së pari, përdoret atje varnishadm, e cila është pjesë e paketës së instalimit të llakut, për vendosjen e llakut pa rindezje.

Nën-ekip vcl.show -v përdoret për të nxjerrë të gjithë konfigurimin VCL të specifikuar në ${VCL_NAME}, te STDOUT.

Për të shfaqur konfigurimin aktual aktiv VCL, si dhe disa versione të mëparshme të konfigurimeve të drejtimit të llakut që janë ende në memorie, mund të përdorni komandën varnishadm vcl.list, prodhimi i të cilit do të jetë i ngjashëm me atë 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_12587

Vlera e ndryshueshme ${VCL_NAME} është instaluar në një pjesë tjetër të skenarit varnishreload në emrin e VCL-së aktualisht aktive, nëse ka një të tillë. Në këtë rast do të jetë "reload_20190101_120000_12397".

E shkëlqyeshme, e ndryshueshme ${VCL_SHOW} përmban konfigurim të plotë për llak, i qartë tani për tani. Tani më në fund e kuptoj pse është dalja e vizës set -x doli të ishte kaq i prishur - përfshinte përmbajtjen e konfigurimit që rezulton.

Është e rëndësishme të kuptohet se një konfigurim i plotë VCL shpesh mund të bashkohet së bashku nga disa skedarë. Komentet e stilit C përdoren për të identifikuar se ku janë përfshirë skedarë të caktuar konfigurimi në të tjerët, dhe kjo është ajo që ka të bëjë me rreshtin e mëposhtëm të fragmentit të kodit.
Sintaksa për komentet që përshkruajnë skedarët e përfshirë është në formatin e mëposhtëm:

// VCL.SHOW <NUM> <NUM> <FILENAME>

Numrat nuk janë të rëndësishëm në këtë kontekst, ne jemi të interesuar për emrin e skedarit.

Çfarë ndodh në fund të fundit në kënetën e komandave që fillojnë në rreshtin 116?
Le ta kuptojmë.
Ekipi përbëhet nga katër pjesë:

  1. E thjeshtë echo, e cila printon vlerën e ndryshores ${VCL_SHOW}
    echo "$VCL_SHOW"
  2. awk, e cila kërkon një rresht (rekord) ku fusha e parë, pas thyerjes së tekstit, është “//”, dhe e dyta është “VCL.SHOW”.
    Awk do të shkruajë rreshtin e parë që përputhet me këto modele dhe më pas do të ndalojë përpunimin menjëherë.

    awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
  3. Një bllok kodi që ruan vlerat e fushës në pesë variabla, të ndara me hapësira. Variabli i pestë FILE merr pjesën tjetër të rreshtit. Më në fund, jehona e fundit shkruan përmbajtjen e ndryshores ${FILE}.
    { read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
  4. Meqenëse të gjithë hapat 1 deri në 3 janë të mbyllura në një nënshtresë, duke nxjerrë vlerën $FILE do të shkruhet në një ndryshore VCL_FILE.

Siç sugjeron komenti në rreshtin 119, kjo i shërben qëllimit të vetëm të trajtimit të besueshëm të rasteve kur VCL do t'i referojë skedarët me hapësira në emrat e tyre.

Unë kam komentuar logjikën origjinale të përpunimit për ${VCL_FILE} dhe u përpoq të ndryshonte sekuencën e komandës, por nuk çoi në asgjë. Gjithçka funksionoi mirë për mua, por kur fillova shërbimin dha një gabim.

Duket se gabimi thjesht nuk është i riprodhueshëm kur ekzekutohet skripti me dorë, ndërsa 30 minutat e supozuara tashmë kanë skaduar gjashtë herë dhe, përveç kësaj, është shfaqur një detyrë me përparësi më të lartë, duke i lënë mënjanë çështjet e tjera. Pjesa tjetër e javës ishte e mbushur me një sërë detyrash dhe u hollua pak nga një raport mbi sed dhe një intervistë me një kandidat. Problem me gabimin në varnishreload humbi në mënyrë të pakthyeshme në rërën e kohës.

I ashtuquajturi sed-fu juaj... është në fakt... mbeturina

Javën tjetër pata një ditë mjaft të lirë, kështu që vendosa të trajtoj përsëri këtë biletë. Shpresoja që në trurin tim, një proces sfondi kishte kërkuar një zgjidhje për këtë problem gjatë gjithë kësaj kohe, dhe këtë herë do ta kuptoja patjetër se çfarë po ndodhte.

Meqenëse ndryshimi i thjeshtë i kodit nuk më ndihmoi herën e fundit, thjesht vendosa ta rishkruaj duke filluar nga rreshti 116. Në çdo rast, kodi ekzistues ishte budalla. Dhe nuk ka absolutisht nevojë për ta përdorur atë read.

Duke parë përsëri gabimin:
sh: echo: broken pipe — jehona shfaqet në dy vende në këtë komandë, por dyshoj se i pari është fajtori më i mundshëm (ose të paktën një bashkëpunëtor). As Awk nuk frymëzon besim. Dhe në rast se është me të vërtetë awk | {read; echo} dizajni çon në të gjitha këto probleme, pse të mos e zëvendësoni? Kjo komandë me një rresht nuk përdor të gjitha tiparet e awk, madje edhe këtë shtesë read përveç kësaj.

Që nga java e kaluar ka pasur një raport për sed, doja të provoja aftësitë e mia të reja të fituara dhe të thjeshtoja echo | awk | { read; echo} në një mënyrë më të kuptueshme echo | sed. Ndonëse kjo nuk është padyshim qasja më e mirë për të identifikuar defektin, mendova se të paktën do të provoja sed-fu-në time dhe ndoshta do të mësoja diçka të re për problemin. Gjatë rrugës, i kërkova kolegut tim, autorit të bisedës sed, të më ndihmonte të gjeja një skenar më efikas të sed-it.

I hoqa përmbajtjen varnishadm vcl.show -v "$VCL_NAME" në një skedar, kështu që unë mund të fokusohesha në shkrimin e skriptit sed pa ndonjë sherr të rinisjes së shërbimit.

Një përshkrim i shkurtër i saktësisht se si sed përpunon hyrjen mund të gjendet në manualin e tij GNU. Në burimet sed simboli n specifikuar në mënyrë eksplicite si ndarës vijash.

Në disa kalime dhe me rekomandimet e kolegut tim, ne shkruam një skenar sed që dha të njëjtin rezultat si i gjithë rreshti origjinal 116.

Më poshtë është një skedar mostër me të dhëna 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 text

Kjo mund të mos jetë e qartë nga përshkrimi i mësipërm, por ne jemi të interesuar vetëm për komentin e parë // VCL.SHOW, dhe mund të ketë disa prej tyre në të dhënat hyrëse. Kjo është arsyeja pse awk origjinal përfundon pas ndeshjes 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.vcl

Pra, përmbajtja e skriptit të varnishreload do të duket diçka si kjo:

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 shkurtimisht si më poshtë:
Nëse vargu përputhet me një shprehje të rregullt // VCL.SHOW, pastaj gllabëroni me lakmi tekstin që përfshin të dy numrat në këtë rresht dhe ruani gjithçka që mbetet pas këtij operacioni. Emitoni vlerën e ruajtur dhe përfundoni programin.

E thjeshtë, apo jo?

Ne ishim të kënaqur me skenarin sed dhe faktin që ai zëvendësoi të gjithë kodin origjinal. Të gjitha testet e mia dhanë rezultatet e dëshiruara, kështu që ndryshova "varnishreload" në server dhe e ekzekutova përsëri systemctl reload varnish. Gabim i keq echo: write error: Broken pipe qeshi përsëri në fytyrat tona. Kursori po priste që të futej një komandë e re në zbrazëtinë e errët të terminalit...

Burimi: www.habr.com

Shto një koment