วิธีที่รวดเร็วและสกปรกเพื่อให้แน่ใจว่ามีเพียงหนึ่งอินสแตนซ์ของเชลล์สคริปต์เท่านั้นที่ทำงานในแต่ละครั้ง


179

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


เกี่ยวข้อง @ Unix.SE: unix.stackexchange.com/questions/22044/…
Palec

คำตอบ:


109

นี่คือการใช้งานที่ใช้lockfileและสะท้อน PID ลงไป สิ่งนี้ทำหน้าที่ป้องกันหากกระบวนการถูกฆ่าก่อนที่จะลบpidfile :

LOCKFILE=/tmp/lock.txt
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
    echo "already running"
    exit
fi

# make sure the lockfile is removed when we exit and then claim it
trap "rm -f ${LOCKFILE}; exit" INT TERM EXIT
echo $$ > ${LOCKFILE}

# do stuff
sleep 1000

rm -f ${LOCKFILE}

เคล็ดลับที่นี่คือสิ่งkill -0ที่ไม่ส่งสัญญาณใด ๆ แต่เพียงตรวจสอบว่ากระบวนการที่มี PID ที่ระบุนั้นมีอยู่หรือไม่ นอกจากนี้การเรียกเพื่อtrapให้แน่ใจว่าlockfileถูกลบออกแม้ว่ากระบวนการของคุณจะถูกฆ่า (ยกเว้นkill -9)


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

1
เคล็ดลับ symlink นั้นเรียบร้อย แต่ถ้าเจ้าของ lockfile นั้นถูกฆ่า -9 หรือระบบล่มก็ยังมีสภาพการแข่งขันที่จะอ่าน symlink ให้สังเกตว่าเจ้าของนั้นหายไปแล้วจึงลบมันออก ฉันติดกับโซลูชันของฉัน
bmdhacks

9
การตรวจสอบและสร้างอะตอมมิกมีอยู่ในเชลล์โดยใช้ flock (1) หรือ lockfile (1) ดูคำตอบอื่น ๆ
dmckee --- ผู้ดูแลอดีตลูกแมว

3
ดูคำตอบของฉันสำหรับวิธีพกพาในการทำการตรวจสอบอะตอมและสร้างโดยไม่ต้องพึ่งพาสาธารณูปโภคเช่นฝูงหรือล็อคไฟล์
lhunath

2
นี่ไม่ใช่อะตอมและไม่มีประโยชน์อะไร คุณต้องมีกลไกอะตอมมิกสำหรับการทดสอบและตั้งค่า
K Richard Pixley

214

ใช้flock(1)เพื่อสร้างการล็อกแบบ จำกัด ขอบเขตบนตัวให้คำอธิบายไฟล์ วิธีนี้คุณสามารถซิงโครไนซ์ส่วนต่าง ๆ ของสคริปต์ได้

#!/bin/bash

(
  # Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
  flock -x -w 10 200 || exit 1

  # Do stuff

) 200>/var/lock/.myscript.exclusivelock

สิ่งนี้ทำให้มั่นใจได้ว่ารหัสระหว่าง(และ)ทำงานเพียงครั้งละหนึ่งกระบวนการเท่านั้นและกระบวนการนั้นไม่ต้องรอนานเกินไปสำหรับการล็อค

ข้อแม้: util-linuxคำสั่งนี้โดยเฉพาะอย่างยิ่งเป็นส่วนหนึ่งของ หากคุณใช้ระบบปฏิบัติการอื่นที่ไม่ใช่ Linux อาจเป็นหรือไม่สามารถใช้งานได้


11
200 คืออะไร มันบอกว่า "fd" ใน manul แต่ฉันไม่รู้ว่ามันแปลว่าอะไร
chovy

4
@chovy "file descriptor" เป็นจำนวนเต็มจัดการกำหนดเปิดไฟล์
Alex B

6
ถ้าใครอื่นจะสงสัย: ไวยากรณ์( command A ) command Bเรียก subshell command Aสำหรับ เอกสารที่tldp.org/LDP/abs/html/subshells.html ฉันยังไม่แน่ใจเกี่ยวกับช่วงเวลาของการเรียกร้องของ subshell และสั่ง B.
ดร. Jan-Philip Gehrcke

1
ฉันคิดว่ารหัสภายใน sub-shell ควรเป็นดังนี้: if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fiดังนั้นหากการหมดเวลาเกิดขึ้น (กระบวนการอื่นมีไฟล์ถูกล็อค) สคริปต์นี้จะไม่ไปข้างหน้าและแก้ไขไฟล์ อาจเป็น ... อาร์กิวเมนต์โต้แย้งคือ 'แต่ถ้าใช้เวลา 10 วินาทีและการล็อกยังไม่พร้อมใช้งานมันจะไม่สามารถใช้งานได้' สันนิษฐานว่าเป็นเพราะกระบวนการในการล็อคไม่ได้สิ้นสุดลง (อาจจะมีการเรียกใช้ ภายใต้ดีบักเกอร์?)
Jonathan Leffler

1
ไฟล์ที่ถูกเปลี่ยนเส้นทางไปยังเป็นเพียงโฟลเดอร์สำหรับล็อคเพื่อดำเนินการต่อไม่มีข้อมูลที่มีความหมายเข้ามา จากส่วนภายในexit ( )เมื่อกระบวนการย่อยสิ้นสุดลงการล็อกจะถูกนำออกโดยอัตโนมัติเนื่องจากไม่มีกระบวนการที่จะทำการล็อค
clacke

158

วิธีการทั้งหมดที่ทดสอบการมีอยู่ของ "ล็อกไฟล์" นั้นมีข้อบกพร่อง

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

คุณต้องใช้mkdirแทน mkdirสร้างไดเรกทอรีถ้ามันยังไม่มีอยู่และถ้ามันทำมันจะตั้งรหัสทางออก ที่สำคัญมันทำทั้งหมดนี้ในการกระทำแบบอะตอมเดียวทำให้มันสมบูรณ์แบบสำหรับสถานการณ์นี้

if ! mkdir /tmp/myscript.lock 2>/dev/null; then
    echo "Myscript is already running." >&2
    exit 1
fi

สำหรับรายละเอียดทั้งหมดดู BashFAQ ที่ยอดเยี่ยม: http://mywiki.wooledge.org/BashFAQ/045

หากคุณต้องการดูแลล็อคเก่า ๆฟิวเซอร์ (1)มีประโยชน์ ข้อเสียเพียงอย่างเดียวที่นี่คือการดำเนินการใช้เวลาประมาณหนึ่งวินาทีดังนั้นมันจึงไม่ใช่ทันที

นี่เป็นฟังก์ชั่นที่ฉันเขียนหนึ่งครั้งเพื่อแก้ปัญหาโดยใช้ fuser

#       mutex file
#
# Open a mutual exclusion lock on the file, unless another process already owns one.
#
# If the file is already locked by another process, the operation fails.
# This function defines a lock on a file as having a file descriptor open to the file.
# This function uses FD 9 to open a lock on the file.  To release the lock, close FD 9:
# exec 9>&-
#
mutex() {
    local file=$1 pid pids 

    exec 9>>"$file"
    { pids=$(fuser -f "$file"); } 2>&- 9>&- 
    for pid in $pids; do
        [[ $pid = $$ ]] && continue

        exec 9>&- 
        return 1 # Locked by a pid.
    done 
}

