«Նայիր այստեղ», - ասում է նա՝ մատնացույց անելով էկրանի հերոսներից մեկին, «Ես գրազ եմ գալիս իմ կարմիր գլխարկի վրա, որ եթե այստեղ ավելացնենք այն, ինչ ես ուղարկել եմ քեզ», - մատնացույց անելով կոդի մեկ այլ բաժին, - «սխալն այլևս չի լինի: ցուցադրվի»։
Մի փոքր շփոթված և հոգնած, ես փոխում եմ sed հայտարարությունը, որի վրա մենք որոշ ժամանակ աշխատել ենք, պահում եմ ֆայլը և գործարկում systemctl varnish reload
. Սխալի հաղորդագրությունն անհետացել է...
«Այն նամակները, որոնք ես փոխանակեցի թեկնածուի հետ,- շարունակեց իմ գործընկերը, երբ նրա ժպիտը վերածվում էր ուրախությամբ լի անկեղծ ժպիտի,- հանկարծ հասկացա, որ սա նույն խնդիրն է»:
Ինչպես սկսվեց ամեն ինչ
Հոդվածը ենթադրում է հասկանալ, թե ինչպես են աշխատում bash-ը, awk-ը, sed-ը և systemd-ը: Լաքի իմացությունը նախընտրելի է, բայց պարտադիր չէ։
Հատվածներում ժամանակի դրոշմակնիքները փոխվել են:
Գրված է
Այս տեքստը երկու շաբաթ առաջ անգլերեն հրատարակված բնագրի թարգմանությունն է. թարգմանությունը
Արևը շողում է համայնապատկերային պատուհանների միջով աշնանային մեկ այլ տաք առավոտ, մի բաժակ թարմ կոֆեինացված ըմպելիքը նստած է ստեղնաշարի կողքին, ականջակալներում հնչում է հնչյունների սիրված սիմֆոնիան՝ մեխանիկական ստեղնաշարի խշխշոցի վրա, և առաջին մուտքը Կանբանի տախտակի վրա մնացած տոմսերի ցուցակը զվարճալի կերպով փայլում է «Հետազոտել լաքի բեռնումը sh. echo: I/O սխալ բեմադրության մեջ» (հետազոտել «varnishreload sh: echo: I/O error» բեմադրության մեջ) ճակատագրական վերնագրով: Ինչ վերաբերում է լաքին, ապա սխալներ չկան և չեն կարող լինել, նույնիսկ եթե դրանք խնդիրներ չեն առաջացնում, ինչպես այս դեպքում:
Для тех, кто не знаком с
Ինչպես հուշում է տոմսի վերնագիրը, սխալը տեղի է ունեցել բեմի սերվերներից մեկի վրա, և քանի որ ես վստահ էի, որ լաքի երթուղիչը բեմում ճիշտ է աշխատում, ես ենթադրեցի, որ դա աննշան սխալ կլիներ: Այսպիսով, ընդամենը մի հաղորդագրություն, որն արդեն փակված ելքային հոսքի մեջ է մտել: Ես տոմս եմ վերցնում ինձ համար՝ լիովին վստահ լինելով, որ կնշեմ այն պատրաստ է 30 րոպեից պակաս ժամանակում, թփթփացնում եմ ուսս՝ հաջորդ անպետք նյութերից տախտակը մաքրելու համար և կվերադառնամ ավելի կարևոր բաներին:
200 կմ/ժ արագությամբ բախվելով պատին
Ֆայլի բացում varnishreload
, Debian Stretch-ով աշխատող սերվերներից մեկում ես տեսա 200 տողից պակաս երկարությամբ shell script:
Անցնելով սցենարը, ես չտեսա որևէ բան, որը կարող էր խնդիրներ առաջացնել այն մի քանի անգամ անմիջապես տերմինալից գործարկելիս:
Ի վերջո, սա փուլ է, եթե նույնիսկ կոտրվի, ոչ ոք չի բողոքի, լավ ... ոչ շատ: Ես գործարկում եմ սցենարը և տեսնում եմ, թե ինչ է դուրս գրվելու տերմինալում, բայց սխալներն այլևս չեն երևում:
Եվս մի քանի վազք՝ համոզվելու համար, որ չեմ կարող վերարտադրել սխալն առանց լրացուցիչ ջանքերի, և ես սկսում եմ պարզել, թե ինչպես փոխել այս սցենարը և այնպես անել, որ այն դեռ սխալվի:
Կարո՞ղ է սցենարն արգելափակել STDOUT-ը (օգտագործելով > &-
)? Կամ STDERR? Երկուսն էլ վերջում չաշխատեցին:
Ակնհայտորեն systemd-ը ինչ-որ կերպ փոխում է գործարկման միջավայրը, բայց ինչպես և ինչու:
Միացնում եմ vim-ը և խմբագրում varnishreload
, ավելացնելով set -x
հենց շեբանգի տակ՝ հուսալով, որ սցենարի արդյունքի վրիպազերծումը որոշակի լույս կսփռի:
Ֆայլը ֆիքսված է, այնպես որ ես վերաբեռնում եմ լաքը և տեսնում եմ, որ փոփոխությունն ամբողջությամբ կոտրել է ամեն ինչ... Արտանետումը լրիվ խառնաշփոթ է, դրա մեջ C-ի նման տոննա կոդ կա: Անգամ տերմինալում պտտելը բավարար չէ՝ գտնելու, թե որտեղից է այն սկսվում: Ես լրիվ շփոթված եմ։ Կարո՞ղ է վրիպազերծման ռեժիմն ազդել սցենարով աշխատող ծրագրերի աշխատանքի վրա: Ոչ, հիմարություն: Սխա՞լ է պատյանում: Մի քանի հնարավոր սցենարներ գլխումս ուտիճների պես թռչում են տարբեր ուղղություններով։ Մի բաժակ կոֆեինով լի ըմպելիքն ակնթարթորեն դատարկվում է, արագ ուղևորություն դեպի խոհանոց՝ լիցքավորման համար և… արի գնանք: Ես բացում եմ սցենարը և ավելի մոտիկից նայում շեբանգին. #!/bin/sh
.
/bin/sh
- սա պարզապես bash symlink է, այնպես որ սցենարը մեկնաբանվում է POSIX-ի հետ համատեղելի ռեժիմով, այնպես չէ՞: Այն չկար։ Debian-ի լռելյայն կեղևը dash է, ինչը հենց այն է /bin/sh
.
# ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Jan 24 2017 /bin/sh -> dash
Դատավարության համար ես փոխեցի շեբանգը #!/bin/bash
, հեռացված set -x
և նորից փորձեց: Ի վերջո, լաքի հետագա վերաբեռնման ժամանակ ելքում հայտնվեց տանելի սխալ.
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, ահա այն:
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 }
Բայց, ինչպես պարզվեց, 124 տողը բավականին դատարկ է և ոչ մի հետաքրքրություն չի ներկայացնում։ Ես կարող էի միայն ենթադրել, որ սխալը տեղի է ունեցել որպես 116-րդ տողից սկսվող բազմագիծ:
Ինչ է վերջապես գրված փոփոխականին VCL_FILE
վերը նշված ենթափեղկը գործարկելու արդյունքում?
Սկզբում այն ուղարկում է փոփոխականի բովանդակությունը VLC_SHOW
, որը ստեղծվել է 115 տողում, խողովակի միջոցով հաջորդ հրամանին: Եվ հետո ինչ է տեղի ունենում այնտեղ:
Նախ, այն օգտագործում է varnishadm
, որը լաքի տեղադրման փաթեթի մի մասն է, լաքը կարգավորելու համար առանց վերագործարկման:
ենթահրաման vcl.show -v
օգտագործվում է ամբողջ VCL կոնֆիգուրացիան դուրս բերելու համար, որը նշված է ${VCL_NAME}
, դեպի STDAUT.
Ներկայումս ակտիվ VCL կոնֆիգուրացիան, ինչպես նաև լաքի երթուղային կազմաձևերի մի քանի նախորդ տարբերակները ցուցադրելու համար, որոնք դեռ հիշողության մեջ են, կարող եք օգտագործել հրամանը. varnishadm vcl.list
, որի ելքը նման կլինի հետևյալին.
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
Փոփոխական արժեք ${VCL_NAME}
դրված սցենարի մեկ այլ մասում varnishreload
ներկայումս ակտիվ VCL-ի անվանմանը, եթե այդպիսիք կան: Այս դեպքում դա կլինի «reload_20190101_120000_12397»:
Լավ, փոփոխական: ${VCL_SHOW}
պարունակում է լաքի ամբողջական կոնֆիգուրացիան՝ առայժմ պարզ: Այժմ ես վերջապես հասկանում եմ, թե ինչու է dash արտադրանքը set -x
պարզվեց, որ այնքան կոտրված է. այն ներառում էր ստացված կոնֆիգուրացիայի բովանդակությունը:
Կարևոր է հասկանալ, որ VCL-ի ամբողջական կազմաձևը հաճախ կարելի է միացնել բազմաթիվ ֆայլերից: C ոճի մեկնաբանություններն օգտագործվում են որոշելու համար, թե որտեղ է ներառված մի կազմաձևման ֆայլը մյուսում, և դա հենց այն է, ինչ վերաբերում է կոդի հատվածի հետևյալ տողին:
Ներառված ֆայլերը նկարագրող մեկնաբանությունների շարահյուսությունն ունի հետևյալ ձևաչափը.
// VCL.SHOW <NUM> <NUM> <FILENAME>
Այս համատեքստում թվերը կարևոր չեն, մեզ հետաքրքրում է ֆայլի անվանումը։
Այսպիսով, ինչ է տեղի ունենում հրամանների ճահիճում, որը սկսվում է 116 տողից:
Եկեք պարզենք այն:
Հրամանը բաղկացած է չորս մասից.
- Պարզ
echo
, որը ցուցադրում է փոփոխականի արժեքը${VCL_SHOW}
echo "$VCL_SHOW"
awk
, որը փնտրում է տող (գրառում), որտեղ առաջին դաշտը, տեքստը բաժանելուց հետո, կլինի «//», իսկ երկրորդը՝ «VCL.SHOW»:
Awk-ը դուրս կգրի առաջին տողը, որը համապատասխանում է այս օրինաչափություններին, ապա անմիջապես կդադարեցնի մշակումը:awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
- Կոդի բլոկ, որը պահում է դաշտի արժեքները հինգ փոփոխականներում՝ բաժանված բացատներով: Հինգերորդ FILE փոփոխականը ստանում է տողի մնացած մասը: Վերջապես, վերջին արձագանքը դուրս է գրում փոփոխականի բովանդակությունը
${FILE}
.{ read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
- Քանի որ 1-ից 3-րդ բոլոր քայլերը կցվում են ենթափեղկի մեջ, արժեքի ելքը
$FILE
գրվելու է փոփոխականի վրաVCL_FILE
.
Ինչպես հուշում է 119-րդ տողում տրված մեկնաբանությունը, սա ծառայում է միայն այն նպատակին, որ հուսալիորեն կարգավորի այն դեպքերը, երբ VCL-ը կվերաբերի ֆայլերին իրենց անուններում բացատ նիշերով:
Ես մեկնաբանել եմ նախնական մշակման տրամաբանությունը ${VCL_FILE}
և փորձել է փոխել հրամանների հաջորդականությունը, բայց դա ոչ մի բանի չի հանգեցրել։ Ինձ մոտ ամեն ինչ մաքուր էր աշխատում, իսկ ծառայությունը սկսելու դեպքում սխալ էր տալիս։
Թվում է, թե սխալը պարզապես չի վերարտադրվում սցենարը ձեռքով գործարկելիս, մինչդեռ գնահատված 30 րոպեն արդեն ավարտվել է վեց անգամ և, բացի այդ, առաջացել է ավելի առաջնահերթ խնդիր՝ մի կողմ մղելով մնացած դեպքերը։ Շաբաթվա մնացած մասը հագեցած էր մի շարք առաջադրանքներով և միայն թեթևակի թուլացավ sed-ի մասին զրույցով և թեկնածուի հետ հարցազրույցով: Սխալ խնդիր է varnishreload
անվերադարձ կորած ժամանակի ավազների մեջ:
Ձեր այսպես կոչված sed-fu... իրականում... զիբիլ
Հաջորդ շաբաթը բավական ազատ օր ունեցավ, ուստի ես որոշեցի նորից վերցնել այս տոմսը: Ես հույս ունեի, որ իմ ուղեղում ինչ-որ ֆոնային գործընթաց այս ամբողջ ընթացքում փնտրում էր այս խնդրի լուծումը, և այս անգամ ես անպայման կհասկանամ, թե ինչն է սխալ:
Քանի որ նախորդ անգամ պարզապես կոդը փոխելը չօգնեց, ես պարզապես որոշեցի այն վերաշարադրել՝ սկսած 116-րդ տողից։ Ամեն դեպքում, գոյություն ունեցող օրենսգիրքը հիմար էր։ Եվ բացարձակապես կարիք չկա օգտագործելու read
.
Կրկին նայելով սխալին.
sh: echo: broken pipe
- այս հրամանում արձագանքը երկու տեղ է, բայց ես կասկածում եմ, որ առաջինն ավելի հավանական մեղավորն է (կամ գոնե հանցակիցը): Awk-ը նույնպես վստահություն չի ներշնչում: Իսկ այն դեպքում, երբ դա իսկապես այդպես է awk | {read; echo}
դիզայնը հանգեցնում է այս բոլոր խնդիրների, ինչու չփոխարինել այն: Այս մեկ տող հրամանը չի օգտագործում awk-ի բոլոր հնարավորությունները և նույնիսկ այս լրացուցիչը read
հավելվածում։
Անցած շաբաթվանից հաշվետվություն կար sed
Ես ուզում էի փորձել իմ նոր ձեռք բերած հմտությունները և պարզեցնել echo | awk | { read; echo}
ավելի հասկանալի ձևով echo | sed
. Թեև սա, անկասկած, սխալը բռնելու լավագույն մոտեցումը չէ, ես մտածեցի, որ գոնե փորձեմ իմ sed-fu-ն և միգուցե նոր բան սովորեմ խնդրի մասին: Ճանապարհին ես խնդրեցի իմ գործընկերոջը՝ sed talk գրողին, օգնել ինձ ավելի արդյունավետ sed սցենար ստեղծելու համար:
Ես բաց թողեցի բովանդակությունը varnishadm vcl.show -v "$VCL_NAME"
ֆայլի վրա, որպեսզի կարողանամ կենտրոնանալ sed սցենարը գրելու վրա՝ առանց ծառայության վերագործարկման դժվարությունների:
Հակիրճ նկարագրություն, թե ինչպես է sed-ը կարգավորում մուտքագրումը n
հստակորեն նշված է որպես գծի բաժանարար:
Մի քանի անցումներով և իմ գործընկերոջ խորհրդով մենք գրեցինք sed սցենար, որը տվեց նույն արդյունքը, ինչ ամբողջ բնօրինակ 116 տողը:
Ստորև բերված է մուտքագրված ֆայլի նմուշ.
> 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
Միգուցե վերը նշված նկարագրությունից դա ակնհայտ չէ, բայց մեզ հետաքրքրում է միայն առաջին մեկնաբանությունը // VCL.SHOW
, և դրանցից մի քանիսը կարող են լինել մուտքային տվյալների մեջ: Ահա թե ինչու սկզբնական awk-ն ավարտվում է առաջին հանդիպումից հետո:
# шаг первый, вывести только строки с комментариями
# используя возможности 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
Այսպիսով, varnishreload սցենարի բովանդակությունը նման կլինի հետևյալին.
VCL_FILE="$(echo "$VCL_SHOW" | sed -En '#// VCL.SHOW#{s#.*[0-9]+ [0-9]+ (.*)$#1#p;q;};')"
Վերոնշյալ տրամաբանությունը կարելի է ամփոփել հետևյալ կերպ.
Եթե տողը համապատասխանում է կանոնավոր արտահայտությանը // VCL.SHOW
, այնուհետև ագահորեն խժռեք տեքստը, որը ներառում է այդ տողում երկու թվերն էլ, և պահպանեք այն, ինչ մնացել է այս գործողությունից հետո: Թողարկեք պահված արժեքը և ավարտեք ծրագիրը:
Պարզ, այնպես չէ՞:
Մենք գոհ էինք sed script-ից և այն փաստից, որ այն փոխարինում է ամբողջ բնօրինակ կոդը: Իմ բոլոր թեստերը տվեցին ցանկալի արդյունքները, ուստի ես փոխեցի «varnishreload» սերվերի վրա և նորից վազեցի systemctl reload varnish
. Կեղտոտ սխալ echo: write error: Broken pipe
նորից ծիծաղեց մեր դեմքին: Աչքով անող կուրսորը սպասում էր տերմինալի մութ դատարկության մեջ նոր հրամանի մուտքագրմանը...
Source: www.habr.com