วิธีการทำให้แน่ใจว่ามีเพียงหนึ่งอินสแตนซ์ของสคริปต์ทุบตี


26

จะเลือกใช้โซลูชันที่ไม่ต้องการเครื่องมือเพิ่มเติม


สิ่งที่เกี่ยวกับไฟล์ล็อค?
Marco

@Marco ผมพบว่าคำตอบนี้จึงใช้ว่า แต่เป็นตามที่ระบุไว้ในความคิดเห็นนี้สามารถสร้างสภาพการแข่งขัน
โทเบียส KIENZLER

3
นี่คือBashFAQ 45
jw013

@ jw013 ขอบคุณ! ดังนั้นอาจมีบางสิ่งที่เหมือนln -s my.pid .lockจะอ้างสิทธิ์การล็อค (ตามด้วยecho $$ > my.pid) และเมื่อเกิดข้อผิดพลาดสามารถตรวจสอบว่า PID ที่เก็บไว้ใน.lockนั้นเป็นอินสแตนซ์ที่ใช้งานจริงของสคริปต์หรือไม่
Tobias Kienzler

คำตอบ:


18

เกือบจะเหมือนคำตอบของ NSG: ใช้ล็อคไดเรกทอรี การสร้างไดเรกทอรีอยู่ภายใต้ลินุกซ์และ unix และ * BSD และระบบปฏิบัติการอื่น ๆ มากมาย

if mkdir $LOCKDIR
then
    # Do important, exclusive stuff
    if rmdir $LOCKDIR
    then
        echo "Victory is mine"
    else
        echo "Could not remove lock dir" >&2
    fi
else
    # Handle error condition
    ...
fi

คุณสามารถใส่ PID ของการล็อค sh ลงในไฟล์ในไดเรกทอรีล็อคเพื่อการดีบั๊ก แต่ไม่ตกหลุมพรางของความคิดคุณสามารถตรวจสอบ PID นั้นเพื่อดูว่ากระบวนการล็อคยังดำเนินการอยู่หรือไม่ สภาพการแข่งขันจำนวนมากนอนลงบนเส้นทางนั้น


1
ฉันจะพิจารณาใช้ PID ที่เก็บไว้เพื่อตรวจสอบว่าอินสแตนซ์การล็อคยังมีชีวิตอยู่หรือไม่ อย่างไรก็ตามนี่เป็นข้อเรียกร้องที่mkdirไม่ได้เป็นอะตอมบน NFS (ซึ่งเป็นกรณีที่ไม่สำหรับผม แต่ผมคิดว่าใครควรจะพูดถึงว่าถ้าเป็นจริง)
โทเบียส KIENZLER

ใช่โดยทั้งหมดใช้ PID ที่เก็บไว้เพื่อดูว่ากระบวนการล็อคยังคงทำงานอยู่หรือไม่ แต่อย่าพยายามทำสิ่งอื่นนอกจากบันทึกข้อความ การทำงานของการตรวจสอบ pid ที่จัดเก็บการสร้างไฟล์ PID ใหม่ ฯลฯ ปล่อยให้หน้าต่างบานใหญ่สำหรับการแข่งขัน
Bruce Ediger

ตกลงดังที่Ihunath ระบุไว้ lockdir น่าจะ/tmpเป็นที่มักจะไม่ใช้ NFS ร่วมกันดังนั้นควรปรับ
Tobias Kienzler

ฉันจะใช้rm -rfเพื่อลบไดเรกทอรีล็อค rmdirจะล้มเหลวหากมีคน (ไม่จำเป็นต้องคุณ) จัดการเพื่อเพิ่มไฟล์ลงในไดเรกทอรี
chepner

18

หากต้องการเพิ่มคำตอบของ Bruce Edigerและได้รับแรงบันดาลใจจากคำตอบนี้คุณควรเพิ่มสมาร์ทมากขึ้นในการล้างข้อมูลเพื่อป้องกันการยกเลิกสคริปต์:

#Remove the lock directory
function cleanup {
    if rmdir $LOCKDIR; then
        echo "Finished"
    else
        echo "Failed to remove lock directory '$LOCKDIR'"
        exit 1
    fi
}

if mkdir $LOCKDIR; then
    #Ensure that if we "grabbed a lock", we release it
    #Works for SIGTERM and SIGINT(Ctrl-C)
    trap "cleanup" EXIT

    echo "Acquired lock, running"

    # Processing starts here
else
    echo "Could not create lock directory '$LOCKDIR'"
    exit 1
fi

อีกทางหนึ่ง, if ! mkdir "$LOCKDIR"; then handle failure to lock and exit; fi trap and do processing after if-statement.
Kusalananda

6