คุณสามารถใช้มันในสคริปต์เช่นโดย:

mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }

หากคุณไม่สนใจเกี่ยวกับการพกพา (แก้ปัญหาเหล่านี้ควรจะทำงานบนสวยมาก ๆ กล่อง UNIX), ลินุกซ์ฟิวเซอร์ (1)มีตัวเลือกเพิ่มเติมบางอย่างและยังมีฝูง (1)


1
คุณสามารถรวมif ! mkdirส่วนกับการตรวจสอบว่ากระบวนการกับ PID ที่เก็บไว้ (เมื่อเริ่มต้นที่สำเร็จ) ภายใน lockdir ทำงานจริง ๆและเหมือนกับสคริปต์สำหรับการป้องกัน stalenes สิ่งนี้จะช่วยป้องกันการนำ PID กลับมาใช้ใหม่หลังจากรีบูตและไม่จำเป็นต้องใช้fuserอีกด้วย
Tobias Kienzler

4
แน่นอนว่าเป็นความจริงที่mkdirไม่ได้นิยามว่าเป็นการดำเนินการแบบอะตอมมิกและ "ผลข้างเคียง" เป็นรายละเอียดการนำไปใช้งานของระบบไฟล์ ฉันเชื่อว่าเขาอย่างสมบูรณ์ถ้าเขาบอกว่า NFS ไม่ได้นำไปใช้ในแบบอะตอมมิก แม้ว่าฉันไม่สงสัยว่าคุณ/tmpจะแบ่งปัน NFS และมีแนวโน้มที่จะได้รับจาก fs ที่ดำเนินการแบบmkdirอะตอม
lhunath

5
แต่มีวิธีการตรวจสอบว่ามีไฟล์ปกติอยู่หรือไม่และสร้างเป็นแบบอะตอมถ้าไม่ใช้lnเพื่อสร้างฮาร์ดลิงก์จากไฟล์อื่น หากคุณมีระบบไฟล์แปลก ๆ ซึ่งไม่รับประกันว่าคุณจะสามารถตรวจสอบไอโหนดของไฟล์ใหม่หลังจากนั้นเพื่อดูว่ามันเป็นเหมือนกับไฟล์ต้นฉบับหรือไม่
Juan Cespedes

4
มีเป็น 'วิธีการตรวจสอบว่าไฟล์ที่มีอยู่และสร้างมันขึ้นมาในการดำเนินการของอะตอมเดียว' - open(... O_CREAT|O_EXCL)มัน คุณเพียงต้องการโปรแกรมผู้ใช้ที่เหมาะสมในการทำเช่นlockfile-create(ในlockfile-progs) หรือdotlockfile(ในliblockfile-bin) และตรวจสอบให้แน่ใจว่าคุณทำความสะอาดอย่างถูกต้อง (เช่นtrap EXIT) หรือทดสอบการล็อคเก่า (เช่นกับ--use-pid)
Toby Speight

5
"วิธีการทั้งหมดที่ทดสอบการมีอยู่ของ" ล็อกไฟล์ "นั้นมีข้อบกพร่องเพราะเหตุใดเนื่องจากไม่มีวิธีการตรวจสอบว่ามีไฟล์อยู่และสร้างขึ้นในการกระทำแบบอะตอมมิกเดียวหรือไม่" - เพื่อให้มันเป็นอะตอม ระดับเคอร์เนล - และจะทำที่ระดับเคอร์เนลด้วย flock (1) linux.die.net/man/1/flockซึ่งปรากฏจากวันที่ลิขสิทธ์คนมีประมาณตั้งแต่อย่างน้อยปี 2006 ดังนั้นฉันจึงทำการ downvote (- 1) ไม่มีอะไรที่เป็นส่วนตัวมีความเชื่อมั่นอย่างสูงว่าการใช้เคอร์เนลที่ใช้โดยเครื่องมือที่ผู้พัฒนาเคอร์เนลให้มานั้นถูกต้อง
Craig Hicks

42

มีเสื้อคลุมรอบ ๆ การเรียกของระบบฝูง (2) ที่เรียกว่าไม่น่าแปลกใจฝูง (1) ทำให้ง่ายต่อการได้รับการล็อคแบบเอกสิทธิ์เฉพาะบุคคลโดยไม่ต้องกังวลเกี่ยวกับการล้างข้อมูล ฯลฯ มีตัวอย่างในman page เกี่ยวกับวิธีการใช้มันในเชลล์สคริปต์


3
การflock()เรียกระบบไม่ใช่ POSIX และไม่สามารถใช้งานกับไฟล์บน NFS mounts
maxschlepzig

17
ทำงานจากงาน Cron ที่ฉันใช้flock -x -n %lock file% -c "%command%"เพื่อให้แน่ใจว่ามีเพียงหนึ่งอินสแตนซ์เท่านั้นที่กำลังทำงาน
Ryall

เอาล่ะแทนที่จะเป็นฝูง (1) พวกมันน่าจะไปกับฝูง (U) .. . มีความคุ้นเคยกับมันบ้าง . ดูเหมือนว่าฉันเคยได้ยินมาก่อนหรือสองครั้ง
Kent Kruckeberg

เป็นที่น่าสังเกตว่าเอกสารประกอบการ flock (2) ระบุใช้เฉพาะกับไฟล์เท่านั้น แต่เอกสารประกอบการ flock (1) ระบุใช้กับไฟล์หรือไดเรกทอรี เอกสารฝูง (1) ไม่ชัดเจนเกี่ยวกับวิธีระบุความแตกต่างระหว่างการสร้าง แต่ฉันคิดว่ามันทำโดยการเพิ่ม "/" สุดท้าย อย่างไรก็ตามหากฝูง (1) สามารถจัดการไดเรกทอรีได้ แต่ไม่สามารถรวมฝูง (2) จากนั้นฝูง (1) จะไม่ถูกนำไปใช้กับฝูง (2) เท่านั้น
Craig Hicks

27

คุณต้องมีการดำเนินการแบบอะตอมมิกเช่นกันสิ่งนี้จะล้มเหลวในที่สุด

แต่จะทำอย่างไรถ้าหากฝูงไม่ว่าง ก็มี mkdir นั่นเป็นปฏิบัติการอะตอมด้วยเช่นกัน กระบวนการเดียวเท่านั้นที่จะส่งผลให้ mkdir ประสบความสำเร็จกระบวนการอื่น ๆ ทั้งหมดจะล้มเหลว

ดังนั้นรหัสคือ:

if mkdir /var/lock/.myscript.exclusivelock
then
  # do stuff
  :
  rmdir /var/lock/.myscript.exclusivelock
fi

คุณต้องดูแลการล็อคเก่า ๆ มิฉะนั้นความผิดพลาดที่สคริปต์ของคุณจะไม่ทำงานอีกครั้ง


1
รันสิ่งนี้สองสามครั้งพร้อมกัน (เช่น "./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ") และสคริปต์จะรั่วไหลไปสองสามครั้ง
Nippysaurus

7
@Nippysaurus: วิธีการล็อคนี้ไม่รั่วไหล สิ่งที่คุณเห็นคือสคริปต์เริ่มต้นสิ้นสุดก่อนที่สำเนาทั้งหมดจะถูกเปิดใช้งานดังนั้นอีกคนหนึ่งที่สามารถรับการล็อคได้อย่างถูกต้อง เพื่อหลีกเลี่ยงข้อผิดพลาดที่เป็นบวกนี้ให้เพิ่มsleep 10ก่อนrmdirแล้วลองเรียงซ้อนอีกครั้ง - จะไม่มีสิ่งใด "รั่วไหล"
เซอร์โทส

แหล่งข้อมูลอื่นอ้างว่า mkdir ไม่ได้เป็น atomic ในบางระบบไฟล์เช่น NFS และ btw ฉันเคยเห็นเหตุการณ์ที่ mkdir แบบเรียกซ้ำพร้อมกันนำไปสู่ข้อผิดพลาดบางครั้งกับงานเมทริกซ์เจนกินส์ ดังนั้นฉันค่อนข้างแน่ใจว่าเป็นอย่างนั้น แต่ mkdir นั้นค่อนข้างดีสำหรับกรณีใช้งาน IMO ที่มีความต้องการน้อย
akostadinov

คุณสามารถใช้ตัวเลือก Bash'es noclobber กับไฟล์ปกติ
Palec

26

เพื่อให้การล็อคมีความน่าเชื่อถือคุณต้องมีการทำงานของอะตอม ข้อเสนอข้างต้นส่วนใหญ่ไม่ได้เป็นแบบอะตอม ยูทิลิตี lockfile (1) ที่เสนอนั้นดูมีแนวโน้มตามที่ man-page กล่าวถึงว่ามันเป็น "NFS-resistant" หากระบบปฏิบัติการของคุณไม่รองรับ lockfile (1) และโซลูชันของคุณต้องทำงานกับ NFS คุณมีตัวเลือกไม่มากนัก ....

NFSv2 มีการดำเนินการแบบสองอะตอม:

  • symlink
  • เปลี่ยนชื่อ

ด้วย NFSv3 การสร้างการโทรยังเป็นแบบอะตอมมิก

การดำเนินงานของไดเรกทอรีไม่ใช่อะตอมมิกภายใต้ NFSv2 และ NFSv3 (โปรดอ้างอิงจากหนังสือ 'NFS Illustrated' โดย Brent Callaghan, ไอ 0-201-32570-5; Brent เป็นทหารผ่านศึกของ NFS ที่ Sun)

เมื่อรู้สิ่งนี้คุณสามารถใช้งานสปินล็อคสำหรับไฟล์และไดเรกทอรี (ในเชลล์ไม่ใช่ PHP):

ล็อค dir ปัจจุบัน:

while ! ln -s . lock; do :; done

ล็อคไฟล์:

while ! ln -s ${f} ${f}.lock; do :; done

ปลดล็อค dir ปัจจุบัน (สมมติว่ากระบวนการที่ทำงานได้รับการล็อคจริง ๆ ):

mv lock deleteme && rm deleteme

ปลดล็อคไฟล์ (สมมติว่ากระบวนการที่ทำงานได้รับการล็อคจริง ๆ ):

mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme

การลบยังไม่ได้เป็นอะตอมมิดังนั้นจึงเปลี่ยนชื่อเป็นครั้งแรก

สำหรับ symlink และเปลี่ยนชื่อการโทรชื่อไฟล์ทั้งสองจะต้องอยู่ในระบบไฟล์เดียวกัน ข้อเสนอของฉัน: ใช้ชื่อไฟล์อย่างง่ายเท่านั้น (ไม่มีเส้นทาง) และวางไฟล์และล็อคไว้ในไดเรกทอรีเดียวกัน


หน้าใดของ NFS Illustrated ที่สนับสนุนคำสั่งที่ mkdir ไม่ได้เป็น atomic over NFS?
maxschlepzig

ขอบคุณสำหรับเทคนิคนี้ การปรับใช้เชลล์ mutex มีอยู่ใน lib lib ใหม่ของฉัน: github.com/Offirmo/offirmo-shell-libดูที่ "mutex" มันใช้lockfileถ้ามีหรือสำรองsymlinkวิธีนี้ถ้าไม่
Offirmo

ดี น่าเสียดายที่วิธีการนี้ไม่มีวิธีในการลบการล็อกเก่าโดยอัตโนมัติ
Richard Hansen

สำหรับการปลดล็อกสองด่าน ( mv, rm) ควรrm -fจะใช้มากกว่าrmในกรณีที่กระบวนการสอง P1, P2 กำลังแข่งอยู่หรือไม่ ตัวอย่างเช่น P1 เริ่มปลดล็อคด้วยmvจากนั้นล็อค P2 จากนั้นปลดล็อค P2 (ทั้งคู่mvและrm) ในที่สุด P1 จะพยายามrmและล้มเหลว
แมตต์วอลลิส

1
@MattWallis ปัญหาสุดท้ายที่สามารถบรรเทาได้อย่างง่ายดายโดยรวม$$ไว้ใน${f}.deletemeชื่อไฟล์
Stefan Majewsky

23

อีกตัวเลือกหนึ่งคือการใช้เปลือกของตัวเลือกโดยการทำงานnoclobber set -Cจากนั้น>จะล้มเหลวหากไฟล์นั้นมีอยู่แล้ว

โดยย่อ:

set -C
lockfile="/tmp/locktest.lock"
if echo "$$" > "$lockfile"; then
    echo "Successfully acquired lock"
    # do work
    rm "$lockfile"    # XXX or via trap - see below
else
    echo "Cannot acquire lock - already locked by $(cat "$lockfile")"
fi

สิ่งนี้ทำให้เชลล์โทร:

open(pathname, O_CREAT|O_EXCL)

ซึ่งสร้างไฟล์หรือล้มเหลวหากไฟล์นั้นมีอยู่แล้ว


ตามความคิดเห็นในBashFAQ 045สิ่งนี้อาจล้มเหลวksh88แต่มันใช้ได้ในทุกเชลล์ของฉัน:

$ strace -e trace=creat,open -f /bin/bash /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/zsh /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/pdksh /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_TRUNC|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/dash /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0666) = 3

สิ่งที่น่าสนใจที่pdkshเพิ่มการO_TRUNCตั้งค่าสถานะ แต่เห็นได้ชัดว่าเป็นการซ้ำซ้อน:
คุณกำลังสร้างไฟล์ว่างเปล่าหรือคุณไม่ได้ทำอะไรเลย


วิธีการที่คุณทำrmขึ้นอยู่กับวิธีที่คุณต้องการจัดการทางออกที่ไม่สะอาด

ลบเมื่อออกสะอาด

การทำงานใหม่ล้มเหลวจนกว่าปัญหาที่ทำให้การออกจากสิ่งไม่สะอาดได้รับการแก้ไขและลบไฟล์ออกด้วยตนเอง

# acquire lock
# do work (code here may call exit, etc.)
rm "$lockfile"

ลบเมื่อออกจากระบบ

การทำงานใหม่สำเร็จแล้วหากสคริปต์ไม่ได้ทำงานอยู่

trap 'rm "$lockfile"' EXIT

วิธีนวนิยายมาก ... นี้ดูเหมือนจะเป็นวิธีหนึ่งในการบรรลุอะตอมมิกซิตี้โดยใช้ไฟล์ล็อคมากกว่าล็อคไดเรกทอรี
Matt Caldwell

วิธีการที่ดี :-) บนแทร็ก EXIT ควร จำกัด กระบวนการที่สามารถล้างไฟล์ล็อคได้ ตัวอย่างเช่น: trap 'if [[$ (cat "$ lockfile") == "$$"]]; จากนั้น rm "$ lockfile"; fi 'EXIT
Kevin Seifert

1
ไฟล์ล็อคไม่ใช่อะตอมมิกผ่าน NFS นั่นเป็นสาเหตุที่ผู้คนย้ายไปใช้ไดเรกทอรีล็อค
K Richard Pixley

20

คุณสามารถใช้GNU Parallelสำหรับการนี้มันทำงานเป็น mutex semเมื่อเรียกว่าเป็น ดังนั้นในแง่ที่เป็นรูปธรรมคุณสามารถใช้:

sem --id SCRIPTSINGLETON yourScript

หากคุณต้องการหมดเวลาด้วยให้ใช้:

sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript

การหมดเวลาของ <0 หมายถึงการออกโดยไม่เรียกใช้สคริปต์หากสัญญาณไม่ได้ถูกปล่อยออกมาภายในระยะหมดเวลาการหมดเวลาของ> 0 หมายถึงเรียกใช้สคริปต์ต่อไป

โปรดทราบว่าคุณควรให้ชื่อ (พร้อม--id) เป็นค่าเริ่มต้นให้กับสถานีควบคุม

GNU Parallel เป็นการติดตั้งที่ง่ายมากบนแพลตฟอร์ม Linux / OSX / Unix ส่วนใหญ่ - มันเป็นเพียงสคริปต์ Perl


คนที่แย่เกินไปก็ลังเลที่จะตอบโต้กับคำตอบที่ไร้ประโยชน์: สิ่งนี้นำไปสู่คำตอบที่เกี่ยวข้องใหม่ที่ถูกฝังอยู่ในกองขยะ
Dmitry Grigoryev

4
เราต้องการ upvotes มากมาย นี่เป็นคำตอบที่เป็นระเบียบและเป็นที่รู้จักน้อยมาก (แม้ว่าจะเป็น OP อวดความรู้ที่ต้องการได้อย่างรวดเร็วและสกปรกในขณะนี้มีความรวดเร็วและสะอาด!) เพิ่มเติมเกี่ยวกับsemที่คำถามที่เกี่ยวข้องunix.stackexchange.com/a/322200/199525
เมฆบางส่วน

16

สำหรับเชลล์สคริปท์ฉันมักจะทำmkdirมากกว่าflockเพราะมันทำให้ตัวล็อคพกพาได้มากกว่า

ไม่ว่าจะด้วยวิธีใดการใช้set -eไม่เพียงพอ ซึ่งจะออกจากสคริปต์เท่านั้นหากคำสั่งใด ๆ ล้มเหลว ล็อคของคุณจะยังคงถูกทิ้งไว้ข้างหลัง

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

#=======================================================================
# Predefined Global Variables
#=======================================================================

TMPDIR=/tmp/myapp
[[ ! -d $TMP_DIR ]] \
    && mkdir -p $TMP_DIR \
    && chmod 700 $TMPDIR

LOCK_DIR=$TMP_DIR/lock

#=======================================================================
# Functions
#=======================================================================

function mklock {
    __lockdir="$LOCK_DIR/$(date +%s.%N).$$" # Private Global. Use Epoch.Nano.PID

    # If it can create $LOCK_DIR then no other instance is running
    if $(mkdir $LOCK_DIR)
    then
        mkdir $__lockdir  # create this instance's specific lock in queue
        LOCK_EXISTS=true  # Global
    else
        echo "FATAL: Lock already exists. Another copy is running or manually lock clean up required."
        exit 1001  # Or work out some sleep_while_execution_lock elsewhere
    fi
}

function rmlock {
    [[ ! -d $__lockdir ]] \
        && echo "WARNING: Lock is missing. $__lockdir does not exist" \
        || rmdir $__lockdir
}

#-----------------------------------------------------------------------
# Private Signal Traps Functions {{{2
#
# DANGER: SIGKILL cannot be trapped. So, try not to `kill -9 PID` or 
#         there will be *NO CLEAN UP*. You'll have to manually remove 
#         any locks in place.
#-----------------------------------------------------------------------
function __sig_exit {

    # Place your clean up logic here 

    # Remove the LOCK
    [[ -n $LOCK_EXISTS ]] && rmlock
}

function __sig_int {
    echo "WARNING: SIGINT caught"    
    exit 1002
}

function __sig_quit {
    echo "SIGQUIT caught"
    exit 1003
}

function __sig_term {
    echo "WARNING: SIGTERM caught"    
    exit 1015
}

#=======================================================================
# Main
#=======================================================================

# Set TRAPs
trap __sig_exit EXIT    # SIGEXIT
trap __sig_int INT      # SIGINT
trap __sig_quit QUIT    # SIGQUIT
trap __sig_term TERM    # SIGTERM

mklock

# CODE

exit # No need for cleanup code here being in the __sig_exit trap function

นี่คือสิ่งที่จะเกิดขึ้น กับดักทั้งหมดจะสร้างทางออกเพื่อให้ฟังก์ชั่น__sig_exitจะเกิดขึ้นเสมอ (ยกเว้น SIGKILL) ซึ่งทำความสะอาดล็อคของคุณ

หมายเหตุ: ค่าออกของฉันไม่ใช่ค่าต่ำ ทำไม? ระบบประมวลผลแบบแบตช์หลายแบบสร้างหรือมีความคาดหวังของตัวเลข 0 ถึง 31 การตั้งค่าให้เป็นอย่างอื่นฉันสามารถให้สคริปต์และกระแสข้อมูลแบตช์ของฉันตอบสนองตามงานแบ็ตช์หรือสคริปต์ชุดก่อนหน้า


2
สคริปต์ของคุณละเอียดเกินไปฉันคิดว่าสั้นกว่านี้เยอะ แต่โดยรวมแล้วใช่คุณต้องตั้งค่ากับดักเพื่อที่จะทำสิ่งนี้ให้ถูกต้อง นอกจากนี้ฉันจะเพิ่ม SIGHUP
mojuba

สิ่งนี้ทำงานได้ดียกเว้นว่าจะตรวจสอบ $ LOCK_DIR ในขณะที่ลบ $ __ lockdir บางทีฉันควรแนะนำเมื่อลบล็อคคุณจะทำ rm -r $ LOCK_DIR?
bevada

ขอบคุณสำหรับคำแนะนำ รหัสข้างต้นถูกยกขึ้นและวางไว้ในรูปแบบรหัส psuedo ดังนั้นมันจะต้องมีการปรับแต่งตามการใช้งานของผู้คน อย่างไรก็ตามฉันจงใจไปกับ rmdir ในกรณีของฉันเนื่องจาก rmdir ลบไดเรกทอรีอย่างปลอดภัยเท่านั้นถ้าว่างเปล่า ถ้าคนกำลังวางทรัพยากรในพวกเขาเช่นไฟล์ PID ฯลฯ พวกเขาควรเปลี่ยนการล้างข้อมูลบนล็อคของพวกเขาให้ก้าวร้าวมากขึ้นrm -r $LOCK_DIRหรือแม้กระทั่งบังคับตามที่จำเป็น ไชโย
Mark Stinson

คุณผ่านการทดสอบแล้วexit 1002หรือยัง
Gilles Quenot

13

จริงๆอย่างรวดเร็วและจริงๆสกปรก? หนึ่งซับในที่ด้านบนของสคริปต์ของคุณจะทำงาน:

[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit

แน่นอนให้แน่ใจว่าชื่อสคริปต์ของคุณไม่ซ้ำกัน :)


ฉันจะจำลองสิ่งนี้เพื่อทดสอบได้อย่างไร มีวิธีการเริ่มสคริปต์สองครั้งในหนึ่งบรรทัดและอาจได้รับคำเตือนถ้ามันทำงานอยู่แล้ว?
rubo77

2
มันไม่ทำงานเลย! ทำไมการตรวจสอบ-gt 2? grep ไม่เคยพบตัวเองในผลของ ps!
rubo77

pgrepไม่ได้อยู่ใน POSIX หากคุณต้องการให้พอร์ตนี้ใช้งานได้คุณต้องมี POSIX psและประมวลผลเอาต์พุต
Palec

ใน OSX ไม่ได้อยู่ที่คุณจะต้องใช้-c | wc -lเกี่ยวกับการเปรียบเทียบตัวเลข: -gt 1มีการตรวจสอบตั้งแต่อินสแตนซ์แรกที่เห็นตัวเอง
Benjamin Peter

6

ต่อไปนี้เป็นวิธีการที่รวมการล็อกไดเรกทอรีอะตอมกับการตรวจสอบการล็อกเก่าผ่าน PID และเริ่มต้นใหม่หากเก่า นอกจากนี้สิ่งนี้ไม่ได้พึ่งพาการทุบตีใด ๆ

#!/bin/dash

SCRIPTNAME=$(basename $0)
LOCKDIR="/var/lock/${SCRIPTNAME}"
PIDFILE="${LOCKDIR}/pid"

if ! mkdir $LOCKDIR 2>/dev/null
then
    # lock failed, but check for stale one by checking if the PID is really existing
    PID=$(cat $PIDFILE)
    if ! kill -0 $PID 2>/dev/null
    then
       echo "Removing stale lock of nonexistent PID ${PID}" >&2
       rm -rf $LOCKDIR
       echo "Restarting myself (${SCRIPTNAME})" >&2
       exec "$0" "$@"
    fi
    echo "$SCRIPTNAME is already running, bailing out" >&2
    exit 1
else
    # lock successfully acquired, save PID
    echo $$ > $PIDFILE
fi

trap "rm -rf ${LOCKDIR}" QUIT INT TERM EXIT


echo hello

sleep 30s

echo bye

5

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


5

ตัวอย่างนี้อธิบายไว้ในฝูงคน แต่มันต้องการความต้องการบางอย่างเพราะเราควรจัดการข้อบกพร่องและรหัสทางออก:

   #!/bin/bash
   #set -e this is useful only for very stupid scripts because script fails when anything command exits with status more than 0 !! without possibility for capture exit codes. not all commands exits >0 are failed.

( #start subprocess
  # Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
  flock -x -w 10 200
  if [ "$?" != "0" ]; then echo Cannot lock!; exit 1; fi
  echo $$>>/var/lock/.myscript.exclusivelock #for backward lockdir compatibility, notice this command is executed AFTER command bottom  ) 200>/var/lock/.myscript.exclusivelock.
  # Do stuff
  # you can properly manage exit codes with multiple command and process algorithm.
  # I suggest throw this all to external procedure than can properly handle exit X commands

) 200>/var/lock/.myscript.exclusivelock   #exit subprocess

FLOCKEXIT=$?  #save exitcode status
    #do some finish commands

exit $FLOCKEXIT   #return properly exitcode, may be usefull inside external scripts

คุณสามารถใช้วิธีอื่นรายการกระบวนการที่ฉันใช้ในอดีต แต่วิธีนี้มีความซับซ้อนมากกว่า คุณควรแสดงรายการกระบวนการโดย ps, กรองตามชื่อ, ตัวกรองเพิ่มเติม grep -v grep สำหรับการลบปรสิต nad ในที่สุดก็นับโดย grep -c และเปรียบเทียบกับจำนวน มันซับซ้อนและไม่แน่นอน


1
คุณสามารถใช้ ln -s เนื่องจากสิ่งนี้สามารถสร้าง symlink ได้เฉพาะเมื่อไม่มีไฟล์หรือ symlink อยู่เหมือนกับ mkdir กระบวนการของระบบจำนวนมากใช้ symlink ในอดีตเช่น init หรือ inetd synlink เก็บ ID กระบวนการ แต่ชี้ไปที่ไม่มีอะไรจริงๆ สำหรับปีพฤติกรรมนี้เปลี่ยนไป กระบวนการใช้ฝูงและเซมาฟอร์
Znik

5

คำตอบที่มีอยู่โพสต์นั้นขึ้นอยู่กับยูทิลิตี้ CLI flockหรือไม่ปลอดภัยไฟล์ล็อค ยูทิลิตี flock นั้นใช้งานไม่ได้กับทุกระบบที่ไม่ใช่ Linux (เช่น FreeBSD) และใช้งานไม่ได้กับ NFS

ในวันแรก ๆ ของการดูแลระบบและการพัฒนาระบบฉันบอกว่าวิธีที่ปลอดภัยและพกพาได้ในการสร้างไฟล์ล็อคคือการสร้างไฟล์ temp โดยใช้mkemp(3)หรือmkemp(1)เขียนข้อมูลที่ระบุไปยังไฟล์ temp (เช่น PID) จากนั้นฮาร์ดลิงก์ ไฟล์ temp ไปยังไฟล์ล็อค หากลิงก์สำเร็จแสดงว่าคุณได้รับการล็อคสำเร็จ

เมื่อใช้การล็อกในเชลล์สคริปต์โดยปกติแล้วฉันวางobtain_lock()ฟังก์ชันในโปรไฟล์ที่ใช้ร่วมกัน ด้านล่างเป็นตัวอย่างของฟังก์ชั่นล็อคของฉัน:

obtain_lock()
{
  LOCK="${1}"
  LOCKDIR="$(dirname "${LOCK}")"
  LOCKFILE="$(basename "${LOCK}")"

  # create temp lock file
  TMPLOCK=$(mktemp -p "${LOCKDIR}" "${LOCKFILE}XXXXXX" 2> /dev/null)
  if test "x${TMPLOCK}" == "x";then
     echo "unable to create temporary file with mktemp" 1>&2
     return 1
  fi
  echo "$$" > "${TMPLOCK}"

  # attempt to obtain lock file
  ln "${TMPLOCK}" "${LOCK}" 2> /dev/null
  if test $? -ne 0;then
     rm -f "${TMPLOCK}"
     echo "unable to obtain lockfile" 1>&2
     if test -f "${LOCK}";then
        echo "current lock information held by: $(cat "${LOCK}")" 1>&2
     fi
     return 2
  fi
  rm -f "${TMPLOCK}"

  return 0;
};

