วิธีที่รวดเร็วและสกปรกคืออะไรเพื่อให้แน่ใจว่ามีเพียงหนึ่งอินสแตนซ์ของเชลล์สคริปต์เท่านั้นที่ทำงานในเวลาที่กำหนด
วิธีที่รวดเร็วและสกปรกคืออะไรเพื่อให้แน่ใจว่ามีเพียงหนึ่งอินสแตนซ์ของเชลล์สคริปต์เท่านั้นที่ทำงานในเวลาที่กำหนด
คำตอบ:
นี่คือการใช้งานที่ใช้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
)
ใช้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 อาจเป็นหรือไม่สามารถใช้งานได้
( command A ) command B
เรียก subshell command A
สำหรับ เอกสารที่tldp.org/LDP/abs/html/subshells.html ฉันยังไม่แน่ใจเกี่ยวกับช่วงเวลาของการเรียกร้องของ subshell และสั่ง B.
if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fi
ดังนั้นหากการหมดเวลาเกิดขึ้น (กระบวนการอื่นมีไฟล์ถูกล็อค) สคริปต์นี้จะไม่ไปข้างหน้าและแก้ไขไฟล์ อาจเป็น ... อาร์กิวเมนต์โต้แย้งคือ 'แต่ถ้าใช้เวลา 10 วินาทีและการล็อกยังไม่พร้อมใช้งานมันจะไม่สามารถใช้งานได้' สันนิษฐานว่าเป็นเพราะกระบวนการในการล็อคไม่ได้สิ้นสุดลง (อาจจะมีการเรียกใช้ ภายใต้ดีบักเกอร์?)
exit
(
)
เมื่อกระบวนการย่อยสิ้นสุดลงการล็อกจะถูกนำออกโดยอัตโนมัติเนื่องจากไม่มีกระบวนการที่จะทำการล็อค
วิธีการทั้งหมดที่ทดสอบการมีอยู่ของ "ล็อกไฟล์" นั้นมีข้อบกพร่อง
ทำไม? เนื่องจากไม่มีวิธีการตรวจสอบว่ามีไฟล์อยู่และสร้างในการกระทำแบบอะตอมมิกเดียวหรือไม่ ด้วยเหตุนี้ มีเงื่อนไขการแข่งขันที่จะทำให้ความพยายามของคุณในการแยกการยกเว้นซึ่งกันและกัน
คุณต้องใช้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)
if ! mkdir
ส่วนกับการตรวจสอบว่ากระบวนการกับ PID ที่เก็บไว้ (เมื่อเริ่มต้นที่สำเร็จ) ภายใน lockdir ทำงานจริง ๆและเหมือนกับสคริปต์สำหรับการป้องกัน stalenes สิ่งนี้จะช่วยป้องกันการนำ PID กลับมาใช้ใหม่หลังจากรีบูตและไม่จำเป็นต้องใช้fuser
อีกด้วย
mkdir
ไม่ได้นิยามว่าเป็นการดำเนินการแบบอะตอมมิกและ "ผลข้างเคียง" เป็นรายละเอียดการนำไปใช้งานของระบบไฟล์ ฉันเชื่อว่าเขาอย่างสมบูรณ์ถ้าเขาบอกว่า NFS ไม่ได้นำไปใช้ในแบบอะตอมมิก แม้ว่าฉันไม่สงสัยว่าคุณ/tmp
จะแบ่งปัน NFS และมีแนวโน้มที่จะได้รับจาก fs ที่ดำเนินการแบบmkdir
อะตอม
ln
เพื่อสร้างฮาร์ดลิงก์จากไฟล์อื่น หากคุณมีระบบไฟล์แปลก ๆ ซึ่งไม่รับประกันว่าคุณจะสามารถตรวจสอบไอโหนดของไฟล์ใหม่หลังจากนั้นเพื่อดูว่ามันเป็นเหมือนกับไฟล์ต้นฉบับหรือไม่
open(... O_CREAT|O_EXCL)
มัน คุณเพียงต้องการโปรแกรมผู้ใช้ที่เหมาะสมในการทำเช่นlockfile-create
(ในlockfile-progs
) หรือdotlockfile
(ในliblockfile-bin
) และตรวจสอบให้แน่ใจว่าคุณทำความสะอาดอย่างถูกต้อง (เช่นtrap EXIT
) หรือทดสอบการล็อคเก่า (เช่นกับ--use-pid
)
มีเสื้อคลุมรอบ ๆ การเรียกของระบบฝูง (2) ที่เรียกว่าไม่น่าแปลกใจฝูง (1) ทำให้ง่ายต่อการได้รับการล็อคแบบเอกสิทธิ์เฉพาะบุคคลโดยไม่ต้องกังวลเกี่ยวกับการล้างข้อมูล ฯลฯ มีตัวอย่างในman page เกี่ยวกับวิธีการใช้มันในเชลล์สคริปต์
flock()
เรียกระบบไม่ใช่ POSIX และไม่สามารถใช้งานกับไฟล์บน NFS mounts
flock -x -n %lock file% -c "%command%"
เพื่อให้แน่ใจว่ามีเพียงหนึ่งอินสแตนซ์เท่านั้นที่กำลังทำงาน
คุณต้องมีการดำเนินการแบบอะตอมมิกเช่นกันสิ่งนี้จะล้มเหลวในที่สุด
แต่จะทำอย่างไรถ้าหากฝูงไม่ว่าง ก็มี mkdir นั่นเป็นปฏิบัติการอะตอมด้วยเช่นกัน กระบวนการเดียวเท่านั้นที่จะส่งผลให้ mkdir ประสบความสำเร็จกระบวนการอื่น ๆ ทั้งหมดจะล้มเหลว
ดังนั้นรหัสคือ:
if mkdir /var/lock/.myscript.exclusivelock
then
# do stuff
:
rmdir /var/lock/.myscript.exclusivelock
fi
คุณต้องดูแลการล็อคเก่า ๆ มิฉะนั้นความผิดพลาดที่สคริปต์ของคุณจะไม่ทำงานอีกครั้ง
sleep 10
ก่อนrmdir
แล้วลองเรียงซ้อนอีกครั้ง - จะไม่มีสิ่งใด "รั่วไหล"
เพื่อให้การล็อคมีความน่าเชื่อถือคุณต้องมีการทำงานของอะตอม ข้อเสนอข้างต้นส่วนใหญ่ไม่ได้เป็นแบบอะตอม ยูทิลิตี lockfile (1) ที่เสนอนั้นดูมีแนวโน้มตามที่ man-page กล่าวถึงว่ามันเป็น "NFS-resistant" หากระบบปฏิบัติการของคุณไม่รองรับ lockfile (1) และโซลูชันของคุณต้องทำงานกับ NFS คุณมีตัวเลือกไม่มากนัก ....
NFSv2 มีการดำเนินการแบบสองอะตอม:
ด้วย 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 และเปลี่ยนชื่อการโทรชื่อไฟล์ทั้งสองจะต้องอยู่ในระบบไฟล์เดียวกัน ข้อเสนอของฉัน: ใช้ชื่อไฟล์อย่างง่ายเท่านั้น (ไม่มีเส้นทาง) และวางไฟล์และล็อคไว้ในไดเรกทอรีเดียวกัน
lockfile
ถ้ามีหรือสำรองsymlink
วิธีนี้ถ้าไม่
mv
, rm
) ควรrm -f
จะใช้มากกว่าrm
ในกรณีที่กระบวนการสอง P1, P2 กำลังแข่งอยู่หรือไม่ ตัวอย่างเช่น P1 เริ่มปลดล็อคด้วยmv
จากนั้นล็อค P2 จากนั้นปลดล็อค P2 (ทั้งคู่mv
และrm
) ในที่สุด P1 จะพยายามrm
และล้มเหลว
$$
ไว้ใน${f}.deleteme
ชื่อไฟล์
อีกตัวเลือกหนึ่งคือการใช้เปลือกของตัวเลือกโดยการทำงาน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
คุณสามารถใช้GNU Parallel
สำหรับการนี้มันทำงานเป็น mutex sem
เมื่อเรียกว่าเป็น ดังนั้นในแง่ที่เป็นรูปธรรมคุณสามารถใช้:
sem --id SCRIPTSINGLETON yourScript
หากคุณต้องการหมดเวลาด้วยให้ใช้:
sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript
การหมดเวลาของ <0 หมายถึงการออกโดยไม่เรียกใช้สคริปต์หากสัญญาณไม่ได้ถูกปล่อยออกมาภายในระยะหมดเวลาการหมดเวลาของ> 0 หมายถึงเรียกใช้สคริปต์ต่อไป
โปรดทราบว่าคุณควรให้ชื่อ (พร้อม--id
) เป็นค่าเริ่มต้นให้กับสถานีควบคุม
GNU Parallel
เป็นการติดตั้งที่ง่ายมากบนแพลตฟอร์ม Linux / OSX / Unix ส่วนใหญ่ - มันเป็นเพียงสคริปต์ Perl
sem
ที่คำถามที่เกี่ยวข้องunix.stackexchange.com/a/322200/199525
สำหรับเชลล์สคริปท์ฉันมักจะทำ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 การตั้งค่าให้เป็นอย่างอื่นฉันสามารถให้สคริปต์และกระแสข้อมูลแบตช์ของฉันตอบสนองตามงานแบ็ตช์หรือสคริปต์ชุดก่อนหน้า
rm -r $LOCK_DIR
หรือแม้กระทั่งบังคับตามที่จำเป็น ไชโย
exit 1002
หรือยัง
จริงๆอย่างรวดเร็วและจริงๆสกปรก? หนึ่งซับในที่ด้านบนของสคริปต์ของคุณจะทำงาน:
[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
แน่นอนให้แน่ใจว่าชื่อสคริปต์ของคุณไม่ซ้ำกัน :)
-gt 2
? grep ไม่เคยพบตัวเองในผลของ ps!
pgrep
ไม่ได้อยู่ใน POSIX หากคุณต้องการให้พอร์ตนี้ใช้งานได้คุณต้องมี POSIX ps
และประมวลผลเอาต์พุต
-c
| wc -l
เกี่ยวกับการเปรียบเทียบตัวเลข: -gt 1
มีการตรวจสอบตั้งแต่อินสแตนซ์แรกที่เห็นตัวเอง
ต่อไปนี้เป็นวิธีการที่รวมการล็อกไดเรกทอรีอะตอมกับการตรวจสอบการล็อกเก่าผ่าน 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
สร้างไฟล์ล็อคในตำแหน่งที่รู้จักและตรวจสอบว่ามีสคริปต์เมื่อเริ่มหรือไม่ การวาง PID ลงในไฟล์อาจเป็นประโยชน์หากมีคนพยายามติดตามอินสแตนซ์ที่ผิดพลาดซึ่งป้องกันไม่ให้สคริปต์ทำงาน
ตัวอย่างนี้อธิบายไว้ในฝูงคน แต่มันต้องการความต้องการบางอย่างเพราะเราควรจัดการข้อบกพร่องและรหัสทางออก:
#!/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 และเปรียบเทียบกับจำนวน มันซับซ้อนและไม่แน่นอน
คำตอบที่มีอยู่โพสต์นั้นขึ้นอยู่กับยูทิลิตี้ 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
เมื่อกำหนดเป้าหมายเครื่อง 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
}
หากข้อ จำกัด ของฝูงซึ่งได้รับการอธิบายไว้แล้วที่อื่นในหัวข้อนี้ไม่เป็นปัญหาสำหรับคุณแล้วสิ่งนี้ควรใช้งานได้:
#!/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
-n
จะexit 1
ดำเนินการทันทีหากไม่สามารถล็อคได้
UNIXes บางคนมีซึ่งจะคล้ายกับที่กล่าวมาแล้วlockfile
flock
จาก manpage:
lockfile สามารถใช้เพื่อสร้างไฟล์เซมาฟอร์อย่างน้อยหนึ่งไฟล์ หากไฟล์ lock- ไม่สามารถสร้างไฟล์ที่ระบุทั้งหมด (ตามลำดับที่ระบุ) มันจะรอ sleeptime (ค่าเริ่มต้นคือ 8) วินาทีและลองไฟล์สุดท้ายที่ไม่สำเร็จ คุณสามารถระบุจำนวนครั้งในการพยายามทำจนกว่าจะมีการส่งคืนความล้มเหลว หากจำนวนครั้งในการลองใหม่เป็น -1 (ค่าเริ่มต้นคือ -r-1) lockfile จะลองใหม่ตลอดไป
lockfile
ประโยชน์ได้อย่างไร?
lockfile
procmail
มีการกระจายกับ นอกจากนี้ยังมีทางเลือกdotlockfile
ที่จะไปกับliblockfile
แพคเกจ พวกเขาทั้งสองอ้างว่าทำงานได้อย่างน่าเชื่อถือบน NFS
จริงๆแล้วแม้ว่าคำตอบของ 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 เป็นต้น นี่คือทางออกที่สมบูรณ์
ฉันอยากจะทำอะไรกับ lockfiles, lockdirs, โปรแกรมล็อคพิเศษและแม้ว่าpidof
มันจะไม่พบในการติดตั้ง Linux ทั้งหมด และต้องการให้มีโค้ดที่ง่ายที่สุดเท่าที่จะเป็นไปได้ (หรืออย่างน้อยที่สุดสองสามบรรทัด) if
คำสั่งที่ง่ายที่สุดในหนึ่งบรรทัด:
if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi
/bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
ฉันใช้วิธีการง่ายๆที่จัดการไฟล์ล็อคเก่า ๆ
โปรดทราบว่าโซลูชันข้างต้นบางอย่างที่เก็บ 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
เพิ่มบรรทัดนี้ที่จุดเริ่มต้นของสคริปต์ของคุณ
[ "${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: ... ไฟล์ข้อความไม่ว่าง" อาจถูกเขียนทับโดยการปิดใช้งานสิทธิ์การเขียนในสคริปต์ของคุณ
PID และ lockfiles น่าเชื่อถือที่สุดแน่นอน เมื่อคุณพยายามรันโปรแกรมมันสามารถตรวจสอบ lockfile ซึ่งและถ้ามันมีอยู่มันสามารถใช้ps
เพื่อดูว่ากระบวนการยังคงทำงานอยู่หรือไม่ หากไม่เป็นเช่นนั้นสคริปต์จะสามารถเริ่มต้นอัปเดต PID ใน lockfile ให้เป็นของตนเอง
ฉันพบว่าโซลูชันของ 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 นั้นมีข้อบกพร่องที่สคริปต์ที่ถูกฆ่าอาจทิ้งไว้ข้างหลังไดเรกทอรีดังนั้นจึงป้องกันไม่ให้อินสแตนซ์อื่นทำงาน
semaphoricใช้ยูทิลิตี้flock
(ตามที่กล่าวข้างต้นเช่นโดย presto8) ที่จะใช้สัญญาณนับ มันเปิดใช้งานจำนวนกระบวนการที่เกิดขึ้นพร้อมกันที่คุณต้องการโดยเฉพาะ เราใช้เพื่อ จำกัด ระดับการทำงานพร้อมกันของกระบวนการผู้ปฏิบัติงานคิวต่างๆ
มันเหมือนsemแต่น้ำหนักเบากว่ามาก (การเปิดเผยอย่างเต็มรูปแบบ: ฉันเขียนหลังจากค้นหาเซมาแล้วมันหนักเกินไปสำหรับความต้องการของเราและไม่มียูทิลิตี้เซมาฟอร์นับง่าย ๆ ที่มีให้)
ตัวอย่างที่มีฝูง (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
ตอบล้านครั้งแล้ว แต่อีกวิธีหนึ่งโดยไม่จำเป็นต้องพึ่งพาภายนอก:
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 ล่าสุดหรือไม่
การใช้การล็อคของกระบวนการนั้นแข็งแกร่งกว่ามากและดูแลทางออกที่ไม่น่าดูด้วย 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
ฉันใช้ 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
เป็นการดีที่ได้เห็นการมีอยู่ของกระบวนการในหน่วยความจำ (ไม่ว่าสถานะของกระบวนการจะเป็นอย่างไร) แต่มันทำงานได้สำหรับฉัน
เส้นทางของฝูงคือหนทางที่จะไป คิดว่าจะเกิดอะไรขึ้นเมื่อจู่ ๆ สคริปต์ตาย ในกรณีฝูงคุณเพียง แต่ปล่อยฝูง แต่นั่นไม่ใช่ปัญหา นอกจากนี้โปรดทราบว่าเคล็ดลับที่ชั่วร้ายคือการใช้ฝูงแกะสคริปต์ในตัวเอง .. แต่แน่นอนว่าจะช่วยให้คุณสามารถเรียกใช้เต็มหน้าล่วงหน้าเป็นปัญหาสิทธิ์
รวดเร็วและสกปรก?
#!/bin/sh
if [ -f sometempfile ]
echo "Already running... will now terminate."
exit
else
touch sometempfile
fi
..do what you want here..
rm sometempfile