นี่อาจจะง่ายเกินไปโปรดแก้ไขให้ฉันถ้าฉันผิด ไม่ง่ายpsพอใช่ไหม

#!/bin/bash 

me="$(basename "$0")";
running=$(ps h -C "$me" | grep -wv $$ | wc -l);
[[ $running > 1 ]] && exit;

# do stuff below this comment

1
ดีและ / หรือยอดเยี่ยม :)
Spooky

1
ฉันใช้เงื่อนไขนี้เป็นเวลาหนึ่งสัปดาห์และ 2 ครั้งมันไม่ได้ป้องกันกระบวนการใหม่จากการเริ่มต้น ฉันคิดว่าสิ่งที่เป็นปัญหา - pid grep -v $$ใหม่เป็นย่อยของเดิมและได้รับการซ่อนไว้โดย ตัวอย่างจริง: เก่า - 14532, ใหม่ - 1453, เก่า - 28858, ใหม่
858.

ฉันแก้ไขโดยเปลี่ยนgrep -v $$เป็นgrep -v "^${$} "
Naktibalda

@Naktibalda จับดีขอบคุณ! คุณสามารถแก้ไขได้ด้วยgrep -wv "^$$"(ดูการแก้ไข)
terdon

ขอบคุณสำหรับการอัพเดท รูปแบบของฉันล้มเหลวเป็นครั้งคราวเนื่องจากมีขนาดสั้น
Naktibalda

4

ฉันจะใช้ไฟล์ล็อคตามที่ระบุไว้โดย Marco

#!/bin/bash

# Exit if /tmp/lock.file exists
[ -f /tmp/lock.file ] && exit

# Create lock file, sleep 1 sec and verify lock
echo $$ > /tmp/lock.file
sleep 1
[ "x$(cat /tmp/lock.file)" == "x"$$ ] || exit

# Do stuff
sleep 60

# Remove lock file
rm /tmp/lock.file

1
(ฉันคิดว่าคุณลืมที่จะสร้างไฟล์ล็อค) แล้วสภาพการแข่งขันล่ะ?
Tobias Kienzler

ops :) ใช่สภาพการแข่งขันเป็นปัญหาในตัวอย่างของฉันฉันมักจะเขียนงาน cron รายชั่วโมงหรือรายวันและเงื่อนไขการแข่งขันหายาก
nsg

พวกเขาไม่ควรเกี่ยวข้องในกรณีของฉันเช่นกัน แต่เป็นสิ่งที่เราควรคำนึงถึง บางทีการใช้อาจlsof $0ไม่เลวเลยใช่ไหม
Tobias Kienzler

คุณสามารถลดสภาพการแข่งขันโดยการเขียนของคุณ$$ในไฟล์ล็อค จากนั้นsleepช่วงเวลาสั้น ๆ และอ่านมันกลับมา หาก PID ยังคงเป็นของคุณแสดงว่าคุณได้รับการล็อคสำเร็จ ไม่จำเป็นต้องมีเครื่องมือเพิ่มเติม
จัดการ

1
ฉันไม่เคยใช้ lsof เพื่อจุดประสงค์นี้ฉันควรใช้สิ่งนี้ โปรดทราบว่า lsof คือจริงๆช้าในระบบของฉัน (1-2 วินาที) และส่วนใหญ่มีเป็นจำนวนมากของเวลาสำหรับเงื่อนไขการแข่งขัน
nsg

3

หากคุณต้องการให้แน่ใจว่าสคริปต์ของคุณทำงานเพียงตัวอย่างเดียวให้ดูที่:

ล็อคสคริปต์ของคุณ (เทียบกับการรันแบบขนาน)

มิฉะนั้นคุณสามารถตรวจสอบpsหรือเรียกใช้lsof <full-path-of-your-script>เนื่องจากฉันจะไม่เรียกเครื่องมือเพิ่มเติม


อาหารเสริม :

ที่จริงฉันคิดว่าจะทำแบบนี้:

