Câu trả lời:
Đây là một triển khai sử dụng lockfile và lặp lại một PID vào nó. Điều này phục vụ như một sự bảo vệ nếu quá trình bị giết trước khi loại bỏ 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}
Thủ thuật ở đây là kill -0
không cung cấp bất kỳ tín hiệu nào mà chỉ kiểm tra xem có tồn tại một quy trình với PID đã cho hay không. Ngoài ra, lệnh gọi trap
sẽ đảm bảo rằng lockfile bị xóa ngay cả khi tiến trình của bạn bị hủy (ngoại trừ kill -9
).
Sử dụng flock(1)
để tạo một phạm vi độc quyền khóa một mô tả tập tin. Bằng cách này, bạn thậm chí có thể đồng bộ hóa các phần khác nhau của tập lệnh.
#!/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
Điều này đảm bảo rằng mã giữa (
và)
được chạy bởi một quy trình tại một thời điểm và quy trình đó không chờ quá lâu cho một khóa.
Hãy cẩn thận: lệnh đặc biệt này là một phần của util-linux
. Nếu bạn chạy một hệ điều hành khác ngoài Linux, nó có thể có hoặc không có sẵn.
( command A ) command B
gọi một subshell cho command A
. Tài liệu tại tldp.org/LDP/abs/html/subshells.html . Tôi vẫn không chắc chắn về thời gian gọi của subshell và lệnh B.
if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fi
để nếu thời gian chờ xảy ra (một số quy trình khác có tệp bị khóa), tập lệnh này không đi trước và sửa đổi tệp. Có lẽ ... đối số là 'nhưng nếu mất 10 giây và khóa vẫn không khả dụng, thì nó sẽ không bao giờ khả dụng', có lẽ vì quá trình giữ khóa không kết thúc (có thể nó đang được chạy theo một trình sửa lỗi?).
exit
từ một phần bên trong (
)
. Khi quá trình con kết thúc, khóa sẽ tự động được giải phóng, vì không có quá trình giữ nó.
Tất cả các phương pháp kiểm tra sự tồn tại của "tập tin khóa" đều thiếu sót.
Tại sao? Bởi vì không có cách nào để kiểm tra xem một tập tin có tồn tại và tạo nó trong một hành động nguyên tử không. Vì điều này; có một điều kiện chủng tộc SILL làm cho những nỗ lực của bạn ở trạng thái loại trừ lẫn nhau.
Thay vào đó, bạn cần sử dụng mkdir
. mkdir
tạo một thư mục nếu nó chưa tồn tại và nếu có, nó sẽ đặt mã thoát. Quan trọng hơn, nó thực hiện tất cả điều này trong một hành động nguyên tử duy nhất làm cho nó hoàn hảo cho kịch bản này.
if ! mkdir /tmp/myscript.lock 2>/dev/null; then
echo "Myscript is already running." >&2
exit 1
fi
Để biết tất cả các chi tiết, xem BashFAQ xuất sắc: http://mywiki.wooledge.org/BashFAQ/045
Nếu bạn muốn chăm sóc ổ khóa cũ, bộ nhiệt áp (1) có ích. Nhược điểm duy nhất ở đây là hoạt động mất khoảng một giây, vì vậy nó không phải là ngay lập tức.
Đây là một chức năng tôi đã viết một lần để giải quyết vấn đề bằng cách sử dụng bộ nhiệt áp:
# 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
}
Bạn có thể sử dụng nó trong một kịch bản như vậy:
mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }
Nếu bạn không quan tâm đến tính di động (các giải pháp này sẽ hoạt động trên hầu hết các hộp UNIX), bộ kết hợp của Linux (1) cung cấp một số tùy chọn bổ sung và cũng có đàn (1) .
if ! mkdir
phần này với việc kiểm tra xem quy trình với bộ lưu trữ PID (khi khởi động thành công) bên trong lockdir có thực sự đang chạy và giống hệt với tập lệnh để bảo vệ stalenes hay không. Điều này cũng sẽ bảo vệ chống lại việc sử dụng lại PID sau khi khởi động lại và thậm chí không yêu cầu fuser
.
mkdir
là không được xác định là hoạt động nguyên tử và do đó "tác dụng phụ" là một chi tiết triển khai của hệ thống tệp. Tôi hoàn toàn tin anh ta nếu anh ta nói NFS không thực hiện nó theo kiểu nguyên tử. Mặc dù tôi không nghi ngờ rằng bạn /tmp
sẽ là một chia sẻ NFS và có khả năng sẽ được cung cấp bởi một fs thực hiện mkdir
nguyên tử.
ln
để tạo một liên kết cứng từ một tệp khác. Nếu bạn có các hệ thống tệp lạ không đảm bảo điều đó, bạn có thể kiểm tra nút inode của tệp mới sau đó để xem liệu nó có giống với tệp gốc không.
open(... O_CREAT|O_EXCL)
. Bạn chỉ cần một chương trình người dùng phù hợp để làm như vậy, chẳng hạn như lockfile-create
(trong lockfile-progs
) hoặc dotlockfile
(trong liblockfile-bin
). Và đảm bảo bạn dọn dẹp đúng cách (ví dụ trap EXIT
) hoặc kiểm tra các ổ khóa cũ (ví dụ như với --use-pid
).
Có một trình bao bọc xung quanh cuộc gọi hệ thống đàn (2) được gọi là, không tưởng tượng, đàn (1). Điều này làm cho nó tương đối dễ dàng để có được các khóa độc quyền mà không phải lo lắng về việc dọn dẹp, v.v. Có những ví dụ trên trang hướng dẫn về cách sử dụng nó trong tập lệnh shell.
flock()
gọi hệ thống không phải là POSIX và không hoạt động đối với các tệp trên ngàm NFS.
flock -x -n %lock file% -c "%command%"
để đảm bảo chỉ có một phiên bản được thực thi.
Bạn cần một hoạt động nguyên tử, như đàn, điều này cuối cùng sẽ thất bại.
Nhưng phải làm gì nếu đàn không có sẵn. Vâng, có mkdir. Đó cũng là một hoạt động nguyên tử. Chỉ một quá trình sẽ dẫn đến một mkdir thành công, tất cả những quá trình khác sẽ thất bại.
Vì vậy, mã là:
if mkdir /var/lock/.myscript.exclusivelock
then
# do stuff
:
rmdir /var/lock/.myscript.exclusivelock
fi
Bạn cần phải chăm sóc các ổ khóa cũ khác sau khi sự cố kịch bản của bạn sẽ không bao giờ chạy lại.
sleep 10
trước rmdir
và thử xếp tầng lại - không có gì sẽ "rò rỉ".
Để làm cho khóa đáng tin cậy, bạn cần một hoạt động nguyên tử. Nhiều đề xuất trên không phải là nguyên tử. Tiện ích lockfile (1) được đề xuất có vẻ đầy hứa hẹn như trang con người đã đề cập, đó là "kháng NFS". Nếu HĐH của bạn không hỗ trợ lockfile (1) và giải pháp của bạn phải hoạt động trên NFS, bạn không có nhiều tùy chọn ....
NFSv2 có hai hoạt động nguyên tử:
Với NFSv3, cuộc gọi tạo cũng là nguyên tử.
Các hoạt động của thư mục KHÔNG phải là nguyên tử theo NFSv2 và NFSv3 (vui lòng tham khảo cuốn sách 'NFS Illustrated' của Brent Callaghan, ISBN 0-201-32570-5; Brent là cựu chiến binh NFS tại Sun).
Biết được điều này, bạn có thể triển khai khóa spin cho các tệp và thư mục (trong shell chứ không phải PHP):
khóa dir hiện tại:
while ! ln -s . lock; do :; done
khóa một tập tin:
while ! ln -s ${f} ${f}.lock; do :; done
mở khóa thư mục hiện tại (giả định, quá trình đang chạy thực sự có được khóa):
mv lock deleteme && rm deleteme
mở khóa một tập tin (giả định, quá trình đang chạy thực sự có được khóa):
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
Hủy bỏ cũng không phải là nguyên tử, do đó đầu tiên là đổi tên (đó là nguyên tử) và sau đó loại bỏ.
Đối với các cuộc gọi symlink và đổi tên, cả hai tên tệp phải nằm trên cùng một hệ thống tệp. Đề xuất của tôi: chỉ sử dụng tên tệp đơn giản (không có đường dẫn) và đặt tệp và khóa vào cùng một thư mục.
lockfile
nếu có sẵn, hoặc dự phòng cho symlink
phương pháp này nếu không.
mv
, rm
), nên rm -f
được sử dụng, thay vì rm
trong trường hợp hai quá trình P1, P2 đang chạy đua? Ví dụ: P1 bắt đầu mở khóa bằng mv
, sau đó khóa P2, sau đó P2 mở khóa (cả mv
và rm
), cuối cùng là P1 thử rm
và thất bại.
$$
vào ${f}.deleteme
tên tệp.
Một tùy chọn khác là sử dụng noclobber
tùy chọn của shell bằng cách chạy set -C
. Sau đó>
sẽ thất bại nếu tập tin đã tồn tại.
Tóm lại:
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
Điều này khiến shell gọi:
open(pathname, O_CREAT|O_EXCL)
mà nguyên tử tạo tệp hoặc thất bại nếu tệp đã tồn tại.
Theo nhận xét về BashFAQ 045 , điều này có thể thất bại ksh88
, nhưng nó hoạt động trong tất cả các trình bao của tôi:
$ 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
Điều thú vị là pdksh
thêm O_TRUNC
cờ, nhưng rõ ràng nó là dư thừa:
hoặc bạn đang tạo một tệp trống hoặc bạn không làm gì cả.
Cách bạn thực hiện rm
tùy thuộc vào cách bạn muốn các lối thoát ô uế được xử lý.
Xóa trên lối thoát sạch
Các lần chạy mới thất bại cho đến khi vấn đề khiến lối thoát ô uế được giải quyết và lockfile được xóa thủ công.
# acquire lock
# do work (code here may call exit, etc.)
rm "$lockfile"
Xóa trên mọi lối thoát
Chạy mới thành công với điều kiện kịch bản chưa chạy.
trap 'rm "$lockfile"' EXIT
Bạn có thể sử dụng GNU Parallel
cho điều này vì nó hoạt động như một mutex khi được gọi là sem
. Vì vậy, về mặt cụ thể, bạn có thể sử dụng:
sem --id SCRIPTSINGLETON yourScript
Nếu bạn cũng muốn thời gian chờ, hãy sử dụng:
sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript
Hết thời gian <0 có nghĩa là thoát mà không chạy tập lệnh nếu semaphore không được phát hành trong thời gian chờ, thời gian chờ> 0 có nghĩa là vẫn chạy tập lệnh.
Lưu ý rằng bạn nên đặt tên cho nó (với --id
) nếu không nó sẽ mặc định cho thiết bị đầu cuối kiểm soát.
GNU Parallel
là một cài đặt rất đơn giản trên hầu hết các nền tảng Linux / OSX / Unix - nó chỉ là một tập lệnh Perl.
sem
tại câu hỏi liên quan unix.stackexchange.com/a/322200/199525 .
Đối với các kịch bản shell, tôi có xu hướng đi với mkdir
hơnflock
vì nó làm cho các ổ khóa di động hơn.
Dù bằng cách nào, sử dụng set -e
là không đủ. Điều đó chỉ thoát khỏi tập lệnh nếu bất kỳ lệnh nào thất bại. Khóa của bạn vẫn sẽ bị bỏ lại phía sau.
Để dọn dẹp khóa đúng cách, bạn thực sự nên đặt bẫy của mình thành một cái gì đó giống như mã psuedo này (được nâng lên, đơn giản hóa và chưa được kiểm tra nhưng từ các tập lệnh được sử dụng tích cực):
#=======================================================================
# 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
Đây là những gì sẽ xảy ra. Tất cả các bẫy sẽ tạo ra một lối thoát để chức năng__sig_exit
sẽ luôn xảy ra (chặn SIGKILL) để dọn sạch ổ khóa của bạn.
Lưu ý: giá trị thoát của tôi không phải là giá trị thấp. Tại sao? Các hệ thống xử lý hàng loạt khác nhau thực hiện hoặc có kỳ vọng về các số từ 0 đến 31. Đặt chúng thành một thứ khác, tôi có thể để các tập lệnh và luồng hàng loạt của mình phản ứng tương ứng với công việc hoặc tập lệnh trước đó.
rm -r $LOCK_DIR
hoặc thậm chí buộc nó cần thiết (như tôi đã làm trong các trường hợp đặc biệt như giữ các tệp cào tương đối). Chúc mừng.
exit 1002
chưa?
Thực sự nhanh chóng và thực sự bẩn? Điều này một lót trên đầu tập lệnh của bạn sẽ hoạt động:
[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
Tất nhiên, chỉ cần đảm bảo rằng tên tập lệnh của bạn là duy nhất. :)
-gt 2
? grep không phải lúc nào cũng tìm thấy chính nó trong kết quả của ps!
pgrep
không có trong POSIX. Nếu bạn muốn làm việc này một cách hợp lý, bạn cần POSIX ps
và xử lý đầu ra của nó.
-c
không tồn tại, bạn sẽ phải sử dụng | wc -l
. Về so sánh số: -gt 1
được kiểm tra vì phiên bản đầu tiên nhìn thấy chính nó.
Đây là một cách tiếp cận kết hợp khóa thư mục nguyên tử với kiểm tra khóa cũ thông qua PID và khởi động lại nếu cũ. Ngoài ra, điều này không dựa trên bất kỳ bashism.
#!/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
Ví dụ này được giải thích trong đàn người, nhưng nó cần một số điều kiện, bởi vì chúng ta nên quản lý lỗi và mã thoát:
#!/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
Bạn có thể sử dụng một phương pháp khác, liệt kê các quy trình mà tôi đã sử dụng trong quá khứ. Nhưng điều này phức tạp hơn phương pháp trên. Bạn nên liệt kê các quy trình theo ps, lọc theo tên của nó, bộ lọc bổ sung grep -v grep để loại bỏ ký sinh trùng cuối cùng đếm nó bằng grep -c. và so sánh với số lượng. Nó phức tạp và không chắc chắn
Các câu trả lời hiện có được đăng hoặc dựa trên tiện ích CLI flock
hoặc không bảo mật đúng cách tệp khóa. Tiện ích đàn không có sẵn trên tất cả các hệ thống không phải Linux (ví dụ FreeBSD) và không hoạt động đúng trên NFS.
Trong những ngày đầu quản trị hệ thống và phát triển hệ thống, tôi đã nói rằng một phương pháp an toàn và tương đối di động để tạo tệp khóa là tạo tệp tạm thời bằng cách sử dụng mkemp(3)
hoặcmkemp(1)
ghi thông tin nhận dạng vào tệp tạm thời (ví dụ: PID), sau đó liên kết cứng tập tin tạm thời vào tập tin khóa. Nếu liên kết thành công, thì bạn đã có được khóa thành công.
Khi sử dụng khóa trong shell script, tôi thường đặt một obtain_lock()
hàm trong một hồ sơ được chia sẻ và sau đó lấy nguồn từ các script. Dưới đây là một ví dụ về chức năng khóa của tôi:
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;
};
Sau đây là một ví dụ về cách sử dụng chức năng khóa:
#!/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
Nhớ gọi clean_up
tại bất kỳ điểm thoát nào trong tập lệnh của bạn.
Tôi đã sử dụng ở trên trong cả môi trường Linux và FreeBSD.
Khi nhắm mục tiêu một máy Debian, tôi thấy lockfile-progs
gói đó là một giải pháp tốt. procmail
cũng đi kèm với một lockfile
công cụ. Tuy nhiên đôi khi tôi bị mắc kẹt với cả hai điều này.
Đây là giải pháp của tôi sử dụng mkdir
cho nguyên tử và một tệp PID để phát hiện các khóa cũ. Mã này hiện đang được sản xuất trên thiết lập Cygwin và hoạt động tốt.
Để sử dụng nó chỉ cần gọi exclusive_lock_require
khi bạn cần có quyền truy cập độc quyền vào một cái gì đó. Tham số tên khóa tùy chọn cho phép bạn chia sẻ khóa giữa các tập lệnh khác nhau. Ngoài ra còn có hai hàm cấp thấp hơn ( exclusive_lock_try
và exclusive_lock_retry
) nếu bạn cần một cái gì đó phức tạp hơn.
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
}
Nếu các giới hạn của đàn, đã được mô tả ở nơi khác trên chuỗi này, thì đó không phải là vấn đề đối với bạn, thì điều này sẽ hoạt động:
#!/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
sẽ exit 1
ngay lập tức nếu nó không thể có được khóa
Một số unixes có lockfile
rất giống với đã được đề cập flock
.
Từ trang hướng dẫn:
lockfile có thể được sử dụng để tạo một hoặc nhiều tệp semaphore. Nếu tệp khóa không thể tạo tất cả các tệp được chỉ định (theo thứ tự được chỉ định), nó sẽ đợi thời gian chờ (mặc định là 8) giây và thử lại tệp cuối cùng không thành công. Bạn có thể chỉ định số lần thử lại để làm cho đến khi thất bại được trả về. Nếu số lần thử lại là -1 (mặc định, tức là -r-1) lockfile sẽ thử lại mãi mãi.
lockfile
tiện ích ??
lockfile
được phân phối với procmail
. Ngoài ra có một sự thay thế dotlockfile
đi kèm với liblockfile
gói. Cả hai đều tuyên bố sẽ làm việc đáng tin cậy trên NFS.
Trên thực tế mặc dù câu trả lời của bmdhacks gần như tốt, nhưng có một chút khả năng tập lệnh thứ hai chạy sau lần đầu tiên kiểm tra lockfile và trước khi nó viết nó. Vì vậy, cả hai sẽ viết tập tin khóa và cả hai sẽ chạy. Đây là cách làm cho nó hoạt động chắc chắn:
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
Các noclobber
tùy chọn sẽ đảm bảo rằng lệnh chuyển hướng sẽ thất bại nếu tập tin đã tồn tại. Vì vậy, lệnh redirect thực sự là nguyên tử - bạn viết và kiểm tra tệp bằng một lệnh. Bạn không cần phải xóa tệp khóa ở cuối tệp - nó sẽ bị xóa bởi bẫy. Tôi hy vọng điều này sẽ giúp những người sẽ đọc nó sau.
Tái bút Tôi không thấy Mikel đã trả lời đúng câu hỏi, mặc dù anh ta không bao gồm lệnh bẫy để giảm khả năng tệp khóa sẽ bị bỏ lại sau khi dừng tập lệnh bằng Ctrl-C chẳng hạn. Vì vậy, đây là giải pháp hoàn chỉnh
Tôi muốn loại bỏ các lockfiles, lockdir, các chương trình khóa đặc biệt và thậm chí pidof
vì nó không được tìm thấy trên tất cả các bản cài đặt Linux. Cũng muốn có mã đơn giản nhất có thể (hoặc ít nhất là ít dòng nhất có thể). if
Câu lệnh đơn giản nhất , trong một dòng:
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; }'
Tôi sử dụng một cách tiếp cận đơn giản để xử lý các tập tin khóa cũ.
Lưu ý rằng một số giải pháp trên lưu trữ pid, bỏ qua thực tế là pid có thể bao quanh. Vì vậy - chỉ cần kiểm tra xem có một quy trình hợp lệ với pid được lưu trữ là không đủ, đặc biệt là đối với các tập lệnh chạy dài.
Tôi sử dụng noclobber để đảm bảo chỉ một tập lệnh có thể mở và ghi vào tệp khóa cùng một lúc. Hơn nữa, tôi lưu trữ đủ thông tin để xác định duy nhất một quy trình trong tệp khóa. Tôi xác định tập hợp dữ liệu để xác định duy nhất một quy trình được pid, ppid, lstart.
Khi một tập lệnh mới khởi động, nếu nó không tạo được tệp khóa, thì nó sẽ xác minh rằng quá trình tạo tệp khóa vẫn còn. Nếu không, chúng tôi giả sử quy trình ban đầu đã chết một cái chết vô duyên, và để lại một tập tin khóa cũ. Kịch bản mới sau đó có quyền sở hữu tệp khóa và tất cả cũng là thế giới.
Nên hoạt động với nhiều shell trên nhiều nền tảng. Nhanh chóng, di động và đơn giản.
#!/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
Thêm dòng này vào đầu tập lệnh của bạn
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
Đó là một mã soạn sẵn từ đàn ông.
Nếu bạn muốn đăng nhập nhiều hơn, hãy sử dụng cái này
[ "${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."
Điều này thiết lập và kiểm tra khóa bằng cách sử dụng flock
tiện ích. Mã này phát hiện nếu nó được chạy lần đầu tiên bằng cách kiểm tra biến FLOCKER, nếu nó không được đặt thành tên tập lệnh, thì nó sẽ cố gắng khởi động lại tập lệnh một cách đệ quy bằng cách sử dụng flock và với biến FLOCKER được khởi tạo, nếu FLOCKER được đặt chính xác, sau đó đổ vào lần lặp trước đã thành công và nó vẫn ổn để tiến hành. Nếu khóa bận, nó không thành công với mã thoát cấu hình.
Nó dường như không hoạt động trên Debian 7, nhưng dường như hoạt động trở lại với gói linux 2.25 thử nghiệm. Nó viết "bầy: ... Tập tin văn bản bận". Nó có thể bị ghi đè bằng cách vô hiệu hóa quyền ghi trên tập lệnh của bạn.
PID và lockfiles chắc chắn là đáng tin cậy nhất. Khi bạn cố chạy chương trình, nó có thể kiểm tra lockfile và nếu nó tồn tại, nó có thể sử dụng ps
để xem liệu tiến trình có còn chạy hay không. Nếu không, tập lệnh có thể bắt đầu, cập nhật PID trong tệp khóa thành chính nó.
Tôi thấy rằng giải pháp của bmdhack là thực tế nhất, ít nhất là cho trường hợp sử dụng của tôi. Sử dụng flock và lockfile dựa vào việc loại bỏ lockfile bằng rm khi script kết thúc, điều này không thể luôn được đảm bảo (ví dụ: kill -9).
Tôi sẽ thay đổi một điều nhỏ về giải pháp của bmdhack: Nó đưa ra quan điểm loại bỏ tệp khóa, mà không nói rằng điều này là không cần thiết cho hoạt động an toàn của semaphore này. Việc anh ta sử dụng kill -0 đảm bảo rằng một lockfile cũ cho một tiến trình chết sẽ đơn giản bị bỏ qua / ghi đè.
Do đó, giải pháp đơn giản hóa của tôi chỉ đơn giản là thêm phần sau vào đầu đĩa đơn của bạn:
## 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}
Tất nhiên, tập lệnh này vẫn có một lỗ hổng là các quá trình có khả năng bắt đầu cùng một lúc có nguy cơ về chủng tộc, vì kiểm tra khóa và thiết lập các hoạt động không phải là một hành động nguyên tử đơn lẻ. Nhưng giải pháp đề xuất cho việc này bởi lhunath sử dụng mkdir có một lỗ hổng là một tập lệnh bị giết có thể để lại thư mục, do đó ngăn các trường hợp khác chạy.
Các semaphoric sử dụng tiện ích flock
(như đã trình bày ở trên, ví dụ bằng cách presto8) để thực hiện một semaphore đếm . Nó cho phép bất kỳ số lượng cụ thể của các quá trình đồng thời bạn muốn. Chúng tôi sử dụng nó để giới hạn mức độ đồng thời của các quy trình nhân viên xếp hàng khác nhau.
Nó giống như sem nhưng trọng lượng nhẹ hơn nhiều . (Tiết lộ đầy đủ: Tôi đã viết nó sau khi tìm thấy sem quá nặng so với nhu cầu của chúng tôi và không có tiện ích semaphore đơn giản nào có sẵn.)
Một ví dụ với đàn (1) nhưng không có subshell. flock () ed file / tmp / foo không bao giờ bị xóa, nhưng điều đó không quan trọng vì nó bị flock () và 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
Đã trả lời một triệu lần rồi, nhưng theo một cách khác, không cần phụ thuộc bên ngoài:
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
Mỗi lần nó ghi PID hiện tại ($$) vào lockfile và kiểm tra khởi động tập lệnh nếu một tiến trình đang chạy với PID mới nhất.
Sử dụng khóa của quy trình mạnh hơn nhiều và cũng quan tâm đến các lối thoát vô duyên. lock_file được giữ miễn là quá trình đang chạy. Nó sẽ bị đóng (bằng vỏ) khi quá trình tồn tại (ngay cả khi nó bị giết). Tôi thấy điều này rất hiệu quả:
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
Tôi sử dụng oneliner @ ngay từ đầu kịch bản:
#!/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
Thật tốt khi thấy sự hiện diện của quá trình trong bộ nhớ (bất kể trạng thái của quá trình là gì); nhưng nó làm công việc cho tôi
Con đường đàn là con đường để đi. Hãy suy nghĩ về những gì xảy ra khi kịch bản đột ngột chết. Trong trường hợp đàn, bạn chỉ mất đàn, nhưng đó không phải là vấn đề. Ngoài ra, lưu ý rằng một mánh khóe xấu xa là lấy một bầy trên chính kịch bản .. nhưng điều đó tất nhiên cho phép bạn chạy toàn diện trước các vấn đề về quyền.
Nhanh chóng và hèn hạ?
#!/bin/sh
if [ -f sometempfile ]
echo "Already running... will now terminate."
exit
else
touch sometempfile
fi
..do what you want here..
rm sometempfile