Խուսափեք այս խնդիրներից՝ սահմանափակելով 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-ի սյուժե, բայց այն հիանալի է աշխատում: Այդ մեկ տողը, անշուշտ, դրական արդյունք է տալիս:

Պարզության համար, դա սցենարի ֆայլի կողպեքն է, որը կանխում է այլ օրինակների գործարկումը, այլ ոչ թե շրջակա միջավայրի փոփոխականի հայտնաբերումը: Շրջակա միջավայրի փոփոխականը միայն ասում է աշխատող սկրիպտին կամ սահմանել կողպեքը, կամ որ կողպեքն արդեն տեղում է:

Կողպեք և բեռնեք

Ավելի հեշտ է, քան դուք կարող եք ակնկալել, ապահովել սկրիպտի միայն մեկ օրինակ միանգամից: Այս երեք տեխնիկան էլ աշխատում են: Չնայած այն ամենադյուրինն է շահագործման մեջ, հոտի մեկ երթևեկությունը ամենահեշտն է ցանկացած սցենարի մեջ դնելը: