Falante la Kuniklotruon: La rakonto pri unu eraro pri rekomenco de verniso - Parto 1

ghostinushanka, martelante for ĉe la butonoj dum la antaŭaj 20 minutoj kvazaŭ lia vivo dependus de ĝi, turnas sin al mi kun duonsovaĝa rigardo en siaj okuloj kaj ruza rido - "Ho, mi kredas ke mi ricevis ĝin."

"Rigardu ĉi tien," li diras, montrante unu el la simboloj sur la ekrano, "Mi vetas mian ruĝan ĉapelon, ke se ni aldonos ĉi tie tion, kion mi ĵus sendis al vi," indikante alian sekcion de kodo, "la eraro ne plu estos. estos montrata."

Iom konfuzita kaj laca, mi modifas la sed-esprimon, pri kiu ni laboris dum kelka tempo, konservas la dosieron kaj kuras systemctl varnish reload. La erarmesaĝo malaperis...

"La retpoŝtojn, kiujn mi interŝanĝis kun la kandidato," daŭrigis mia kolego, dum lia rido kreskis en aŭtentan rideton de ĝojo, "subite ekkomprenis, ke ĉi tio estas ĝuste la sama problemo!"

Kiel ĉio komenciĝis

La artikolo supozas komprenon pri kiel bash, awk, sed kaj systemd funkcias. Scio pri verniso estas preferita, sed ne postulata.
Tempmarkoj en fragmentoj estis ŝanĝitaj.
Skribita kun ghostinushanka.
Ĉi tiu teksto estas traduko de la originalo publikigita en la angla antaŭ du semajnoj; traduko boikoden.

La suno brilas tra la panoramaj fenestroj en alia varma aŭtuna mateno, taso da freŝe preparita kafein-riĉa trinkaĵo ripozas for de la klavaro, via plej ŝatata simfonio de sonoj sonas en viaj aŭdiloj, dronigante la susuradon de mekanikaj klavaroj, kaj la unua eniro. en la listo de backlog-biletoj sur la Kanban-tabulo lude brilas kun la fatala titolo "Enketi varnishreload" sh: echo: I/O-eraro en enscenigo" (Enketu "varnishreload sh: echo: I/O-eraro" en enscenigo). Kiam temas pri verniso, estas kaj ne povas esti loko por eraroj, eĉ se ili ne rezultigas problemojn kiel en ĉi tiu kazo.

Por tiuj, kiuj ne konas varnishreload, ĉi tio estas simpla ŝelskripto uzata por reŝargi la agordon verniso - ankaŭ nomata VCL.

Kiel la titolo de la bileto sugestas, la eraro okazis sur unu el la serviloj sur la scenejo, kaj ĉar mi estis certa, ke la varniĉa vojo sur la scenejo funkcias ĝuste, mi supozis, ke tio estus eta eraro. Do, nur mesaĝo kiu finiĝis en jam fermita eligofluo. Mi prenas la bileton por mi, tute konfidante, ke mi markos ĝin preta en malpli ol 30 minutoj, frapos min la dorson por purigi la tabulon de ankoraŭ alia rubo kaj revenos al pli gravaj aferoj.

Frakasi kontraŭ muro je 200 km/h

Malfermante la dosieron varnishreload, sur unu el la serviloj kurantaj Debian Stretch, mi vidis ŝelan skripton longan malpli ol 200 liniojn.

Traleginte la skripton, mi ne rimarkis ion ajn, kio povus rezultigi problemojn kiam ĝi ruliĝas plurfoje rekte de la terminalo.

Ja tio ĉi estas etapo, eĉ se ĝi rompas, neniu plendos, nu... ne tro. Mi rulas la skripton kaj vidas, kio estos skribita al la terminalo, sed la eraroj ne plu videblas.

Kelkaj pliaj kuroj por certigi, ke mi ne povas reprodukti la eraron sen plia peno, kaj mi komencas eltrovi kiel ŝanĝi ĉi tiun skripton kaj igi ĝin ankoraŭ ĵeti eraron.

Ĉu la skripto povas anstataŭi STDOUT (uzante > &-)? Aŭ STDERR? Neniu el ĉi tiuj funkciis finfine.

Ŝajne systemd iel modifas la startan medion, sed kiel, kaj kial?
Mi malfermas vim kaj redaktas varnishreload, aldonante set -x ĝuste sub la shebang, esperante ke la sencimiga eligo de la skripto ĵetos iom da lumo.

