ما هي أفضل طريقة لضمان تشغيل مثيل واحد فقط من البرنامج النصي باش؟ [مكرر
سؤال
هذا السؤال لديه بالفعل إجابة هنا:
ما هي أبسط / أفضل طريقة لضمان تشغيل مثيل واحد فقط من البرنامج النصي المعطى - على افتراض أنه باش على لينكس؟
في الوقت الحالي أفعل:
ps -C script.name.sh > /dev/null 2>&1 || ./script.name.sh
لكن لديها العديد من القضايا:
- يضع الشيك خارج البرنامج النصي
- لا يسمح لي بتشغيل نفس البرنامج النصي من حسابات منفصلة - وهو ما أود في بعض الأحيان.
-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" ستقوم بمزاياتين.
- تجاهل PIDS الذي يتم تحريره بمحرر مثل VIM، لأن VIM تحرير ملف التعيين الخاص به مثل ".file.swp".
- تجاهل 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}