ต่อไปนี้เป็นตัวอย่างของวิธีการใช้ฟังก์ชั่นล็อค:

#!/bin/sh

. /path/to/locking/profile.sh
PROG_LOCKFILE="/tmp/myprog.lock"

clean_up()
{
  rm -f "${PROG_LOCKFILE}"
}

obtain_lock "${PROG_LOCKFILE}"
if test $? -ne 0;then
   exit 1
fi
trap clean_up SIGHUP SIGINT SIGTERM

# bulk of script

clean_up
exit 0
# end of script

อย่าลืมโทรหาclean_upที่จุดออกใด ๆ ในสคริปต์ของคุณ

ฉันใช้ข้างต้นทั้งใน Linux และ FreeBSD


4

เมื่อกำหนดเป้าหมายเครื่อง Debian ฉันพบว่าlockfile-progsแพ็คเกจเป็นทางออกที่ดี procmailยังมาพร้อมกับlockfileเครื่องมือ อย่างไรก็ตามบางครั้งฉันก็ติดอยู่กับสิ่งเหล่านี้

นี่คือทางออกของฉันซึ่งใช้mkdirสำหรับ atomic-ness และไฟล์ PID เพื่อตรวจจับการล็อกเก่า รหัสนี้กำลังใช้งานในการตั้งค่า Cygwin และทำงานได้ดี

หากต้องการใช้งานก็เพียงโทรหาexclusive_lock_requireเมื่อคุณต้องการเข้าถึงสิ่งที่พิเศษ พารามิเตอร์ชื่อตัวเลือกล็อคช่วยให้คุณแบ่งปันการล็อคระหว่างสคริปต์ที่แตกต่างกัน นอกจากนี้ยังมีฟังก์ชั่นระดับล่างสอง ( exclusive_lock_tryและexclusive_lock_retry) หากคุณต้องการบางสิ่งที่ซับซ้อนมากขึ้น

