Խուսափեք այս խնդիրներից՝ սահմանափակելով Bash-ի սցենարները մեկ անգամ գործարկելու համար
Հիմնական Takeaways
- Համոզվեք, որ ձեր սցենարի միայն մեկ օրինակ է աշխատում՝ օգտագործելով pgrep, lsof կամ flock՝ կանխելու համաժամանակյա խնդիրները:
- Հեշտությամբ կատարեք ստուգումներ՝ սկրիպտը ինքնուրույն դադարեցնելու համար, եթե հայտնաբերվեն այլ գործարկվող օրինակներ:
- Օգտագործելով exec և env հրամանները, flock հրամանը կարող է հասնել այս ամենին մեկ տող կոդի միջոցով:
Linux-ի որոշ սկրիպտներ ունեն նման կատարման գերավճար, և պետք է կանխել միանգամից մի քանի օրինակների գործարկումը: Բարեբախտաբար, ձեր սեփական Bash սկրիպտներում դրան հասնելու մի քանի եղանակ կա:
Երբեմն մեկ անգամ բավական է
Որոշ սցենարներ չպետք է գործարկվեն, եթե այդ սկրիպտի նախորդ օրինակը դեռ աշխատում է: Եթե ձեր սկրիպտը սպառում է չափազանց մեծ պրոցեսորի ժամանակ և օպերատիվ հիշողություն, կամ առաջացնում է ցանցի մեծ թողունակություն կամ սկավառակի խզում, դրա կատարումը միաժամանակ մեկ օրինակով սահմանափակելը ողջամտություն է:
Բայց ոչ միայն ռեսուրսների խոզերը պետք է աշխատեն առանձին: Եթե ձեր սկրիպտը փոփոխում է ֆայլերը, կարող է կոնֆլիկտ լինել սկրիպտի երկու (կամ ավելի) օրինակների միջև, քանի որ նրանք կռվում են ֆայլեր մուտք գործելու համար: Թարմացումները կարող են կորչել, կամ ֆայլը կարող է վնասվել:
Այս խնդրահարույց սցենարներից խուսափելու մեթոդներից մեկն այն է, որ սկրիպտը ստուգի, որ իր այլ տարբերակներ չկան: Եթե այն հայտնաբերում է որևէ այլ գործող պատճեն, սցենարն ինքնին կդադարի:
Մեկ այլ տեխնիկա է սցենարի ինժեներական ձևավորումն այնպես, որ այն գործարկվելիս ինքն իրեն արգելափակվի՝ կանխելով այլ պատճենների գործարկումը:
Մենք կդիտարկենք առաջին տեխնիկայի երկու օրինակ, այնուհետև կանդրադառնանք երկրորդը կատարելու մեկ եղանակին:
Օգտագործելով pgrep՝ միաժամանակությունը կանխելու համար
pgrep հրամանը որոնում է Linux համակարգչի վրա աշխատող գործընթացների միջով և վերադարձնում գործընթացների ID-ն, որոնք համապատասխանում են որոնման օրինակին:
Ես ունեմ մի սցենար, որը կոչվում է loop.sh: Այն պարունակում է for loop, որը տպում է օղակի կրկնությունը, այնուհետև քնում է մեկ վայրկյան: Դա անում է տասը անգամ:
#!/bin/bash
for (( i=1; i<=10; i+=1 ))
do
echo "Loop:" $i
sleep 1
done
exit 0
Ես դրեցի դրա գործարկման երկու օրինակ, այնուհետև օգտագործեցի pgrep՝ անունով որոնելու համար:
pgrep loop.sh
Այն գտնում է երկու ատյանները և հայտնում դրանց գործընթացի ID-ները: Մենք կարող ենք ավելացնել -c (count) տարբերակը, որպեսզի pgrep-ը վերադարձնի օրինակների քանակը։
pregp -c loop.sh
Մենք կարող ենք օգտագործել օրինակների այդ թիվը մեր սցենարում: Եթե pgrep-ի վերադարձած արժեքը մեկից մեծ է, ապա պետք է գործարկվի մեկից ավելի օրինակ, և մեր սցենարը կփակվի:
Մենք կստեղծենք սցենար, որն օգտագործում է այս տեխնիկան: Մենք այն կանվանենք pgrep-solo.sh:
If համեմատությունը ստուգում է, թե արդյոք pgrep-ով վերադարձված թիվը մեկից մեծ է: Եթե այդպես է, ապա սցենարը դուրս է գալիս:
# count the instances of this script
if [ $(pgrep -c pgrep-solo.sh) -gt 1 ]; then
echo "Another instance of $0 is running. Stopping."
exit 1
fi
Եթե pgrep-ով վերադարձված թիվը մեկ է, ապա սկրիպտը կարող է շարունակվել: Ահա ամբողջական սցենարը.
#!/bin/bash
echo "Starting."
# count the instances of this script
if [ $(pgrep -c pgrep-solo.sh) -gt 1 ]; then
echo "Another instance of $0 is running. Stopping."
exit 1
fi
# we're cleared for take off
for (( i=1; i<=10; i+=1 ))
do
echo "Loop:" $i
sleep 1
done
exit 0
Պատճենեք սա ձեր նախընտրած խմբագրին և պահեք որպես pgrep-solo.sh: Այնուհետև այն գործարկելի դարձրեք chmod-ով։
chmod +x pgrep-loop.sh
Երբ այն աշխատում է, կարծես սա է.
./pgrep-solo.sh
Բայց եթե ես փորձեմ այն սկսել մեկ այլ օրինակով, որն արդեն աշխատում է մեկ այլ տերմինալի պատուհանում, այն հայտնաբերում է դա և դուրս է գալիս:
./pgrep-solo.sh
Օգտագործելով lsof-ը միաժամանակությունը կանխելու համար
Մենք կարող ենք շատ նման բան անել lsof հրամանով:
Եթե ավելացնենք -t (terse) տարբերակը, lsof-ը թվարկում է գործընթացի ID-ները:
lsof -t loop.sh
Մենք կարող ենք ելքը խողովակավորել lsof-ից լոգարան: -l (գծեր) տարբերակը հաշվում է տողերի քանակը, որոնք այս սցենարում նույնն են գործընթացի ID-ների քանակին:
lsof -t loop.sh | wc -l
Մենք կարող ենք դա օգտագործել որպես թեստի հիմք մեր սցենարի if համեմատության մեջ:
Պահպանեք այս տարբերակը որպես lsof-solo.sh:
#!/bin/bash
echo "Starting."
# count the instances of this script
if [ $(lsof -t "$0" | wc -l) -gt 1 ]; then
echo "Another instance of $0 is running. Stopping."
exit 1
fi
# we're cleared for take off
for (( i=1; i<=10; i+=1 ))
do
echo "Loop:" $i
sleep 1
done
exit 0
Օգտագործեք chmod՝ այն գործարկելի դարձնելու համար:
chmod +x lsof-solo.sh
Այժմ, երբ lsof-solo.sh սկրիպտը աշխատում է մեկ այլ տերմինալի պատուհանում, մենք չենք կարող սկսել երկրորդ պատճենը:
./lsof-solo.sh
pgrep մեթոդը պահանջում է միայն մեկ զանգ դեպի արտաքին ծրագիր (pgrep), lsof մեթոդը պահանջում է երկու (lsof և wc): Սակայն lsof մեթոդի առավելությունը pgrep մեթոդի նկատմամբ այն է, որ եթե համեմատության մեջ կարող եք օգտագործել $0 փոփոխականը: Սա պահում է սցենարի անունը:
Դա նշանակում է, որ դուք կարող եք վերանվանել սցենարը, և այն դեռ կաշխատի: Պետք չէ հիշել, որ խմբագրեք if համեմատության գիծը և տեղադրեք սցենարի նոր անունը: $0 փոփոխականը ներառում է «./» սկրիպտի անվան սկզբում (ինչպես ./lsof-solo.sh), իսկ pgrep-ին դա դուր չի գալիս:
Հոտի օգտագործումը միաժամանակությունը կանխելու համար
Մեր երրորդ տեխնիկան օգտագործում է flock հրամանը, որը նախատեսված է սկրիպտների միջից ֆայլերի և գրացուցակի կողպեքներ սահմանելու համար: Մինչ այն կողպված է, ոչ մի այլ գործընթաց չի կարող մուտք գործել կողպված ռեսուրս:
Այս մեթոդը պահանջում է մեկ տող ավելացնել ձեր սցենարի վերևում:
[ "${GEEKLOCK}" != "$0" ] && exec env GEEKLOCK="$0" flock -en "$0" "$0" "$@" || :
Մենք շուտով կվերծանենք այդ հիերոգլիֆները: Առայժմ, եկեք ստուգենք, որ այն աշխատում է: Պահպանեք այս մեկը որպես flock-solo.sh:
#!/bin/bash
[ "${GEEKLOCK}" != "$0" ] && exec env GEEKLOCK="$0" flock -en "$0" "$0" "$@" || :
echo "Starting."
# we're cleared for take off
for (( i=1; i<=10; i+=1 ))
do
echo "Loop:" $i
sleep 1
done
exit 0
Իհարկե, մենք պետք է այն գործարկելի դարձնենք:
chmod +x flock-solo.sh
Ես սկսեցի սցենարը մեկ տերմինալի պատուհանում, այնուհետև փորձեցի այն նորից գործարկել այլ տերմինալի պատուհանում:
./flock-solo
./flock-solo
./flock-solo
Ես չեմ կարող գործարկել սցենարը, քանի դեռ չի ավարտվել մյուս տերմինալի պատուհանի օրինակը:
Եկեք հանենք այն գիծը, որը կախարդական է դարձնում: Դրա հիմքում հոտի հրամանն է:
flock -en "$0" "$0" "$@"
Flock հրամանն օգտագործվում է ֆայլը կամ գրացուցակը կողպելու և այնուհետև հրաման գործարկելու համար: Մեր օգտագործած տարբերակներն են -e (բացառիկ) և -n (չարգելափակող):
Բացառիկ տարբերակը նշանակում է, որ եթե մենք հաջողակ կողպենք ֆայլը, ոչ ոք չի կարող մուտք գործել այն: Չարգելափակող տարբերակը նշանակում է, որ եթե մենք չենք կարողանում կողպեք ձեռք բերել, մենք անմիջապես դադարում ենք փորձել: Մենք չենք կրկնում որոշակի ժամանակահատված, մենք նրբագեղորեն խոնարհվում ենք անմիջապես:
Առաջին $0-ը ցույց է տալիս այն ֆայլը, որը ցանկանում ենք կողպել: Այս փոփոխականը պահում է ընթացիկ սցենարի անունը:
Երկրորդ $0-ը հրամանն է, որը մենք ցանկանում ենք գործարկել, եթե հաջողվի կողպեք ձեռք բերել: Կրկին, մենք անցնում ենք այս սցենարի անունով: Քանի որ կողպեքն արգելափակում է բոլորին բացի մեզանից, մենք կարող ենք գործարկել սցենարի ֆայլը:
Մենք կարող ենք պարամետրեր փոխանցել գործարկվող հրամանին: Մենք օգտագործում ենք $@՝ այս սկրիպտին փոխանցված հրամանի տողի ցանկացած պարամետր փոխանցելու համար, սկրիպտի նոր կանչին, որը գործարկվելու է flock-ի կողմից:
Այսպիսով, մենք ստացել ենք այս սցենարը, որը արգելափակում է սցենարի ֆայլը, այնուհետև գործարկում է իր մեկ այլ օրինակ: Դա գրեթե այն է, ինչ մենք ուզում ենք, բայց կա մի խնդիր. Երբ երկրորդ դեպքն ավարտվի, առաջին սցենարը կվերսկսի իր մշակումը: Այնուամենայնիվ, մենք ունենք ևս մեկ հնարք՝ դրանով ապահովելու համար, ինչպես կտեսնեք:
Մենք օգտագործում ենք շրջակա միջավայրի փոփոխական, որը մենք անվանում ենք GEEKLOCK՝ ցույց տալու համար, թե արդյոք գործող սկրիպտը պետք է կիրառի կողպեքը, թե ոչ: Եթե սցենարը գործարկվել է, և կողպեք չկա, ապա կողպեքը պետք է կիրառվի: Եթե սկրիպտը գործարկվել է, և կողպեք կա, այն որևէ բան անելու կարիք չունի, այն կարող է պարզապես գործարկել: Գործող սկրիպտի և կողպեքի առկայության դեպքում սկրիպտի այլ օրինակներ չեն կարող գործարկվել:
[ "${GEEKLOCK}" != "$0" ]
Այս թեստը թարգմանվում է որպես «վերադարձ ճշմարիտ, եթե GEEKLOCK միջավայրի փոփոխականը չի դրված սկրիպտի անվան վրա»: Թեստը շղթայված է հրամանի մնացած մասի հետ && (and) և || (կամ): && մասը կատարվում է, եթե թեստը վերադարձնում է true, իսկ || բաժինը կատարվում է, եթե թեստը վերադարձնում է false:
env GEEKLOCK="$0"
Env հրամանն օգտագործվում է փոփոխված միջավայրերում այլ հրամաններ գործարկելու համար: Մենք փոփոխում ենք մեր միջավայրը՝ ստեղծելով GEEKLOCK միջավայրի փոփոխականը և դնելով այն սցենարի անվանման վրա: Հրամանը, որը պատրաստվում է գործարկել env-ը, flock հրամանն է, իսկ flock հրամանը գործարկում է սցենարի երկրորդ օրինակը:
Սկրիպտի երկրորդ օրինակը կատարում է իր ստուգումը` տեսնելու, թե արդյոք GEEKLOCK միջավայրի փոփոխականը չկա, բայց գտնում է, որ գոյություն ունի: || հրամանի բաժինը կատարվում է, որը ոչինչ չի պարունակում, բացի «:» կետից, որն իրականում ոչինչ չի անում: Կատարման ուղին այնուհետև անցնում է սցենարի մնացած մասով:
Բայց մենք դեռ խնդիր ունենք, որ առաջին սցենարը շարունակի իր մշակումը, երբ երկրորդ սցենարն ավարտվի: Դրա լուծումը exec հրամանն է: Սա գործարկում է այլ հրամաններ՝ փոխարինելով զանգի գործընթացը նոր գործարկված գործընթացով:
exec env GEEKLOCK="$0" flock -en "$0" "$0" "$@"
Այսպիսով, ամբողջական հաջորդականությունը հետևյալն է.
- Սցենարը գործարկվում է և չի կարողանում գտնել շրջակա միջավայրի փոփոխականը: && կետը կատարվում է:
- exec-ը գործարկում է env և փոխարինում է բնօրինակ սցենարի գործընթացը նոր env գործընթացով:
- Env գործընթացը ստեղծում է շրջակա միջավայրի փոփոխական և գործարկում flock:
- flock-ը կողպում է սցենարի ֆայլը և գործարկում է սկրիպտի նոր օրինակ, որը հայտնաբերում է շրջակա միջավայրի փոփոխականը և գործարկում || կետը և սցենարը կարող է հասնել իր ավարտին:
- Քանի որ սկզբնական սցենարը փոխարինվել է env գործընթացով, այն այլևս առկա չէ և չի կարող շարունակել իր կատարումը, երբ ավարտվի երկրորդ սցենարը:
- Քանի որ սկրիպտի ֆայլը կողպված է, երբ այն աշխատում է, այլ օրինակներ չեն կարող գործարկվել այնքան ժամանակ, քանի դեռ flock-ի կողմից գործարկված սկրիպտը չի դադարում աշխատել և չի ազատում կողպեքը:
Դա կարող է թվալ որպես Inception-ի սյուժե, բայց այն հիանալի է աշխատում: Այդ մեկ տողը, անշուշտ, դրական արդյունք է տալիս:
Պարզության համար, դա սցենարի ֆայլի կողպեքն է, որը կանխում է այլ օրինակների գործարկումը, այլ ոչ թե շրջակա միջավայրի փոփոխականի հայտնաբերումը: Շրջակա միջավայրի փոփոխականը միայն ասում է աշխատող սկրիպտին կամ սահմանել կողպեքը, կամ որ կողպեքն արդեն տեղում է:
Կողպեք և բեռնեք
Ավելի հեշտ է, քան դուք կարող եք ակնկալել, ապահովել սկրիպտի միայն մեկ օրինակ միանգամից: Այս երեք տեխնիկան էլ աշխատում են: Չնայած այն ամենադյուրինն է շահագործման մեջ, հոտի մեկ երթևեկությունը ամենահեշտն է ցանկացած սցենարի մեջ դնելը: