Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

semaphore dans un shell-script

14 réponses
Avatar
Alain Ketterlin
Salut à tous,

J'aurais besoin d'utiliser un sémaphore dans shell-script (pour
contrôler le nombre de processus que je peux lancer). Quelqu'un a-t-il
déjà vu quelque chose de ce type (même si ce sont de bêtes coquilles
autour de sem_open et compagnie -- j'avoue que j'ai un eu la flemme de
les écrire moi-même) ?

J'ai bien envisagé d'utiliser une fifo pour ça, mais ça ne me convient
pas parce qu'un lecteur s'arrête quand il n'y a plus d'écrivain. Je
cherche quelque chose qui corresponde exactement au comportement d'un
sémaphore (POSIX suffit, je n'ai pas besoin des raffinements des IPC
SysV).

Merci d'avance.

-- Alain.

P/S: je suis sous Linux 2.6, mais si c'est portable c'est mieux.

10 réponses

1 2
Avatar
Cyrille Lefevre
Salut à tous,

J'aurais besoin d'utiliser un sémaphore dans shell-script (pour
contrôler le nombre de processus que je peux lancer). Quelqu'un a-t-il
déjà vu quelque chose de ce type (même si ce sont de bêtes coquilles
autour de sem_open et compagnie -- j'avoue que j'ai un eu la flemme de
les écrire moi-même) ?

J'ai bien envisagé d'utiliser une fifo pour ça, mais ça ne me convient
pas parce qu'un lecteur s'arrête quand il n'y a plus d'écrivain. Je
cherche quelque chose qui corresponde exactement au comportement d'un
sémaphore (POSIX suffit, je n'ai pas besoin des raffinements des IPC
SysV).


peux-tu donner plus de detail... sur ce que tu as besoin de faire.

pour ma part, je ne vois que la commande ln qui permet d'effectuer des
operations atomiques en shell. un truc du style (en ksh88) :

#!/bin/ksh
#
#ident @(#) lock.sh 1.3 (Cyrille.Lefevre at laposte.net) Aug 8 13:51:32
2003
#
# Copyright (c) 2002-2003 Cyrille Lefevre. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. The name of the authors and contributors may not be used to
# endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS''
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS
# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.

function usage {
print -u2 "usage: ${basename} [-fvx] [-l|-u] [-M modes] [-T temp_dir]
[-s sleeptime] [-t timeout] file..."
print -u2 "options:"
print -u2 " -f force mode."
print -u2 " -l lock files."
print -u2 " -u unlock files."
print -u2 " -M modes temp directory permissions."
print -u2 " -T temp_dir temp directory."
print -u2 " -s sleeptime time to sleep between retries."
print -u2 " -t timeout maximum time to lock files."
print -u2 " -v verbose mode."
print -u2 " -x trace mode."
exit 1
}

function error {
print -u2 ${basename}: ERROR: "$@"
}

function warning {
print -u2 ${basename}: WARNING:"$@"
}

function info {
print -u2 ${basename}: INFO: "$@"
}

function trace {
info "$@"
"$@"
}

integer nSIG2
SIGs[0]=EXIT
eval $(kill -l | sed 's/)//g;s/^ //;s/ {1,}/ /g' | tr ' ' ' 12' |
awk 'NR%2{n=$1;next}n<2{print "SIGs[" n "]=" $1}')
for sig in ${SIGs[@]}; do
eval unset ${sig}s
eval integer n${sig}=0
done

function signal {
typeset signame=$1
integer signo=0
typeset arg tmp

shift

while (( signo < nSIG )); do
[[ ${signame} = ${signo} ]] && signame=${SIGs[signo]}
[[ ${signame} = ${SIGs[signo]} ]] && break
(( signo += 1 ))
done

(( signo == nSIG )) && return 1

for arg; do
tmp=$(print -- ${arg} | sed 's/$/&/g')
eval ${signame}s[n${signame}]="${${signame}s[n${signame}]}
"$tmp""
done

eval "(( n${signame} += 1 ))"
}

function atexit {
signal EXIT "$@"
}

function ontrap {
integer signo=$?

(( signo > 128 )) && (( signo -= 128 ))
(( signo > nSIG )) && return 1

typeset signame=${SIGs[signo]}
eval integer trapno=n${signame}-1

[[ -z ${signame} ]] && return 0

while (( trapno >= 0 )); do
eval eval eval "${${signame}s[trapno]}"
(( trapno -= 1 ))
done
}

function exit_on_trap {
(( rc )) || rc=signo

exit ${rc}
}

function lock_exit {
typeset file

eval set -- ${files}

for file; do
rm -f "${file}.${pid}"
done
}

function lock {
typeset file
integer time0=SECONDS

atexit lock_exit

eval set -- ${files}

for file; do
print -- "$@" > "${file}.${pid}"
done

for lastfile; do
while ! ln ${force} "${lastfile}.${pid}"
"${lastfile}.lck" > /dev/null 2>&1; do
if (( timeout && ( SECONDS - time0 ) > timeout )); then
rc=1

atexit unlock_exit

break 2
fi

sleep ${sleeptime}
done
done
}

function unlock_exit {
typeset file

eval set -- ${files}

for file; do
[[ ${file} = ${lastfile} ]] && break

rm -f "${file}.lck"
done
}

function unlock {
lastfile
atexit unlock_exit
}

function main {
typeset action=lock
typeset force integer modes77
integer sleeptime
integer timeout=0
integer verbose=0
integer trace=0

case ${basename} in
unlock)
action=unlock
;;
esac

while getopts fluM:T:s:t:vx c; do
case ${c} in
f) force=-f ;;
+f) force= ;;
l) action=lock ;;
u) action=unlock ;;
M) modes=${OPTARG} ;;
T) temp_dir=${OPTARG} ;;
s) sleeptime=${OPTARG} ;;
t) timeout=${OPTARG} ;;
v) verbose=1 ;;
+v) verbose=0 ;;
x) trace=1 ;;
+x) trace=0 ;;
*) usage ;;
esac
done
shift OPTIND-1

if (( $# < 1 )); then
usage
fi

if (( trace )) && [[ ! -d ${temp_dir} || ! -w ${temp_dir} ]]; then
warning "${temp_dir}: no such file or directory."
trace=0 # exit 1
fi

if (( trace )); then
log_dir=${temp_dir}/${date}/${basename}
log_file=${log_dir}/${basename}_${now}.log
fi

trap ontrap EXIT HUP INT QUIT TERM

signal HUP exit_on_trap
signal INT exit_on_trap
signal QUIT exit_on_trap
signal TERM exit_on_trap

if (( trace )); then
atexit 'cat -s "${log_file}" >&4'

mkdir -p -m ${modes} ${log_dir}
exec 3>&1 4>&2 > "${log_file}" 2>&1
info "${log_file}"
fi

if (( verbose )); then
# typeset -ft signal
typeset -ft ontrap
typeset -ft exit_on_trap
typeset -ft lock
typeset -ft lock_exit
typeset -ft unlock
typeset -ft unlock_exit

set -x
fi

lastfile=$1
for file; do
files="${files} "${file}""
done

signal HUP unlock_exit
signal INT unlock_exit
signal QUIT unlock_exit
signal TERM unlock_exit

eval ${action}

exit $rc
}

typeset dirname=${0%/*}
typeset basename=${0##*/}
typeset basename=${basename%.*sh}

integer pid=$$
typeset now=$(date +%Y%m%d_%H%M%S)
typeset date=$(date +%Y/%m/%d)

typeset temp_dir=${LOGDIR:-${TMPDIR:-${TMP:-/var/tmp}}}
typeset log_dir typeset log_file
typeset files typeset lastfile integer rc=0

main ${1+"$@"}

# eof

Regards, Cordialement,

Cyrille Lefevre.
--
mailto:Cyrille.Lefevre-news%
supprimer "%nospam% et ".invalid" pour me repondre.
remove "%nospam" and ".invalid" to answer me.

Avatar
Luc.Habert.00__arjf
Cyrille Lefevre :

pour ma part, je ne vois que la commande ln qui permet d'effectuer des
operations atomiques en shell


Attention, les implémentations usuelles de l'option « -f » font un unlink
plutôt qu'un link vers un nom temporaire suivi d'un rename.

Avatar
Cyrille Lefevre
Cyrille Lefevre :

pour ma part, je ne vois que la commande ln qui permet d'effectuer des
operations atomiques en shell


Attention, les implémentations usuelles de l'option « -f » font un unlink
plutôt qu'un link vers un nom temporaire suivi d'un rename.


on s'en fout, du moment que link ou rename soient atomiques :)

Regards, Cordialement,

Cyrille Lefevre.
--
mailto:Cyrille.Lefevre-news%
supprimer "%nospam% et ".invalid" pour me repondre.
remove "%nospam" and ".invalid" to answer me.


Avatar
Alain Ketterlin
Cyrille Lefevre <cyrille.lefevre-news%
writes:

J'aurais besoin d'utiliser un sémaphore dans shell-script


peux-tu donner plus de detail... sur ce que tu as besoin de faire.


En fait, wait et post simplement (P et V pour ceux qui prefèrent).
Mon problème n'est pas un problème d'exclusion mutuelle, mais un
problème d'affectation d'un nombre limité de ressources.

L'idée c'est de contrôler un script qui lance (potentiellement) des
tas de processus (une sorte de producteurs/consommateurs, en fait).
Idéalement, chacun de ces processus ferait :

sem_wait /mon/semaphore
... # travail
sem_post /mon/semaphore

Donc je pourrais les lancer tous en même temps (ou faire les wait/post
avant/après le lancement), tout en étant sûr qu'il n'y en a jamais
plus de N qui bossent. D'autre part, je pourrais ("de l'extérieur")
incrémenter le sémaphore pour avoir plus de processus concurrents, ou
l'inverse (jusqu'à empêcher temporairement le déclenchement de
nouveaux processus -- je ne suis pas seul sur la machine).