La dosiero estas korektita, do mi reŝargas vernison kaj vidas, ke la ŝanĝo tute rompis ĉion... La ellasilo estas kompleta malordo, en kiu estas tunoj da C-simila kodo. Eĉ movo en la terminalo ne sufiĉas por trovi kie ĝi komenciĝas. Mi estas tute konfuzita. Ĉu sencimiga reĝimo povas influi la funkciadon de programoj lanĉitaj en skripto? Ne, ĝi estas sensencaĵo. Cimo en la ŝelo? Pluraj eblaj scenaroj kuregas tra mia kapo kiel blatoj en malsamaj direktoj. La taso de la kafeina trinkaĵo estas tuj malplenigita, rapida vojaĝo al la kuirejo por replenigi la stokon kaj... foriras. Mi malfermas la skripton kaj pli detale rigardas la shebang: #!/bin/sh.

/bin/sh - ĉi tio estas nur simbolligo al bash, do la skripto estas interpretita en POSIX-kongrua reĝimo, ĉu ne? Ne tiel! La defaŭlta ŝelo sur Debian estas dash, kaj ĝuste tiel ĝi aspektas. rilatas /bin/sh.

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

Kiel testo, mi ŝanĝis la shebang al #!/bin/bash, forigita set -x kaj provis denove. Finfine, post posta rekomenco de verniso, tolerebla eraro aperis en la eligo:

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

Linio 124, jen ĝi!

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 }

Sed kiel ĝi rezultas, linio 124 estas sufiĉe malplena kaj sen intereso. Mi nur povis supozi, ke la eraro okazis kiel parto de plurlinia ĉeno komencanta sur linio 116.
Kio estas finfine skribita al la variablo? VCL_FILE kiel rezulto de ekzekuto de la supra subŝelo?

Komence, ĝi sendas la enhavon de la variablo VLC_SHOW, kreita sur linio 115, sekvante la komandon tra la pipo. Kaj tiam kio okazas tie?

Unue, ĝi estas uzata tie varnishadm, kiu estas parto de la varnish-instala pako, por agordi varnikon sen rekomenco.

Subteamo vcl.show -v uzata por eligi la tutan VCL-agordon specifitan en ${VCL_NAME}, al STDOUT.

Por montri la nunan aktivan VCL-agordon, same kiel plurajn antaŭajn versiojn de varnish-vojaj agordoj, kiuj ankoraŭ estas en memoro, vi povas uzi la komandon varnishadm vcl.list, kies eligo estos simila al tiu ĉi sube:

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

Varia valoro ${VCL_NAME} estas instalita en alia parto de la skripto varnishreload al la nomo de la aktuale aktiva VCL, se ekzistas unu. En ĉi tiu kazo ĝi estos "reload_20190101_120000_12397".

Granda, varia ${VCL_SHOW} enhavas kompletan agordon por verniso, klara nuntempe. Nun mi finfine komprenas kial la streka eligo estas set -x montriĝis tiel rompita - ĝi inkludis la enhavon de la rezulta agordo.

Gravas kompreni, ke kompleta VCL-agordo ofte povas esti kunmetita el pluraj dosieroj. C-stilaj komentoj estas uzataj por identigi kie certaj agordaj dosieroj estis inkluzivitaj en aliaj, kaj pri tio temas la sekva linio de koda fragmento.
La sintakso por komentoj priskribantaj inkluzivitajn dosierojn estas en la sekva formato:

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

La nombroj ne gravas en ĉi tiu kunteksto, ni interesiĝas pri la dosiernomo.

