ما هي أفضل طريقة لضمان تشغيل مثيل واحد فقط من البرنامج النصي باش؟ [مكرر

StackOverflow https://stackoverflow.com/questions/1715137

سؤال

ما هي أبسط / أفضل طريقة لضمان تشغيل مثيل واحد فقط من البرنامج النصي المعطى - على افتراض أنه باش على لينكس؟

في الوقت الحالي أفعل:

ps -C script.name.sh > /dev/null 2>&1 || ./script.name.sh

لكن لديها العديد من القضايا:

  1. يضع الشيك خارج البرنامج النصي
  2. لا يسمح لي بتشغيل نفس البرنامج النصي من حسابات منفصلة - وهو ما أود في بعض الأحيان.
  3. -C يتحقق فقط أول 14 حرفا من اسم العملية

بالطبع، أستطيع أن أكتب معالجتي PidFile الخاصة بي، لكنني أشعر أنه يجب أن يكون هناك طريقة بسيطة للقيام بذلك.

هل كانت مفيدة؟

المحلول

إذا كان البرنامج النصي هو نفسه عبر جميع المستخدمين، فيمكنك استخدام lockfile نهج. إذا حصلت على القفل، فانتقل إلى إظهار رسالة وإنهاء آخر.

كمثال:

[Terminal #1] $ lockfile -r 0 /tmp/the.lock
[Terminal #1] $ 

[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] lockfile: Sorry, giving up on "/tmp/the.lock"

[Terminal #1] $ rm -f /tmp/the.lock
[Terminal #1] $ 

[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] $ 

بعد /tmp/the.lock تم الحصول على البرنامج النصي الخاص بك سيكون هو الوحيد مع الوصول إلى التنفيذ. عند الانتهاء، فقط قم بإزالة القفل. في نموذج البرنامج النصي قد يبدو هذا:

#!/bin/bash

lockfile -r 0 /tmp/the.lock || exit 1

# Do stuff here

rm -f /tmp/the.lock

نصائح أخرى

تم استخدام قفل الاستشاري للأعمار ويمكن استخدامه في البرامج النصية باش. انا افضل بسيط flock (من util-linux[-ng]) على lockfile (من procmail). وتذكر دائما عن فخ عند الخروج (Sigspec == EXIT أو 0, ، محاصرة الإشارات المحددة غير ضرورية) في تلك النصوص.

في عام 2009، أصدرت غنيمة السيناريو القابلة للقفل (متوفرة في الأصل في صفحة Wiki الخاصة بي، المتاحة في الوقت الحاضر جوهر). تحويل ذلك إلى حالة واحدة لكل مستخدم هو تافهة. باستخدامه يمكنك أيضا كتابة البرامج النصية بسهولة لسيناريوهات أخرى تتطلب بعض القفل أو المزامنة.

هنا هو المرجع المذكور لراحتك.

#!/bin/bash
# SPDX-License-Identifier: MIT

## Copyright (C) 2009 Przemyslaw Pawelczyk <przemoc@gmail.com>
##
## This script is licensed under the terms of the MIT license.
## https://opensource.org/licenses/MIT
#
# Lockable script boilerplate

### HEADER ###

LOCKFILE="/var/lock/`basename $0`"
LOCKFD=99

# PRIVATE
_lock()             { flock -$1 $LOCKFD; }
_no_more_locking()  { _lock u; _lock xn && rm -f $LOCKFILE; }
_prepare_locking()  { eval "exec $LOCKFD>\"$LOCKFILE\""; trap _no_more_locking EXIT; }

# ON START
_prepare_locking

# PUBLIC
exlock_now()        { _lock xn; }  # obtain an exclusive lock immediately or fail
exlock()            { _lock x; }   # obtain an exclusive lock
shlock()            { _lock s; }   # obtain a shared lock
unlock()            { _lock u; }   # drop a lock

### BEGIN OF SCRIPT ###

# Simplest example is avoiding running multiple instances of script.
exlock_now || exit 1

# Remember! Lock file is removed when one of the scripts exits and it is
#           the only script holding the lock or lock is not acquired at all.

أظن flock ربما يكون البديل الأسهل (والأكثر لا تنسى). أنا استخدمه في وظيفة كرون إلى الترميز التلقائي دي في دي و CDS.

# try to run a command, but fail immediately if it's already running
flock -n /var/lock/myjob.lock   my_bash_command

يستخدم -w بالنسبة للتجارب أو ترك الخيارات للانتظار حتى يتم إصدار القفل. أخيرا، تظهر صفحة الرجل مثالا رائعا لأوامر متعددة:

   (
     flock -n 9 || exit 1
     # ... commands executed under lock ...
   ) 9>/var/lock/mylockfile

استخدم ال set -o noclobber الخيار ومحاولة الكتابة فوق ملف مشترك.

مثال قصير

if ! (set -o noclobber ; echo > /tmp/global.lock) ; then
    exit 1  # the global.lock already exists
fi

# ... remainder of script ...

مثال أطول

هذا المثال سوف تنتظر global.lock ملف ولكن المهلة بعد فترة طويلة جدا.

 function lockfile_waithold()
 {
    declare -ir time_beg=$(date '+%s')
    declare -ir time_max=7140  # 7140 s = 1 hour 59 min.

    # poll for lock file up to ${time_max}s
    # put debugging info in lock file in case of issues ...
    while ! \
       (set -o noclobber ; \
        echo -e "DATE:$(date)\nUSER:$(whoami)\nPID:$$" > /tmp/global.lock \ 
       ) 2>/dev/null
    do
        if [ $(($(date '+%s') - ${time_beg})) -gt ${time_max} ] ; then
            echo "Error: waited too long for lock file /tmp/global.lock" 1>&2
            return 1
        fi
        sleep 1
    done

    return 0
 }

 function lockfile_release()
 {
    rm -f /tmp/global.lock
 }

 if ! lockfile_waithold ; then
      exit 1
 fi
 trap lockfile_release EXIT

 # ... remainder of script ...


(هذا يشبه هذا المشنور بواسطة Barry Kelly والتي لوحظ بعد ذلك.)

لست متأكدا من وجود أي حل قوي قوي، لذلك قد ينتهي بك الأمر بدافعك.

Lockfiles غير كامل، ولكن أقل من استخدام "PS | Grep |. خط أنابيب Grep -v.

بعد القول أنه قد تفكر في الحفاظ على التحكم في العملية منفصلة عن البرنامج النصي الخاص بك - لديك برنامج نصي بدء التشغيل. أو، عوامل على الأقل في الوظائف التي عقدت في ملف منفصل، لذلك قد تكون في البرنامج النصي للمتصل:

. my_script_control.ksh

# Function exits if cannot start due to lockfile or prior running instance.
my_start_me_up lockfile_name;
trap "rm -f $lockfile_name; exit" 0 2 3 15

في كل برنامج نصي يحتاج إلى منطق التحكم. ال فخ يضمن إزالته LockFile عند خروج المتصل، لذلك ليس لديك رمز هذا على كل نقطة خروج في البرنامج النصي.

باستخدام برنامج نصي للتحكم منفصل يعني أنه يمكنك التحقق من حالات الحافة: إزالة ملفات السجلات التي لا معنى لها، تحقق من أن LockFile مرتبط بشكل صحيح مع مثيل تشغيل حاليا من البرنامج النصي، وإعطاء خيار لقتل العملية قيد التشغيل، وما إلى ذلك. وهذا يعني أيضا أن لديك فرصة أفضل لاستخدام GREP ON ps الناتج بنجاح. يمكن استخدام PS-GREP للتحقق من أن Lockfile لديه عملية قيد التشغيل المرتبطة به. ربما يمكنك تسمية Lockfiles الخاص بك بطريقة ما لتضمين معلومات حول العملية: المستخدم، PID، وما إلى ذلك، والتي يمكن استخدامها من قبل الاحتجاج البرمجي لاحقا لتحديد ما إذا كانت العملية التي أنشأت LockFile لا تزال موجودة حولها.

أول اختبار مثال

[[ $(lsof -t $0| wc -l) > 1 ]] && echo "At least one of $0 is running"

مثال الاختبار الثاني

currsh=$0
currpid=$$
runpid=$(lsof -t $currsh| paste -s -d " ")
if [[ $runpid == $currpid ]]
then
  sleep 11111111111111111
else
  echo -e "\nPID($runpid)($currpid) ::: At least one of \"$currsh\" is running !!!\n"
  false
  exit 1
fi

تفسير

"LSOF -T" لإدراج جميع PIDs من البرامج النصية الحالية التي تم تسميتها "$ 0".

القيادة "LSOF" ستقوم بمزاياتين.

  1. تجاهل PIDS الذي يتم تحريره بمحرر مثل VIM، لأن VIM تحرير ملف التعيين الخاص به مثل ".file.swp".
  2. تجاهل PIDS متوجها من خلال البرامج النصية التيار الجاري الحالي، والتي لا يمكن أن يحققه الأمر المشتق "GREP". استخدم الأمر "pstree -ph pidnum" لمعرفة تفاصيل حول الوضع الحالي الشوكي.

لقد وجدت هذا في تبعيات حزمة PROCMALAL:

apt install liblockfile-bin

يهرب:dotlockfile -l file.lock

سيتم إنشاء file.lock.

لفتح:dotlockfile -u file.lock

استخدم هذا لإدراج ملفات / الأمر هذه: dpkg-query -L liblockfile-bin

أوبونتو / ديبيان توزيعة لها start-stop-daemon أداة وهي لنفس الغرض الذي تصفه. أنظر أيضا /etc/init.d/skeleton. لمعرفة كيفية استخدامها في كتابة البرامج النصية بدء / إيقاف.

- نوح

أود أيضا أن أوصي بالنظر إلى chpst (جزء من الطائرات):

chpst -L /tmp/your-lockfile.loc ./script.name.sh

سطر واحد الحل النهائي:

[ "$(pgrep -fn $0)" -ne "$(pgrep -fo $0)" ] && echo "At least 2 copies of $0 are running"

كان لدي نفس المشكلة، وتوصلت مع قالب يستخدم Lockfile، ملف PID الذي يحمل رقم معرف العملية، و kill -0 $(cat $pid_file) تحقق لجعل البرامج النصية الإجهاض لا تتوقف عن التشغيل التالي. يؤدي هذا إلى إنشاء مجلد Userid Foobar- $ في / tmp حيث يعيش ملف LockFile و PID.

لا يزال بإمكانك استدعاء البرنامج النصي ويفعل أشياء أخرى، طالما أنك تبقي تلك الإجراءات في alertRunningPS.

#!/bin/bash

user_id_num=$(id -u)
pid_file="/tmp/foobar-$user_id_num/foobar-$user_id_num.pid"
lock_file="/tmp/foobar-$user_id_num/running.lock"
ps_id=$$

function alertRunningPS () {
    local PID=$(cat "$pid_file" 2> /dev/null)
    echo "Lockfile present. ps id file: $PID"
    echo "Checking if process is actually running or something left over from crash..."
    if kill -0 $PID 2> /dev/null; then
        echo "Already running, exiting"
        exit 1
    else
        echo "Not running, removing lock and continuing"
        rm -f "$lock_file"
        lockfile -r 0 "$lock_file"
    fi
}

echo "Hello, checking some stuff before locking stuff"

# Lock further operations to one process
mkdir -p /tmp/foobar-$user_id_num
lockfile -r 0 "$lock_file" || alertRunningPS

# Do stuff here
echo -n $ps_id > "$pid_file"
echo "Running stuff in ONE ps"

sleep 30s

rm -f "$lock_file"
rm -f "$pid_file"
exit 0

لقد وجدت طريقة بسيطة جدا للتعامل مع "نسخة واحدة من البرنامج النصي لكل نظام". لا يسمح لي بتشغيل نسخ متعددة من البرنامج النصي من العديد من الحسابات على الرغم من (على Linux القياسي الذي هو).

المحلول:

في بداية البرنامج النصي، أعطيت:

pidof -s -o '%PPID' -x $( basename $0 ) > /dev/null 2>&1 && exit

فيما يبدو Pidof. يعمل بشكل رائع بطريقة ما يلي:

  • ليس لديه حد اسم البرنامج مثل ps -C ...
  • لا يتطلب مني أن أفعل grep -v grep (أو أي شيء مماثل)

وهذا لا يعتمد على Lockfiles، والتي بالنسبة لي فوز كبير، لأن الترحيل عليهم يعني أن عليك إضافة التعامل مع Lockfiles لا معنى له - وهو أمر غير معقد حقا، ولكن إذا كان يمكن تجنبه - لماذا لا؟

أما بالنسبة للتحقق من "نسخة واحدة من النصوص لكل مستخدم"، فقد كتبت هذا، لكنني لست سعيدا للغاية به:

(
    pidof -s -o '%PPID' -x $( basename $0 ) | tr ' ' '\n'
    ps xo pid= | tr -cd '[0-9\n]'
) | sort | uniq -d

ثم تحقق من إخراجها - إذا كان فارغا - فلا توجد نسخ من البرنامج النصي من نفس المستخدم.

من مع النص الخاص بك:

ps -ef | grep $0 | grep $(whoami)

إليك بت قياسي لدينا. يمكن أن يتعافى من البرنامج النصي يموت بطريقة أو بأخرى دون تنظيف lockfile.

يكتب معرف العملية إلى ملف القفل إذا كان يعمل بشكل طبيعي. إذا وجدت ملف قفل عند بدء تشغيله، فسوف يقرأ معرف العملية من ملف القفل وتحقق مما إذا كانت هذه العملية موجودة. إذا كانت العملية غير موجودة، فسوف تقوم بإزالة ملف القفل الذي لا معنى له واستمرار. وفقط إذا كان ملف القفل موجودا والعملية لا تزال قيد التشغيل. ويكتب رسالة عند خروجها.

# lock to ensure we don't get two copies of the same job
script_name="myscript.sh"
lock="/var/run/${script_name}.pid"
if [[ -e "${lock}" ]]; then
    pid=$(cat ${lock})
    if [[ -e /proc/${pid} ]]; then
        echo "${script_name}: Process ${pid} is still running, exiting."
        exit 1
    else
        # Clean up previous lock file
        rm -f ${lock}
   fi
fi
trap "rm -f ${lock}; exit $?" INT TERM EXIT
# write $$ (PID) to the lock file
echo "$$" > ${lock}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top