function exclusive_lock_try() # [lockname]
{

    local LOCK_NAME="${1:-`basename $0`}"

    LOCK_DIR="/tmp/.${LOCK_NAME}.lock"
    local LOCK_PID_FILE="${LOCK_DIR}/${LOCK_NAME}.pid"

    if [ -e "$LOCK_DIR" ]
    then
        local LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"
        if [ ! -z "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2> /dev/null
        then
            # locked by non-dead process
            echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"
            return 1
        else
            # orphaned lock, take it over
            ( echo $$ > "$LOCK_PID_FILE" ) 2> /dev/null && local LOCK_PID="$$"
        fi
    fi
    if [ "`trap -p EXIT`" != "" ]
    then
        # already have an EXIT trap
        echo "Cannot get lock, already have an EXIT trap"
        return 1
    fi
    if [ "$LOCK_PID" != "$$" ] &&
        ! ( umask 077 && mkdir "$LOCK_DIR" && umask 177 && echo $$ > "$LOCK_PID_FILE" ) 2> /dev/null
    then
        local LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"
        # unable to acquire lock, new process got in first
        echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"
        return 1
    fi
    trap "/bin/rm -rf \"$LOCK_DIR\"; exit;" EXIT

    return 0 # got lock

}

function exclusive_lock_retry() # [lockname] [retries] [delay]
{

    local LOCK_NAME="$1"
    local MAX_TRIES="${2:-5}"
    local DELAY="${3:-2}"

    local TRIES=0
    local LOCK_RETVAL

    while [ "$TRIES" -lt "$MAX_TRIES" ]
    do

        if [ "$TRIES" -gt 0 ]
        then
            sleep "$DELAY"
        fi
        local TRIES=$(( $TRIES + 1 ))

        if [ "$TRIES" -lt "$MAX_TRIES" ]
        then
            exclusive_lock_try "$LOCK_NAME" > /dev/null
        else
            exclusive_lock_try "$LOCK_NAME"
        fi
        LOCK_RETVAL="${PIPESTATUS[0]}"

        if [ "$LOCK_RETVAL" -eq 0 ]
        then
            return 0
        fi

    done

    return "$LOCK_RETVAL"

}

function exclusive_lock_require() # [lockname] [retries] [delay]
{
    if ! exclusive_lock_retry "$@"
    then
        exit 1
    fi
}

ขอบคุณลองใช้กับ cygwin ด้วยตัวเองและผ่านการทดสอบอย่างง่าย
ndemou

4

หากข้อ จำกัด ของฝูงซึ่งได้รับการอธิบายไว้แล้วที่อื่นในหัวข้อนี้ไม่เป็นปัญหาสำหรับคุณแล้วสิ่งนี้ควรใช้งานได้:

#!/bin/bash

{
    # exit if we are unable to obtain a lock; this would happen if 
    # the script is already running elsewhere
    # note: -x (exclusive) is the default
    flock -n 100 || exit

    # put commands to run here
    sleep 100
} 100>/tmp/myjob.lock 

3
แค่คิดว่าฉันจะชี้ให้เห็นว่า -x (ล็อคการเขียน) ตั้งไว้แล้วโดยค่าเริ่มต้น
Keldon Alleyne

และ-nจะexit 1ดำเนินการทันทีหากไม่สามารถล็อคได้
Anentropic

ขอบคุณ @KeldonAlleyne ฉันอัปเดตรหัสเพื่อลบ "-x" เนื่องจากเป็นค่าเริ่มต้น
presto8

3

UNIXes บางคนมีซึ่งจะคล้ายกับที่กล่าวมาแล้วlockfileflock

จาก manpage:

lockfile สามารถใช้เพื่อสร้างไฟล์เซมาฟอร์อย่างน้อยหนึ่งไฟล์ หากไฟล์ lock- ไม่สามารถสร้างไฟล์ที่ระบุทั้งหมด (ตามลำดับที่ระบุ) มันจะรอ sleeptime (ค่าเริ่มต้นคือ 8) วินาทีและลองไฟล์สุดท้ายที่ไม่สำเร็จ คุณสามารถระบุจำนวนครั้งในการพยายามทำจนกว่าจะมีการส่งคืนความล้มเหลว หากจำนวนครั้งในการลองใหม่เป็น -1 (ค่าเริ่มต้นคือ -r-1) lockfile จะลองใหม่ตลอดไป


เราจะรับlockfileประโยชน์ได้อย่างไร?
Offirmo

lockfileprocmailมีการกระจายกับ นอกจากนี้ยังมีทางเลือกdotlockfileที่จะไปกับliblockfileแพคเกจ พวกเขาทั้งสองอ้างว่าทำงานได้อย่างน่าเชื่อถือบน NFS
นายมรณะ

3

จริงๆแล้วแม้ว่าคำตอบของ bmdhacks นั้นเกือบจะดี แต่มีโอกาสเล็กน้อยที่สคริปต์ตัวที่สองที่จะรันหลังจากตรวจสอบ lockfile เป็นครั้งแรกและก่อนที่มันจะเขียนมัน ดังนั้นพวกเขาทั้งสองจะเขียนไฟล์ล็อคและทั้งคู่จะทำงาน นี่คือวิธีทำให้มันใช้งานได้อย่างแน่นอน:

lockfile=/var/lock/myscript.lock

if ( set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null ; then
  trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
else
  # or you can decide to skip the "else" part if you want
  echo "Another instance is already running!"
fi

noclobberตัวเลือกที่จะให้แน่ใจว่าคำสั่งเปลี่ยนเส้นทางจะล้มเหลวหากไฟล์ที่มีอยู่แล้ว ดังนั้นคำสั่ง redirect จึงเป็น atomic จริง ๆ - คุณเขียนและตรวจสอบไฟล์ด้วยคำสั่งเดียว คุณไม่จำเป็นต้องลบ lockfile ในตอนท้ายของไฟล์ - มันจะถูกลบโดย Trap ฉันหวังว่านี่จะช่วยให้คนที่จะอ่านในภายหลัง

PS ฉันไม่เห็นว่า Mikel ตอบคำถามอย่างถูกต้องแล้วแม้ว่าเขาจะไม่ได้รวมคำสั่ง trap เพื่อลดโอกาสที่ไฟล์ล็อคจะถูกทิ้งไว้หลังจากหยุดสคริปต์ด้วย Ctrl-C เป็นต้น นี่คือทางออกที่สมบูรณ์


3

ฉันอยากจะทำอะไรกับ lockfiles, lockdirs, โปรแกรมล็อคพิเศษและแม้ว่าpidofมันจะไม่พบในการติดตั้ง Linux ทั้งหมด และต้องการให้มีโค้ดที่ง่ายที่สุดเท่าที่จะเป็นไปได้ (หรืออย่างน้อยที่สุดสองสามบรรทัด) ifคำสั่งที่ง่ายที่สุดในหนึ่งบรรทัด:

if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi

1
สิ่งนี้มีความอ่อนไหวต่อเอาต์พุต 'ps' บนเครื่องของฉัน (Ubuntu 14.04, / bin / ps จากรุ่น procps-ng 3.3.9) คำสั่ง 'ps axf' พิมพ์ตัวอักษรต้นไม้ ASCII ซึ่งทำลายหมายเลขฟิลด์ สิ่งนี้ใช้ได้ผลกับฉัน: /bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
qneill

2

ฉันใช้วิธีการง่ายๆที่จัดการไฟล์ล็อคเก่า ๆ

โปรดทราบว่าโซลูชันข้างต้นบางอย่างที่เก็บ pid นั้นไม่สนใจข้อเท็จจริงที่ว่า pid สามารถล้อมรอบได้ ดังนั้น - เพียงแค่ตรวจสอบว่ามีกระบวนการที่ถูกต้องกับ pid ที่จัดเก็บไม่เพียงพอโดยเฉพาะอย่างยิ่งสำหรับสคริปต์ที่ใช้งานนาน

ฉันใช้ noclobber เพื่อให้แน่ใจว่ามีสคริปต์เดียวเท่านั้นที่สามารถเปิดและเขียนไปยังไฟล์ล็อคได้ในครั้งเดียว นอกจากนี้ฉันเก็บข้อมูลเพียงพอที่จะระบุกระบวนการเฉพาะใน lockfile ฉันกำหนดชุดข้อมูลเพื่อระบุกระบวนการที่ไม่ซ้ำกันว่าเป็น pid, ppid, lstart

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

ควรทำงานกับหลายเชลล์ในหลายแพลตฟอร์ม รวดเร็วพกพาง่าย

#!/usr/bin/env sh
# Author: rouble

LOCKFILE=/var/tmp/lockfile #customize this line

trap release INT TERM EXIT

# Creates a lockfile. Sets global variable $ACQUIRED to true on success.
# 
# Returns 0 if it is successfully able to create lockfile.
acquire () {
    set -C #Shell noclobber option. If file exists, > will fail.
    UUID=`ps -eo pid,ppid,lstart $$ | tail -1`
    if (echo "$UUID" > "$LOCKFILE") 2>/dev/null; then
        ACQUIRED="TRUE"
        return 0
    else
        if [ -e $LOCKFILE ]; then 
            # We may be dealing with a stale lock file.
            # Bring out the magnifying glass. 
            CURRENT_UUID_FROM_LOCKFILE=`cat $LOCKFILE`
            CURRENT_PID_FROM_LOCKFILE=`cat $LOCKFILE | cut -f 1 -d " "`
            CURRENT_UUID_FROM_PS=`ps -eo pid,ppid,lstart $CURRENT_PID_FROM_LOCKFILE | tail -1`
            if [ "$CURRENT_UUID_FROM_LOCKFILE" == "$CURRENT_UUID_FROM_PS" ]; then 
                echo "Script already running with following identification: $CURRENT_UUID_FROM_LOCKFILE" >&2
                return 1
            else
                # The process that created this lock file died an ungraceful death. 
                # Take ownership of the lock file.
                echo "The process $CURRENT_UUID_FROM_LOCKFILE is no longer around. Taking ownership of $LOCKFILE"
                release "FORCE"
                if (echo "$UUID" > "$LOCKFILE") 2>/dev/null; then
                    ACQUIRED="TRUE"
                    return 0
                else
                    echo "Cannot write to $LOCKFILE. Error." >&2
                    return 1
                fi
            fi
        else
            echo "Do you have write permissons to $LOCKFILE ?" >&2
            return 1
        fi
    fi
}

# Removes the lock file only if this script created it ($ACQUIRED is set), 
# OR, if we are removing a stale lock file (first parameter is "FORCE") 
release () {
    #Destroy lock file. Take no prisoners.
    if [ "$ACQUIRED" ] || [ "$1" == "FORCE" ]; then
        rm -f $LOCKFILE
    fi
}

# Test code
# int main( int argc, const char* argv[] )
echo "Acquring lock."
acquire
if [ $? -eq 0 ]; then 
    echo "Acquired lock."
    read -p "Press [Enter] key to release lock..."
    release
    echo "Released lock."
else
    echo "Unable to acquire lock."
fi

ฉันให้ +1 โซลูชันที่แตกต่างให้คุณ แม้ว่ามันจะไม่ทำงานทั้งใน AIX (> ps -eo pid, ppid, lstart $$ | tail -1 ps: รายการที่ไม่ถูกต้องด้วย -o.) ไม่ใช่ HP-UX (> ps -eo pid, ppid, lstart $$ | tail -1 ps: ตัวเลือกที่ผิดกฎหมาย - o) ขอบคุณ
Tagar

2

เพิ่มบรรทัดนี้ที่จุดเริ่มต้นของสคริปต์ของคุณ

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

มันเป็นรหัสสำเร็จรูปจากฝูงคน

หากคุณต้องการการบันทึกเพิ่มเติมให้ใช้อันนี้

[ "${FLOCKER}" != "$0" ] && { echo "Trying to start build from queue... "; exec bash -c "FLOCKER='$0' flock -E $E_LOCKED -en '$0' '$0' '$@' || if [ \"\$?\" -eq $E_LOCKED ]; then echo 'Locked.'; fi"; } || echo "Lock is free. Completing."

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

ดูเหมือนว่าจะไม่ทำงานบน Debian 7 แต่ดูเหมือนว่าจะกลับมาทำงานอีกครั้งด้วยแพ็คเกจ util-linux 2.25 รุ่นทดลอง มันเขียน "flock: ... ไฟล์ข้อความไม่ว่าง" อาจถูกเขียนทับโดยการปิดใช้งานสิทธิ์การเขียนในสคริปต์ของคุณ


1

PID และ lockfiles น่าเชื่อถือที่สุดแน่นอน เมื่อคุณพยายามรันโปรแกรมมันสามารถตรวจสอบ lockfile ซึ่งและถ้ามันมีอยู่มันสามารถใช้psเพื่อดูว่ากระบวนการยังคงทำงานอยู่หรือไม่ หากไม่เป็นเช่นนั้นสคริปต์จะสามารถเริ่มต้นอัปเดต PID ใน lockfile ให้เป็นของตนเอง


1

ฉันพบว่าโซลูชันของ bmdhack เป็นวิธีที่ใช้งานได้จริงอย่างน้อยที่สุดสำหรับกรณีการใช้งานของฉัน การใช้ flock และ lockfile ขึ้นอยู่กับการลบ lockfile โดยใช้ rm เมื่อสคริปต์สิ้นสุดซึ่งไม่สามารถรับประกันได้เสมอ (เช่น kill -9)

ฉันจะเปลี่ยนสิ่งเล็กน้อยเกี่ยวกับวิธีการแก้ปัญหาของ bmdhack: มันทำให้การลบไฟล์ล็อคโดยไม่ระบุว่าสิ่งนี้ไม่จำเป็นสำหรับการทำงานที่ปลอดภัยของเซมาฟอร์นี้ การใช้ kill -0 ทำให้เขามั่นใจว่า lockfile แบบเก่าสำหรับกระบวนการที่ไม่ทำงานจะถูกละเว้น / เขียนทับมากเกินไป

โซลูชันที่เรียบง่ายของฉันคือการเพิ่มสิ่งต่อไปนี้ที่ด้านบนของซิงเกิลของคุณ:

## Test the lock
LOCKFILE=/tmp/singleton.lock 
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
    echo "Script already running. bye!"
    exit 
fi

## Set the lock 
echo $$ > ${LOCKFILE}

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


1

semaphoricใช้ยูทิลิตี้flock(ตามที่กล่าวข้างต้นเช่นโดย presto8) ที่จะใช้สัญญาณนับ มันเปิดใช้งานจำนวนกระบวนการที่เกิดขึ้นพร้อมกันที่คุณต้องการโดยเฉพาะ เราใช้เพื่อ จำกัด ระดับการทำงานพร้อมกันของกระบวนการผู้ปฏิบัติงานคิวต่างๆ

มันเหมือนsemแต่น้ำหนักเบากว่ามาก (การเปิดเผยอย่างเต็มรูปแบบ: ฉันเขียนหลังจากค้นหาเซมาแล้วมันหนักเกินไปสำหรับความต้องการของเราและไม่มียูทิลิตี้เซมาฟอร์นับง่าย ๆ ที่มีให้)


1

ตัวอย่างที่มีฝูง (1) แต่ไม่มีเชลล์ย่อย flock () ed file / tmp / foo ไม่เคยถูกลบ แต่นั่นไม่สำคัญว่าจะได้รับ flock () และ un-flock () ed

#!/bin/bash

exec 9<> /tmp/foo
flock -n 9
RET=$?
if [[ $RET -ne 0 ]] ; then
    echo "lock failed, exiting"
    exit
fi

#Now we are inside the "critical section"
echo "inside lock"
sleep 5
exec 9>&- #close fd 9, and release lock

#The part below is outside the critical section (the lock)
echo "lock released"
sleep 5

1

ตอบล้านครั้งแล้ว แต่อีกวิธีหนึ่งโดยไม่จำเป็นต้องพึ่งพาภายนอก:

LOCK_FILE="/var/lock/$(basename "$0").pid"
trap "rm -f ${LOCK_FILE}; exit" INT TERM EXIT
if [[ -f $LOCK_FILE && -d /proc/`cat $LOCK_FILE` ]]; then
   // Process already exists
   exit 1
fi
echo $$ > $LOCK_FILE

แต่ละครั้งที่มันเขียน PID ปัจจุบัน ($$) ลงใน lockfile และในการเริ่มต้นสคริปต์จะตรวจสอบว่ากระบวนการกำลังทำงานด้วย PID ล่าสุดหรือไม่


1
หากไม่มีการเรียก Trap (หรืออย่างน้อยการล้างข้อมูลใกล้ถึงจุดสิ้นสุดสำหรับกรณีปกติ) คุณจะพบข้อผิดพลาดที่เป็นบวกซึ่ง lockfile ถูกทิ้งไว้หลังจากการทำงานครั้งสุดท้ายและ PID ถูกนำกลับมาใช้โดยกระบวนการอื่นในภายหลัง (และในกรณีที่เลวร้ายที่สุดมันถูกมอบให้แก่กระบวนการที่ใช้เวลานานอย่างอาปาเช่ .... )
Philippe Chaintreuil

1
ฉันเห็นด้วยวิธีการของฉันมีข้อบกพร่องมันไม่จำเป็นต้องมีกับดัก ฉันอัพเดตโซลูชันแล้ว ฉันยังคงต้องการที่จะไม่พึ่งพาภายนอก
Filidor Wiese

1

การใช้การล็อคของกระบวนการนั้นแข็งแกร่งกว่ามากและดูแลทางออกที่ไม่น่าดูด้วย lock_file ถูกเปิดไว้ตราบใดที่กระบวนการทำงาน มันจะถูกปิด (โดยเชลล์) เมื่อกระบวนการมีอยู่ (แม้ว่าจะถูกฆ่า) ฉันพบว่าสิ่งนี้มีประสิทธิภาพมาก:

lock_file=/tmp/`basename $0`.lock

if fuser $lock_file > /dev/null 2>&1; then
    echo "WARNING: Other instance of $(basename $0) running."
    exit 1
fi
exec 3> $lock_file 

1

ฉันใช้ oneliner @ จุดเริ่มต้นของสคริปต์มาก:

#!/bin/bash

if [[ $(pgrep -afc "$(basename "$0")") -gt "1" ]]; then echo "Another instance of "$0" has already been started!" && exit; fi
.
the_beginning_of_actual_script

เป็นการดีที่ได้เห็นการมีอยู่ของกระบวนการในหน่วยความจำ (ไม่ว่าสถานะของกระบวนการจะเป็นอย่างไร) แต่มันทำงานได้สำหรับฉัน


0

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


0

รวดเร็วและสกปรก?

#!/bin/sh

if [ -f sometempfile ]
  echo "Already running... will now terminate."
  exit
else
  touch sometempfile
fi

..do what you want here..

rm sometempfile

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

3
C News ซึ่งสอนฉันเกี่ยวกับการเขียนสคริปต์เชลล์แบบพกพาใช้เพื่อสร้างการล็อกไฟล์ $$ จากนั้นพยายามเชื่อมโยงกับ "ล็อค" - หากลิงก์สำเร็จคุณมีล็อคมิฉะนั้นคุณจะลบล็อค $$ และออกจาก
พอลทอมบลิน

เป็นวิธีที่ดีมากในการทำยกเว้นคุณยังคงต้องถอด lockfile ด้วยตนเองหากมีสิ่งผิดปกติและไม่ได้ลบ lockfile
Matthew Scharley

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