Cách nhanh và bẩn để đảm bảo chỉ có một phiên bản của tập lệnh shell đang chạy


Câu trả lời:


109

Đâ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 -0khô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 trapsẽ đả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).


73
Như đã đề cập trong một nhận xét về câu trả lời bao phấn, điều này có một lỗ hổng nghiêm trọng - nếu tập lệnh khác bắt đầu giữa kiểm tra và tiếng vang, bạn sẽ nướng.
Paul Tomblin

1
Thủ thuật symlink rất gọn gàng, nhưng nếu chủ sở hữu của lockfile bị giết -9'd hoặc hệ thống gặp sự cố, vẫn còn một điều kiện chạy đua để đọc symlink, thông báo chủ sở hữu đã biến mất và sau đó xóa nó. Tôi đang gắn bó với giải pháp của tôi.
bmdhacks

9
Kiểm tra và tạo nguyên tử có sẵn trong vỏ bằng cách sử dụng đàn (1) hoặc lockfile (1). Xem câu trả lời khác.
dmckee --- ex-moderator mèo con

3
Xem câu trả lời của tôi để biết cách di động để thực hiện kiểm tra nguyên tử và tạo mà không cần phải phụ thuộc vào các tiện ích như đàn hoặc khóa.
lhunath

2
Đây không phải là nguyên tử và do đó là vô dụng. Bạn cần một cơ chế nguyên tử để kiểm tra & thiết lập.
K Richard Pixley

214

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 () đượ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.


11
200 là gì? Nó nói "fd" trong manul, nhưng tôi không biết điều đó có nghĩa là gì.
chovy

4
@chovy "mô tả tập tin", một số nguyên xử lý chỉ định một tệp đang mở.
Alex B

6
Nếu bất cứ ai khác đang tự hỏi: Cú pháp ( command A ) command Bgọ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.
Tiến sĩ Jan-Philip Gehrcke

1
Tôi nghĩ rằng mã bên trong lớp vỏ phụ sẽ giống như: 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?).
Jonathan Leffler

1
Các tập tin được chuyển hướng đến chỉ là một trình giữ chỗ cho khóa để hành động, không có dữ liệu có ý nghĩa đi vào nó. Là exittừ 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ó.
clacke

158

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. mkdirtạ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) .


1
Bạn có thể kết hợp if ! mkdirphầ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 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.
Tobias Kienzler

4
Điều chắc chắn mkdirlà 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 /tmpsẽ 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 mkdirnguyên tử.
lhunath

5
Nhưng có một cách để kiểm tra sự tồn tại của một tệp thông thường và tạo nó một cách nguyên tử nếu nó không: sử dụng 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.
Juan Cespedes

4
'một cách để kiểm tra xem một tập tin tồn tại và tạo ra nó trong một hành động đơn nguyên tử' - đó là 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).
Toby Speight

5
"Tất cả các cách tiếp cận kiểm tra sự tồn tại của" tệp 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 có tồn tại và tạo ra nó trong một hành động nguyên tử không." - Để làm cho nó nguyên tử, nó phải được thực hiện tại cấp độ kernel - và nó được thực hiện ở cấp kernel với flock (1) linux.die.net/man/1/flock xuất hiện từ ngày bản quyền của người đàn ông có khoảng ít nhất là từ năm 2006. Vì vậy, tôi đã tạo ra một downvote (- 1), không có gì cá nhân, chỉ cần có niềm tin mạnh mẽ rằng sử dụng các công cụ được triển khai kernel được cung cấp bởi các nhà phát triển kernel là chính xác.
Craig Hicks

42

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.


3
Cuộc 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.
maxschlepzig

17
Chạy từ một công việc Cron tôi sử dụng flock -x -n %lock file% -c "%command%"để đảm bảo chỉ có một phiên bản được thực thi.
Ryall

À, thay vì đàn chiên không tưởng tượng (1) đáng lẽ họ phải đi với một thứ như đàn (U). .. nó có một số quen thuộc với nó. . có vẻ như tôi đã nghe điều đó trước một hoặc hai lần.
Kent Kruckeberg

Đáng chú ý là tài liệu flock (2) chỉ định chỉ sử dụng với các tệp, nhưng tài liệu flock (1) chỉ định sử dụng với tệp hoặc thư mục. Tài liệu về đàn (1) không rõ ràng về cách chỉ ra sự khác biệt trong quá trình tạo, nhưng tôi cho rằng nó được thực hiện bằng cách thêm "/" cuối cùng. Dù sao, nếu đàn (1) có thể xử lý các thư mục nhưng đàn (2) không thể, thì đàn (1) không được thực hiện chỉ khi đàn (2).
Craig Hicks

27

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.


1
Chạy đồng thời một vài lần (như "./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ") và tập lệnh sẽ bị rò rỉ qua một vài lần.
Nippysaurus