(Au passage, l'idée de la fifo, c'était que sem_wait lit une ligne
alors que sem_post écrit une ligne. Mais c'est trop tiré par les
cheveux.)

Si ca n'existe pas, je pense que je vais me taper l'écriture d'un
petit wrapper autour de sem_open/sem_wait/sem_post/... La gestion des
semaphores nommés me conviendrait bien. Je voulais juste vérifier si
ça existe déjà.

pour ma part, je ne vois que la commande ln qui permet d'effectuer des
operations atomiques en shell. un truc du style (en ksh88) :
[...]


Ah ouais, merci. Je ne suis pas sûr d'avoir besoin de quelque chose
d'aussi élaboré au niveau de la gestion des signaux. J'avais dans le
temps utilisé le lockfile de procmail, qui jouait un rôle similaire.

-- Alain.


Avatar
Stephane Chazelas
2007-07-10, 23:12(+02), Cyrille Lefevre:
Salut à tous,

J'aurais besoin d'utiliser un sémaphore dans shell-script (pour
contrôler le nombre de processus que je peux lancer). Quelqu'un a-t-il
déjà vu quelque chose de ce type (même si ce sont de bêtes coquilles
autour de sem_open et compagnie -- j'avoue que j'ai un eu la flemme de
les écrire moi-même) ?

J'ai bien envisagé d'utiliser une fifo pour ça, mais ça ne me convient
pas parce qu'un lecteur s'arrête quand il n'y a plus d'écrivain. Je
cherche quelque chose qui corresponde exactement au comportement d'un
sémaphore (POSIX suffit, je n'ai pas besoin des raffinements des IPC
SysV).


peux-tu donner plus de detail... sur ce que tu as besoin de faire.

pour ma part, je ne vois que la commande ln qui permet d'effectuer des
operations atomiques en shell. un truc du style (en ksh88) :
[...]


mkdir aussi est censé etre atomique.

Tu peut peut-etre faire des trucs du genre:
(semaphore a 3 tokens):

# pour acquerir
if mkdir "$SEM_DIR/x" ||
mkdir "$SEM_DIR/x/x" ||
mkdir "$SEM_DIR/x/x/x"; then
echo lock acquired
else
exit 1
fi