Kio finfine okazas en la marĉo de komandoj ekde la linio 116?
Ni eltrovu ĝin.
La teamo konsistas el kvar partoj:

  1. Simpla echo, kiu presas la valoron de la variablo ${VCL_SHOW}
    echo "$VCL_SHOW"
  2. awk, kiu serĉas linion (rekordon) kie la unua kampo, post rompado de la teksto, estas “//”, kaj la dua estas “VCL.SHOW”.
    Awk skribos la unuan linion kongruan kun ĉi tiuj ŝablonoj kaj poste ĉesos la prilaboradon tuj.

    awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
  3. Bloko de kodo, kiu stokas kampajn valorojn en kvin variabloj, apartigitaj per spacoj. La kvina FILE-variablo ricevas la reston de la linio. Fine, la lasta eĥo skribas la enhavon de la variablo ${FILE}.
    { read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
  4. Ĉar ĉiuj paŝoj 1 ĝis 3 estas enfermitaj en subŝelo, eligante la valoron $FILE estos skribita al variablo VCL_FILE.

Kiel la komento sur linio 119 sugestas, tio servas al la sola celo de fidinde trakti kazojn kie VCL referencos dosierojn kun spacoj en iliaj nomoj.

Mi komentis la originalan prilaboran logikon por ${VCL_FILE} kaj provis ŝanĝi la ordonsekvencon, sed ĝi ne kondukis al io ajn. Ĉio funkciis bone por mi, sed kiam mi komencis la servon, ĝi donis eraron.

Ŝajnas, ke la eraro simple ne estas reproduktebla dum rulado de la skripto permane, dum la supozitaj 30 minutoj jam eksvalidiĝis ses fojojn kaj krome aperis pli alta prioritata tasko, flankenpuŝante aliajn aferojn. La resto de la semajno estis plenigita de diversaj taskoj kaj estis nur iomete diluita de raporto pri sed kaj intervjuo kun kandidato. Problemo kun eraro en varnishreload estis nereveneble perdita en la sabloj de la tempo.

Via tiel nomata sed-fu... efektive estas... rubo

La sekvan semajnon mi havis unu sufiĉe liberan tagon, do mi decidis denove trakti ĉi tiun bileton. Mi esperis, ke en mia cerbo, iu fona procezo serĉis solvon al ĉi tiu problemo dum ĉi tiu tempo, kaj ĉi-foje mi certe komprenos kio okazas.

Ĉar simple ŝanĝi la kodon lastan fojon ne helpis, mi ĵus decidis reverki ĝin ekde la linio 116. Ĉiukaze, la ekzistanta kodo estis stulta. Kaj ne necesas uzi ĝin read.

Rigardante la eraron denove:
sh: echo: broken pipe — eĥo aperas duloke en ĉi tiu ordono, sed mi suspektas, ke la unua estas la pli verŝajna kulpulo (aŭ almenaŭ komplico). Awk ankaŭ ne inspiras konfidon. Kaj se ĝi vere estas awk | {read; echo} la dezajno kondukas al ĉiuj ĉi tiuj problemoj, kial ne anstataŭigi ĝin? Ĉi tiu unulinia komando ne uzas ĉiujn funkciojn de awk, kaj eĉ ĉi tiun kroman read krome.

Ekde la pasinta semajno estis raporto pri sed, Mi volis provi miajn nove akiritajn kapablojn kaj simpligi echo | awk | { read; echo} en pli kompreneblan echo | sed. Kvankam ĉi tio certe ne estas la plej bona aliro por identigi la cimon, mi pensis, ke mi almenaŭ provus mian sed-fu kaj eble lernus ion novan pri la problemo. Survoje mi petis mian kolegon, la aŭtoron de la sed-parolado, ke mi helpu min elpensi pli efikan sed-skripton.

Mi faligis la enhavon varnishadm vcl.show -v "$VCL_NAME" al dosiero, do mi povus koncentriĝi pri verkado de la sed-skripto sen ia ĝeno de servaj rekomencoj.

Mallonga priskribo de ekzakte kiel sed prilaboras enigaĵon troveblas lia GNU-manlibro. En la sed fontoj la simbolo n eksplicite specifita kiel linia apartigilo.

En pluraj paŝoj kaj kun la rekomendoj de mia kolego, ni skribis sed-skripton, kiu donis la saman rezulton kiel la tuta originala linio 116.

Malsupre estas ekzempla dosiero kun enirdatumoj:

> 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

Ĉi tio eble ne estas evidenta el la supra priskribo, sed ni interesiĝas nur pri la unua komento // VCL.SHOW, kaj povas esti pluraj el ili en la enirdatenoj. Tial la originala awk finiĝas post la unua matĉo.

# шаг первый, вывести только строки с комментариями
# используя возможности 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

Do, la enhavo de la varnishreload skripto aspektos kiel ĉi tio:

VCL_FILE="$(echo "$VCL_SHOW" | sed -En '#// VCL.SHOW#{s#.*[0-9]+ [0-9]+ (.*)$#1#p;q;};')"

Ĉi-supra logiko povas esti mallonge esprimita jene:
Se la ĉeno kongruas kun regula esprimo // VCL.SHOW, tiam avide formanĝu la tekston, kiu inkluzivas ambaŭ ciferojn en ĉi tiu linio, kaj konservu ĉion, kio restas post ĉi tiu operacio. Elsendu la konservitan valoron kaj ĉesigu la programon.

Simpla, ĉu ne?

Ni estis feliĉaj kun la sed-skripto kaj la fakto ke ĝi anstataŭigis la tutan originalan kodon. Ĉiuj miaj provoj donis la deziratajn rezultojn, do mi ŝanĝis la "varnishreload" sur la servilo kaj kuris ĝin denove systemctl reload varnish. Malbona eraro echo: write error: Broken pipe denove ridis en niajn vizaĝojn. La palpebruma kursoro atendis ke nova komando estos enigita en la malluma malpleno de la terminalo...

fonto: www.habr.com

Aldoni komenton