7
@Nippysaurus: Phương pháp khóa này không bị rò rỉ. Những gì bạn thấy là tập lệnh ban đầu chấm dứt trước khi tất cả các bản sao được tung ra, vì vậy một bản khác có thể (chính xác) có được khóa. Để tránh điều này dương tính giả, hãy thêm một lần sleep 10trước rmdirvà thử xếp tầng lại - không có gì sẽ "rò rỉ".
Thưa ngài,

Các nguồn khác khẳng định mkdir không phải là nguyên tử trên một số hệ thống tập tin như NFS. Và btw tôi đã thấy những dịp mà trên mkdir đệ quy đồng thời NFS dẫn đến lỗi đôi khi với các công việc ma trận jenkins. Vì vậy, tôi khá chắc chắn đó là trường hợp. Nhưng mkdir là khá tốt cho các trường hợp sử dụng ít đòi hỏi IMO.
akostadinov

Bạn có thể sử dụng tùy chọn noclobber của Bash'es với các tệp thông thường.
Palec

26

Để 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ử:

  • liên kết tượng trưng
  • đổi tên

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.


Những trang nào của NFS Illustrated hỗ trợ tuyên bố rằng mkdir không phải là nguyên tử so với NFS?
maxschlepzig

Thnks cho kỹ thuật này. Một triển khai mutex shell có sẵn trong lib shell mới của tôi: github.com/ Offerirmo/offirmo- shell-lib , xem "mutex". Nó sử dụng lockfilenếu có sẵn, hoặc dự phòng cho symlinkphương pháp này nếu không.
Offirmo

Đẹp. Thật không may, phương pháp này không cung cấp một cách để tự động xóa các khóa cũ.
Richard Hansen

Đối với hai giai đoạn mở khóa ( mv, rm), nên rm -fđược sử dụng, thay vì rmtrong 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ả mvrm), cuối cùng là P1 thử rmvà thất bại.
Matt Wallis

1
@MattWallis Vấn đề cuối cùng có thể dễ dàng được giảm thiểu bằng cách đưa $$vào ${f}.deletemetên tệp.
Stefan Majewsky

23

Một tùy chọn khác là sử dụng noclobbertù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à pdkshthêm O_TRUNCcờ, 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 rmtù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

Cách tiếp cận rất mới lạ ... đây dường như là một cách để thực hiện tính nguyên tử bằng cách sử dụng tệp khóa thay vì thư mục khóa.
Matt Caldwell

Cách tiếp cận tốt đẹp. :-) Trên bẫy EXIT, cần hạn chế quá trình nào có thể dọn sạch tệp khóa. Ví dụ: bẫy 'if [[$ (cat "$ lockfile") == "$$"]]; sau đó rm "$ lockfile"; fi 'EXIT
Kevin Seifert

1
Khóa tập tin không phải là nguyên tử trên NFS. đó là lý do tại sao mọi người chuyển sang sử dụng các thư mục khóa.
K Richard Pixley

20

Bạn có thể sử dụng GNU Parallelcho đ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.


Những người quá xấu không muốn từ chối những câu trả lời vô ích: điều này dẫn đến những câu trả lời mới có liên quan bị chôn vùi trong một đống rác.
Dmitry Grigoryev

4
Chúng tôi chỉ cần rất nhiều upvote. Đây là một câu trả lời gọn gàng và ít được biết đến. (Mặc dù là OP pedantic muốn nhanh chóng-và-bẩn trong khi đây là nhanh và sạch!) Thông tin thêm về semtại câu hỏi liên quan unix.stackexchange.com/a/322200/199525 .
Một phần mây

16

Đối với các kịch bản shell, tôi có xu hướng đi với mkdirhơ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 đó.


2
Kịch bản của bạn quá dài dòng, tôi nghĩ có thể ngắn hơn rất nhiều, nhưng nhìn chung, vâng, bạn phải thiết lập các bẫy để thực hiện điều này một cách chính xác. Ngoài ra tôi sẽ thêm SIGHUP.
mojuba

Điều này hoạt động tốt, ngoại trừ có vẻ như kiểm tra $ LOCK_DIR trong khi nó loại bỏ $ __ lockdir. Có lẽ tôi nên đề xuất khi gỡ khóa bạn sẽ làm rm -r $ LOCK_DIR?
bevada

Cảm ơn vì đã góp ý. Ở trên đã được nâng mã và được đặt theo kiểu mã psuedo vì vậy nó sẽ cần điều chỉnh dựa trên việc sử dụng folks. Tuy nhiên, tôi đã cố tình đi với rmdir trong trường hợp của mình vì rmdir sẽ xóa các thư mục một cách an toàn nếu chúng trống. Nếu mọi người đang đặt tài nguyên vào chúng, chẳng hạn như các tệp PID, v.v. họ nên thay đổi việc dọn dẹp khóa của mình thành mạnh hơn rm -r $LOCK_DIRhoặ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.
Mark Stinson

Bạn đã thử exit 1002chưa?
Gilles Quenot

13

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. :)