# pour relacher:
until rmdir "$SEM_DIR/x/x/x" ||
rmdir "$SEM_DIR/x/x" ||
rmdir "$SEM_DIR/x"; do :; done


--
Stéphane


Avatar
Cyrille Lefevre
Ah ouais, merci. Je ne suis pas sûr d'avoir besoin de quelque chose
d'aussi élaboré au niveau de la gestion des signaux. J'avais dans le
temps utilisé le lockfile de procmail, qui jouait un rôle similaire.


en ce qui concerne la gestion des signaux, à l'époque ou j'ai écrit ça,
il s'agissait plus de l'exercice de style... :)
il est bien évidement possible de faire beaucoup plus simple.
ce qu'il faut retenir, c'est la boucle lock() et son pendant unlock_exit().
Ps : l'important est de faire les locks de plusieurs fichiers dans le
même sens, c-a-d : proc1 => lock 1 2 3; proc2 => lock 1 2 3
et surtout pas proc1 => lock 1 2 3, proc2 => 3 2 1, sinon deadlock
si mes souvenirs sont bons.

Regards, Cordialement,

Cyrille Lefevre.
--
mailto:Cyrille.Lefevre-news%
supprimer "%nospam% et ".invalid" pour me repondre.
remove "%nospam" and ".invalid" to answer me.

Avatar
ALain Montfranc
Cyrille Lefevre a écrit
Cyrille Lefevre :

pour ma part, je ne vois que la commande ln qui permet d'effectuer des
operations atomiques en shell


Attention, les implémentations usuelles de l'option « -f » font un unlink
plutôt qu'un link vers un nom temporaire suivi d'un rename.


on s'en fout, du moment que link ou rename soient atomiques :)



Pour rename, j'ai un doute

J'avais jadis fait un systeme de lock basé sur mv

(si "mv toto tutu" reussi, j'ai le lock sinon j'attend. Si j'ai le
lock, je fait monn taf puis je fais "mv tutu toto")

Ben de temps en temps, sur un nunux, le fichier disparaissait !



Avatar
Nicolas George
ALain Montfranc wrote in message :
(si "mv toto tutu" reussi, j'ai le lock sinon j'attend.


Ça veut dire quoi, que le mv réussit ? Et surtout, dans quelles conditions
échoue-t-il, selon toi ? Si un fichier tutu est déjà présent, il sera
remplacé par le mv : ça ne peut pas servir de verrou.

Avatar
Cyrille Lefevre
Cyrille Lefevre a écrit
Cyrille Lefevre :
[sip]



on s'en fout, du moment que link ou rename soient atomiques :)


Pour rename, j'ai un doute

J'avais jadis fait un systeme de lock basé sur mv

(si "mv toto tutu" reussi, j'ai le lock sinon j'attend. Si j'ai le lock,
je fait monn taf puis je fais "mv tutu toto")

Ben de temps en temps, sur un nunux, le fichier disparaissait !


effectivement, rien ne dit que mv utilise bien rename comme appel
système sous-jacent. d'autant que cet appel système est "assez
récent", d'ou une implémentation de mv via unlink dest/link src
dest/unlink src ou cp si dest est sur un fs différent. aujourd'hui,
ce serait plutôt unlink dest si -f, rename src dest.

pour autant, j'ai un doute sur le fait qu'un rename puisse traverser
un fs à la cp ?

la méthode ln (link) est fiable depuis UUCP :)

Regards, Cordialement,

Cyrille Lefevre.
--
mailto:Cyrille.Lefevre-news%
supprimer "%nospam% et ".invalid" pour me repondre.
remove "%nospam" and ".invalid" to answer me.



Avatar
ALain Montfranc
Nicolas George a écrit
ALain Montfranc wrote in message :
(si "mv toto tutu" reussi, j'ai le lock sinon j'attend.


Ça veut dire quoi, que le mv réussit ? Et surtout, dans quelles conditions


$? = 0

échoue-t-il, selon toi ?


si le fichier source n'existe pas

Si un fichier tutu est déjà présent, il sera
remplacé par le mv : ça ne peut pas servir de verrou.


Je me fous du fichier cible, le fichier source me suffit


1 2