for LINE in `lsof -c <your_script> -F p`; do 
    if [ $$ -gt ${LINE#?} ] ; then
        echo "'$0' is already running" 1>&2
        exit 1;
    fi
done

สิ่งนี้ช่วยให้มั่นใจได้ว่าเฉพาะกระบวนการที่มีระดับต่ำสุดเท่านั้นpidที่สามารถทำงานต่อได้แม้ว่าคุณจะแยกและทำหลาย ๆ อินสแตนซ์<your_script>พร้อมกัน


1
ขอบคุณสำหรับลิงค์ แต่คุณสามารถรวมส่วนสำคัญในคำตอบของคุณ? เป็นนโยบายทั่วไปที่ SE เพื่อป้องกันไม่ให้ลิงค์เน่า ... แต่สิ่งที่คล้ายกัน[[(lsof $0 | wc -l) > 2]] && exitอาจเพียงพอแล้วหรือว่าสิ่งนี้มีแนวโน้มที่จะเกิดสภาพการแข่งขันหรือไม่?
Tobias Kienzler

คุณถูกต้องส่วนสำคัญของคำตอบของฉันหายไปและการโพสต์ลิงค์เท่านั้นเป็นง่อย ฉันเพิ่มข้อเสนอแนะของตัวเองในคำตอบ
user1146332

3

อีกวิธีหนึ่งในการตรวจสอบให้แน่ใจว่าอินสแตนซ์ของสคริปต์ทุบตีทำงาน:

#!/bin/bash

# Check if another instance of script is running
pidof -o %PPID -x $0 >/dev/null && echo "ERROR: Script $0 already running" && exit 1

...

pidof -o %PPID -x $0 รับ PID ของสคริปต์ที่มีอยู่หากมีอยู่แล้วทำงานหรือออกด้วยรหัสข้อผิดพลาด 1 หากไม่มีสคริปต์อื่นกำลังทำงานอยู่


3

แม้ว่าคุณเคยถามสำหรับการแก้ปัญหาโดยไม่ต้องเครื่องมือเพิ่มเติมนี้เป็นวิธีที่ชื่นชอบของฉันโดยใช้flock:

#!/bin/sh

[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :

echo "servus!"
sleep 10

สิ่งนี้มาจากส่วนตัวอย่างของman flockซึ่งอธิบายเพิ่มเติม:

นี่เป็นรหัสสำเร็จรูปที่เป็นประโยชน์สำหรับเชลล์สคริปต์ วางไว้ที่ด้านบนของเชลล์สคริปต์ที่คุณต้องการล็อคและมันจะล็อคตัวเองโดยอัตโนมัติในการรันครั้งแรก หาก env var $ FLOCKER ไม่ได้ถูกตั้งค่าเป็นเชลล์สคริปต์ที่กำลังทำงานอยู่ให้เรียกใช้งานฝูงและคว้าล็อคที่ไม่มีการปิดกั้นแบบเอกสิทธิ์ นอกจากนี้ยังตั้งค่า FLOCKER env var ให้เป็นค่าที่ถูกต้องดังนั้นจึงไม่ทำงานอีกครั้ง

คะแนนที่ต้องพิจารณา:

  • ต้องการflockสคริปต์ตัวอย่างสิ้นสุดลงพร้อมกับข้อผิดพลาดหากไม่พบ
  • ไม่จำเป็นต้องใช้ไฟล์ล็อคเพิ่มเติม
  • อาจไม่ทำงานหากสคริปต์อยู่ใน NFS (ดูhttps://serverfault.com/questions/66919/file-locks-on-an-nfs )

ดูเพิ่มเติมhttps://stackoverflow.com/questions/185451/quick-and-dirty-way-to-ensure-only-one-instance-of-a-shell-script-is-running-at


1

นี้เป็นรุ่นที่ปรับเปลี่ยนจากคำตอบอันเซลโม่ แนวคิดคือการสร้าง descriptor ไฟล์อ่านอย่างเดียวโดยใช้สคริปต์ทุบตีเองและใช้flockเพื่อจัดการล็อค

SCRIPT=`realpath $0`     # get absolute path to the script itself
exec 6< "$SCRIPT"        # open bash script using file descriptor 6
flock -n 6 || { echo "ERROR: script is already running" && exit 1; }   # lock file descriptor 6 OR show error message if script is already running

echo "Run your single instance code here"

ข้อแตกต่างที่สำคัญของคำตอบอื่น ๆ คือรหัสนี้ไม่ได้แก้ไขระบบไฟล์ใช้ footprint ที่ต่ำมากและไม่ต้องการการล้างข้อมูลใด ๆ เนื่องจากไฟล์ descriptor ถูกปิดทันทีที่สคริปต์เสร็จสิ้นโดยไม่ขึ้นกับสถานะทางออก ดังนั้นจึงไม่สำคัญว่าสคริปต์จะล้มเหลวหรือไม่สำเร็จ


คุณควรอ้างอิงการอ้างอิงตัวแปรเชลล์ทั้งหมดเว้นแต่ว่าคุณไม่มีเหตุผลที่ดีและคุณแน่ใจว่าคุณรู้ว่าคุณกำลังทำอะไรอยู่ exec 6< "$SCRIPT"ดังนั้นคุณควรจะทำ
สกอตต์

@Scott ฉันได้เปลี่ยนรหัสตามคำแนะนำของคุณ ขอบคุณมาก.
John Doe

1

ฉันใช้ cksum เพื่อตรวจสอบสคริปต์ของฉันอย่างแท้จริงทำงานเช่นเดียวแม้ฉันจะเปลี่ยนเส้นทางและชื่อไฟล์ไฟล์

ฉันไม่ได้ใช้ไฟล์แทร็ป & ล็อกเพราะหากเซิร์ฟเวอร์ของฉันหยุดทำงานฉันต้องลบไฟล์ล็อคด้วยตนเองหลังจากที่เซิร์ฟเวอร์ขึ้น

หมายเหตุ: จำเป็นต้องมี #! / bin / bash ในบรรทัดแรกสำหรับ grep ps

#!/bin/bash

checkinstance(){
   nprog=0
   mysum=$(cksum $0|awk '{print $1}')
   for i in `ps -ef |grep /bin/bash|awk '{print $2}'`;do 
        proc=$(ls -lha /proc/$i/exe 2> /dev/null|grep bash) 
        if [[ $? -eq 0 ]];then 
           cmd=$(strings /proc/$i/cmdline|grep -v bash)
                if [[ $? -eq 0 ]];then 
                   fsum=$(cksum /proc/$i/cwd/$cmd|awk '{print $1}')
                   if [[ $mysum -eq $fsum ]];then
                        nprog=$(($nprog+1))
                   fi
                fi
        fi
   done

   if [[ $nprog -gt 1 ]];then
        echo $0 is already running.
        exit
   fi
}

checkinstance 

#--- run your script bellow 

echo pass
while true;do sleep 1000;done

หรือคุณสามารถ hardcoded cksum ภายในสคริปต์ของคุณเพื่อให้คุณไม่ต้องกังวลอีกครั้งถ้าคุณต้องการที่จะเปลี่ยนแปลงชื่อไฟล์เส้นทางหรือเนื้อหาของสคริปต์ของคุณ

#!/bin/bash

mysum=1174212411

checkinstance(){
   nprog=0
   for i in `ps -ef |grep /bin/bash|awk '{print $2}'`;do 
        proc=$(ls -lha /proc/$i/exe 2> /dev/null|grep bash) 
        if [[ $? -eq 0 ]];then 
           cmd=$(strings /proc/$i/cmdline|grep -v bash)
                if [[ $? -eq 0 ]];then 
                   fsum=$(grep mysum /proc/$i/cwd/$cmd|head -1|awk -F= '{print $2}')
                   if [[ $mysum -eq $fsum ]];then
                        nprog=$(($nprog+1))
                   fi
                fi
        fi
   done

   if [[ $nprog -gt 1 ]];then
        echo $0 is already running.
        exit
   fi
}

checkinstance

#--- run your script bellow

echo pass
while true;do sleep 1000;done

1
โปรดอธิบายว่าการตรวจสอบฮาร์ดโค้ดเป็นวิธีที่ดีหรือไม่
สกอตต์

ไม่ใช่การตรวจสอบฮาร์ดโค้ดการสร้างรหัสประจำตัวของสคริปต์ของคุณเท่านั้นเมื่ออินสแตนซ์อื่นกำลังทำงานอยู่มันจะตรวจสอบกระบวนการเชลล์สคริปต์อื่น ๆ และทำการจัดวางไฟล์ก่อนหากรหัสประจำตัวของคุณอยู่ในไฟล์นั้น
arputra

ตกลง; โปรดแก้ไขคำตอบของคุณเพื่ออธิบาย และในอนาคตโปรดอย่าโพสต์บล็อกยาว ๆ หลายบรรทัดที่ดูเหมือนว่าพวกเขา (เกือบ) เหมือนกันโดยไม่ต้องพูดและอธิบายถึงความแตกต่าง และอย่าพูดอะไรเช่น“ คุณสามารถ hardcoded [sic] cksum ภายในสคริปต์ของคุณ” และอย่าใช้ชื่อตัวแปรต่อไปmysumและfsumเมื่อคุณไม่ได้พูดถึงเช็คซัมอีกต่อไป
สกอตต์

ดูน่าสนใจขอบคุณ! และยินดีต้อนรับสู่ unix.stackexchange :)
Tobias Kienzler


0

รหัสของฉันกับคุณ

#!/bin/bash

script_file="$(/bin/readlink -f $0)"
lock_file=${script_file////_}

function executing {
  echo "'${script_file}' already executing"
  exit 1
}

(
  flock -n 9 || executing

  sleep 10

) 9> /var/lock/${lock_file}

จากman flockการปรับปรุงเท่านั้น:

  • ชื่อของไฟล์ล็อคที่จะขึ้นอยู่กับชื่อเต็มของสคริปต์
  • ข้อความ executing

ที่ฉันใส่ที่นี่sleep 10คุณสามารถใส่สคริปต์หลักทั้งหมด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.