„Nézz ide” – mondja, és az egyik karakterre mutat a képernyőn –, lefogadom, hogy ha hozzáadjuk ide azt, amit az imént küldtem neked – mutat egy másik kódrészletre –, a hiba többé nem fog megjelenni."
Kicsit tanácstalanul és fáradtan megváltoztatom a sed utasítást, amin már egy ideje dolgozunk, mentem a fájlt, és futok. systemctl varnish reload
. A hibaüzenet eltűnt...
– Azok az e-mailek, amelyeket a jelölttel váltottam – folytatta kollégám, miközben mosolya őszinte, örömteli mosolymá alakul át –, hirtelen eszembe jutott, hogy ez pontosan ugyanaz a probléma!
Hogyan kezdődött az egész
A cikk feltételezi a bash, awk, sed és systemd működésének megértését. A lakkozás ismerete előny, de nem kötelező.
A kivonatok időbélyegei módosultak.
-val írva
Ez a szöveg a két hete angol nyelven megjelent eredeti fordítása; fordítás
A panorámaablakokon besüt a nap egy újabb meleg őszi reggelen, egy csésze frissen főzött koffeines ital pihen a klaviatúra oldalán, a fülhallgatóban a hangok kedvenc szimfóniája szólal meg a mechanikus billentyűzetek susogása mellett, és az első bejegyzés a az elmaradt jegyek listája a kanban táblán játékosan világít a sorsdöntő címen: „Investigate varnishreload sh: echo: I/O error in stageing” (Investigate „varnishreload sh: echo: I/O error” in staging). Ha a lakkozásról van szó, akkor nincs és nem is lehet hiba, még akkor sem, ha ezek nem eredményeznek problémát, mint ebben az esetben.
Azoknak, akik nem ismerik
Ahogy a jegy címe is sugallja, a hiba a színpad egyik szerverén lépett fel, és mivel biztos voltam benne, hogy a lakk továbbítása a színpadon megfelelően működik, feltételeztem, hogy ez kisebb hiba lesz. Tehát csak egy üzenet, amely egy már lezárt kimeneti adatfolyamba került. Veszek egy jegyet magamnak, abban a teljes bizalomban, hogy kevesebb, mint 30 percen belül készre jelölöm, megveregetem a vállam, hogy megtisztítom a táblát a következő szeméttől, és visszatérek a fontosabb dolgokhoz.
200 km/órás sebességgel falnak ütközött
Fájl megnyitása varnishreload
, az egyik Debian Stretchet futtató szerveren láttam egy 200 sornál rövidebb shell szkriptet.
Miután végigfutottam a szkriptet, nem vettem észre semmi olyat, ami problémát okozhatna, ha többször futtassa közvetlenül a terminálról.
Végül is ez egy színpad, még ha el is törik, senki nem fog panaszkodni, hát... nem túl sokat. Lefuttatom a szkriptet, és megnézem, mi lesz kiírva a terminálra, de a hibák már nem látszanak.
Még néhány futtatás, hogy megbizonyosodjon arról, hogy nem tudom reprodukálni a hibát némi extra erőfeszítés nélkül, és elkezdem kitalálni, hogyan változtassam meg ezt a szkriptet, és állítsam be, hogy továbbra is hibát okozzon.
A szkript blokkolhatja-e az STDOUT (a > &-
)? Vagy STDERR? Végül egyik sem működött.
Nyilvánvaló, hogy a systemd valamilyen módon megváltoztatja a futtatási környezetet, de hogyan és miért?
Bekapcsolom a vimet és szerkesztem varnishreload
, hozzátéve set -x
közvetlenül a shebang alatt, remélve, hogy a forgatókönyv kimenetének hibakeresése némi fényt derít.
A reszelő fix, így újratöltöm a lakkot és látom, hogy a váltás teljesen összetört mindent... A kipufogó teljes káosz, rengeteg C-szerű kód van benne. Még a terminálban való görgetés sem elég ahhoz, hogy megtaláljuk, hol kezdődik. teljesen össze vagyok zavarodva. Befolyásolhatja-e a hibakeresési mód a szkriptben futó programok működését? Nem baromság. Hiba a héjban? Több lehetséges forgatókönyv repül a fejemben, mint a csótányok különböző irányokba. Egy csésze koffein teli ital azonnal kiürül, egy gyors kirándulás a konyhába utánpótlásért és… gyerünk. Megnyitom a forgatókönyvet, és közelebbről megnézem a shebanget: #!/bin/sh
.
/bin/sh
- ez csak egy bash symlink, tehát a script POSIX-kompatibilis módban értelmeződik, nem? Nem volt ott! A Debian alapértelmezett shellje a dash, ami pontosan az /bin/sh
.
# ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Jan 24 2017 /bin/sh -> dash
A próba kedvéért megváltoztattam a shebangot #!/bin/bash
, eltávolítva set -x
és újra próbálkozott. Végül a lakk későbbi újratöltésekor egy tolerálható hiba jelent meg a kimenetben:
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
124. sor, itt van!
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 }
De mint kiderült, a 124-es sor meglehetősen üres és érdektelen. Csak azt tudtam feltételezni, hogy a hiba a 116-os vonalról induló többsor részeként történt.
Amit végül a változóba írunk VCL_FILE
a fenti alhéj végrehajtásának eredményeként?
Az elején elküldi a változó tartalmát VLC_SHOW
, a 115. sorban létrehozott, a következő parancshoz a csövön keresztül. És akkor mi történik ott?
Először is használ varnishadm
, amely a lakktelepítő csomag része, a lakk újraindítás nélküli konfigurálásához.
alparancs vcl.show -v
pontban meghatározott teljes VCL konfiguráció kimenetére szolgál ${VCL_NAME}
, STDOUT-ra.
A jelenleg aktív VCL-konfiguráció, valamint a még a memóriában lévő lakk útválasztási konfigurációinak több korábbi verziójának megjelenítéséhez használhatja a parancsot. varnishadm vcl.list
, amelynek kimenete hasonló lesz a következőhöz:
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
Változó érték ${VCL_NAME}
a forgatókönyv másik részében varnishreload
az aktuálisan aktív VCL nevére, ha van ilyen. Ebben az esetben a „reload_20190101_120000_12397” lesz.
Oké, változó. ${VCL_SHOW}
tartalmazza a lakk teljes konfigurációját, eddig egyértelmű. Most végre értem, hogy miért dash kimenet set -x
annyira töröttnek bizonyult - benne volt a kapott konfiguráció tartalma.
Fontos megérteni, hogy egy teljes VCL-konfiguráció gyakran több fájlból is összerakható. A C-stílusú megjegyzések annak meghatározására szolgálnak, hogy az egyik konfigurációs fájl hol szerepel a másikban, és a következő kódrészlet pontosan erről szól.
A mellékelt fájlokat leíró megjegyzések szintaxisa a következő formátumú:
// VCL.SHOW <NUM> <NUM> <FILENAME>
A számok ebben az összefüggésben nem fontosak, minket a fájlnév érdekel.
Tehát mi történik a 116-os vonalon kezdődő parancsok mocsarában?
Nézzünk szembe a tényekkel.
A parancs négy részből áll:
- egyszerű
echo
, amely a változó értékét jeleníti meg${VCL_SHOW}
echo "$VCL_SHOW"
awk
, amely egy sort (rekordot) keres, ahol az első mező a szöveg felosztása után a „//”, a második pedig a „VCL.SHOW” lesz.
Az Awk kiírja az első sort, amely megfelel ezeknek a mintáknak, majd azonnal leállítja a feldolgozást.awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
- Egy kódblokk, amely a mezőértékeket öt változóban tárolja, szóközzel elválasztva. Az ötödik FILE változó fogadja a sor többi részét. Végül az utolsó visszhang kiírja a változó tartalmát
${FILE}
.{ read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
- Mivel az 1–3. lépések mindegyike egy alhéjba van zárva, az érték kimenete
$FILE
változóba lesz írvaVCL_FILE
.
Ahogy a 119. sor megjegyzése is sugallja, ez az egyetlen célt szolgálja, hogy megbízhatóan kezelje azokat az eseteket, amikor a VCL szóköz karaktereket tartalmazó fájlokra hivatkozik a nevében.
Megjegyeztem az eredeti feldolgozási logikát ${VCL_FILE}
és megpróbálta megváltoztatni a parancsok sorrendjét, de nem vezetett semmire. Nálam minden tisztán működött, a szerviz indítása esetén hibát adott.
Úgy tűnik, hogy a hiba egyszerűen nem reprodukálható a szkript kézi futtatásakor, miközben a becsült 30 perc már hatszor véget ért, ráadásul egy magasabb prioritású feladat is megjelent, ami félretolta a többi esetet. A hét hátralévő része változatos feladatokkal telt, és csak kissé felhígult a sed-en folytatott beszélgetés és a jelölttel készült interjú. Hiba probléma be varnishreload
helyrehozhatatlanul elveszett az idő homokjában.
Az úgynevezett sed-fu... valójában... szemét
A következő héten volt egy elég szabad nap, ezért úgy döntöttem, hogy újra megveszem ezt a jegyet. Reméltem, hogy az agyamban valami háttérfolyamat mindvégig erre a problémára keresi a megoldást, és ezúttal biztosan megértem, mi a baj.
Mivel a múltkor a kód megváltoztatása nem segített, ezért úgy döntöttem, hogy átírom a 116. sortól kezdve. Mindenesetre a meglévő kód hülyeség volt. És egyáltalán nincs szükség a használatára read
.
Még egyszer megnézve a hibát:
sh: echo: broken pipe
- ebben a parancsban két helyen van visszhang, de gyanítom, hogy az első a valószínűbb tettes (vagy legalábbis cinkos). Az Awk sem kelt magabiztosságot. És ha tényleg így van awk | {read; echo}
a tervezés mindezen problémákhoz vezet, miért ne cserélné ki? Ez az egysoros parancs nem használja az awk összes funkcióját, és még ezt az extrát sem read
mellékletben.
Múlt hét óta jelentés készült sed
Ki akartam próbálni az újonnan megszerzett készségeimet és egyszerűsíteni echo | awk | { read; echo}
érthetőbbé echo | sed
. Bár határozottan nem ez a legjobb módszer a hiba elkapására, úgy gondoltam, legalább kipróbálom a sed-fu-t, és talán tanulok valami újat a problémáról. Útközben megkértem kollégámat, a sed talk íróját, hogy segítsen egy hatékonyabb sed forgatókönyv kidolgozásában.
Eldobtam a tartalmat varnishadm vcl.show -v "$VCL_NAME"
egy fájlba, hogy a sed szkript megírására koncentrálhassak anélkül, hogy a szolgáltatás újraindításával járó gondok kellenek.
Itt található egy rövid leírás arról, hogy a sed pontosan hogyan kezeli a bevitelt n
kifejezetten sorelválasztóként van megadva.
Több menetben és kollégám tanácsára írtunk egy sed scriptet, ami ugyanazt az eredményt adta, mint a teljes eredeti 116-os sor.
Az alábbiakban egy mintafájl látható a bemeneti adatokkal:
> 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
Lehet, hogy a fenti leírásból nem egyértelmű, de minket csak az első komment érdekel // VCL.SHOW
, és több is lehet belőlük a bemeneti adatokban. Ez az oka annak, hogy az eredeti awk az első meccs után véget ér.
# шаг первый, вывести только строки с комментариями
# используя возможности 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
Tehát a varnishreload script tartalma valahogy így nézne ki:
VCL_FILE="$(echo "$VCL_SHOW" | sed -En '#// VCL.SHOW#{s#.*[0-9]+ [0-9]+ (.*)$#1#p;q;};')"
A fenti logika a következőképpen foglalható össze:
Ha a karakterlánc megfelel a reguláris kifejezésnek // VCL.SHOW
, majd mohón felfalja azt a szöveget, amely mindkét számot tartalmazza abban a sorban, és mentse el, ami a művelet után megmarad. Adja ki a tárolt értéket, és fejezze be a programot.
Egyszerű, nem?
Elégedettek voltunk a sed szkripttel és azzal a ténnyel, hogy az összes eredeti kódot lecseréli. Minden tesztem a kívánt eredményt adta, ezért megváltoztattam a "lakk újratöltését" a szerveren, és újra futottam systemctl reload varnish
. Mocskos hiba echo: write error: Broken pipe
- nevetett ismét az arcunkba. Egy kacsintó kurzor új parancsot várt a terminál sötét ürességében...
Forrás: will.com