Làm thế nào để tôi mô phỏng điều này để kiểm tra nó? Có cách nào để bắt đầu một kịch bản hai lần trong một dòng và có thể nhận được cảnh báo, nếu nó đang chạy không?
rubo77

2
Điều này hoàn toàn không hoạt động! Tại sao phải kiểm tra -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!
rubo77

pgrepkhô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 psvà xử lý đầu ra của nó.
Palec

Trên OSX -ckhô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ó.
Benjamin Peter

6

Đâ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

5

Tạo một tệp khóa ở một vị trí đã biết và kiểm tra sự tồn tại khi bắt đầu tập lệnh? Đặt PID vào tệp có thể hữu ích nếu ai đó đang cố gắng theo dõi một trường hợp sai lầm ngăn cản việc thực thi tập lệnh.


5

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


1
Bạn có thể sử dụng ln -s, vì điều này chỉ có thể tạo symlink khi không có tệp hoặc symlink nào tồn tại, giống như mkdir. rất nhiều quá trình hệ thống được sử dụng symlink trong quá khứ, ví dụ init hoặc inetd. synlink giữ quá trình id, nhưng thực sự chỉ ra không có gì. trong nhiều năm, hành vi này đã được thay đổi. quy trình sử dụng đàn và semaphores.
Znik

5

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.


4

Khi nhắm mục tiêu một máy Debian, tôi thấy lockfile-progsgói đó là một giải pháp tốt. procmailcũng đi kèm với một lockfilecô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 mkdircho 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_requirekhi 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_tryexclusive_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
}

Cảm ơn, đã thử nó trên cygwin và nó đã vượt qua các bài kiểm tra đơn giản.
ndemou

4

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 

3
Chỉ cần nghĩ rằng tôi chỉ ra rằng -x (khóa ghi) đã được đặt mặc định.
Keldon Alleyne

-nsẽ exit 1ngay lập tức nếu nó không thể có được khóa
Anentropic 30/03/2015

Cảm ơn @KeldonAlleyne, tôi đã cập nhật mã để xóa "-x" vì nó là mặc định.
uyo8

3

Một số unixes có lockfilerấ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.


Làm thế nào để chúng ta có được lockfiletiện ích ??
Offirmo

lockfileđược phân phối với procmail. Ngoài ra có một sự thay thế dotlockfileđi kèm với liblockfilegói. Cả hai đều tuyên bố sẽ làm việc đáng tin cậy trên NFS.
Ông Deathless

3

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


3

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í pidofvì 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ể). ifCâ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

1
Điều này nhạy cảm với đầu ra 'ps', trên máy của tôi (Ubuntu 14.04, / bin / ps từ phiên bản Procps-ng 3.3.9) lệnh 'ps axf' in các ký tự cây ascii làm gián đoạn số trường. Điều này làm việc cho tôi: /bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
qneill

2

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

Tôi đã cho bạn +1 cho một giải pháp khác. Althoug nó không hoạt động trong AIX (> ps -eo pid, ppid, lstart $$ | tail -1 ps: danh sách không hợp lệ với -o.) Không phải HP-UX (> ps -eo pid, ppid, lstart $$ | đuôi -1 ps: tùy chọn bất hợp pháp - o). Cảm ơn.
Tagar

2

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.


1

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ó.


1

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.


1

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.)


1

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

1

Đã 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.


1
Nếu không có lệnh gọi bẫy (hoặc ít nhất là dọn dẹp gần cuối đối với trường hợp thông thường), bạn có lỗi dương tính giả trong đó khóa bị bỏ lại xung quanh sau lần chạy cuối cùng và PID đã được sử dụng lại bởi quy trình khác sau đó. (Và trong trường hợp xấu nhất, nó đã được ban tặng cho một quá trình chạy dài như apache ....)
Philippe Chaintreuil

1
Tôi đồng ý, cách tiếp cận của tôi là thiếu sót, nó cần một cái bẫy. Tôi đã cập nhật giải pháp của mình. Tôi vẫn thích không có phụ thuộc bên ngoài.
Filidor Wiese

1

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 

1

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


0

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.


0

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

7
Điều này có thể hoặc không phải là một vấn đề, tùy thuộc vào cách sử dụng, nhưng có một điều kiện chạy đua giữa kiểm tra khóa và tạo nó, để hai tập lệnh có thể được khởi động cùng một lúc. Nếu một kết thúc trước, cái còn lại sẽ không chạy với tập tin khóa.
TimB

3
C News, người đã dạy tôi nhiều về kịch bản shell di động, được sử dụng để tạo một tệp khóa. Sau đó cố gắng liên kết nó với "khóa" - nếu liên kết thành công, bạn đã khóa, nếu không bạn đã xóa khóa. $$ và thoát ra.
Paul Tomblin

Đó là một cách thực sự tốt để làm điều đó, ngoại trừ bạn vẫn phải tháo khóa bằng tay nếu có sự cố và khóa tệp không bị xóa.
Matthew Scharley

2
Nhanh và bẩn, đó là những gì anh ta yêu cầu :)
Aupajo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.