Comment envoyer toutes les sorties à `logger` dans le shell POSIX?


10

Je voudrais enregistrer la sortie standard et l'erreur standard séparément lors de l' .xprofileutilisation logger. Dans Bash, je pense que cela ressemblerait à ceci:

exec 1> >(logger --priority user.notice --tag $(basename $0)) \
     2> >(logger --priority user.error --tag $(basename $0))

Comment pourrais-je faire cela d'une manière compatible POSIX /bin/sh ?

Réponses:


10

Il n'y a pas d'équivalent POSIX. Vous pouvez uniquement effectuer une redirection avec exec, pas un fork. Une pipe nécessite une fourchette et la coque attend que l'enfant ait fini.

Une solution consiste à mettre tout votre code dans une fonction.

all_my_code () {
  
}
{ all_my_code |
  logger --priority user.notice --tag "$(basename "$0")"; } 2>&1 | 
  logger --priority user.error --tag "$(basename "$0")"

(Cela enregistre également toute erreur de l'instance stdout de l'enregistreur vers l'instance stderr. Vous pouvez éviter cela en mélangeant davantage les descripteurs de fichiers.)

Si vous souhaitez que le shell parent se termine même si les loggerprocessus sont toujours en cours d'exécution, placez-les &à la fin des loggerinvocations.

{ all_my_code |
  logger --priority user.notice --tag "$(basename "$0")" & } 2>&1 | 
  logger --priority user.error --tag "$(basename "$0")" &

Vous pouvez également utiliser des canaux nommés.

pipe_dir=$(mktemp -d)
mkfifo "$pipe_dir/out" "$pipe_dir/err"
<"$pipe_dir/out" logger --priority user.notice --tag "$(basename "$0")" &
<"$pipe_dir/err" logger --priority user.error --tag "$(basename "$0")" &
exec >"$pipe_dir/out" 2>"$pipe_dir/err" 

rm -r "$pipe_dir"

Cela apparaîtra dans syslog sous la forme de deux entrées (out & err) pour l'ensemble du script. L'exemple de la question aura une (ou deux) entrée par commande.
DarkHeart

@DarkHeart Je ne comprends pas votre commentaire. Le code dans la question et les deux solutions que je propose exécutent le même nombre d'instances loggeret transmettent la même entrée à chacune d'elles.
Gilles 'SO- arrête d'être méchant'

désolé, mon erreur
DarkHeart

1
Cela semble plus simple , donc je vais avec votre solution nommée de tuyaux.
l0b0

9

Substitution de commandes / processus POSIX


_log()( x=0
    while  [ -e "${TMPDIR:=/tmp}/$$.$((x+=1))" ]
    do     continue; done        &&
    mkfifo -- "$TMPDIR/$$.$x"    &&
    printf %s\\n "$TMPDIR/$$.$x" || exit
    exec >&- >/dev/null
    {  rm -- "$TMPDIR/$$.$x"
       logger --priority user."$1" --tag "${0##*/}"
    }  <"$TMPDIR/$$.$x" &
)   <&- </dev/null

Vous devriez pouvoir l'utiliser comme:

exec >"$(_log notice)" 2>"$(_log error)"

Voici une version qui utilise la mktempcommande:

_log()( p=
    mkfifo "${p:=$(mktemp -u)}"    &&
    printf %s "$p"                 &&
    exec  <&- >&- <>/dev/null >&0  &&
    {   rm "$p"
        logger --priority user."$1" --tag "${0##*/}"
    }   <"$p" &
)

... qui fait à peu près la même chose, sauf qu'il permet mktempde sélectionner le nom de fichier pour vous. Cela fonctionne parce que la substitution de processus n'est en aucun cas magique et fonctionne de manière très similaire à la substitution de commandes . Au lieu de remplacer l'expansion avec la valeur de la commande exécuter en son sein comme une substitution de commande fait, la substitution de processus remplace par le nom d'un lien de système de fichiers où la sortie se trouve.

Bien que le shell POSIX ne fournisse pas de corollaire direct à une telle chose, l'émuler se fait très simplement. Tout ce que vous avez à faire est de créer un fichier, d'imprimer son nom dans la norme à partir d'une substitution de commande et, en arrière-plan, d'exécuter votre commande qui sortira dans ce fichier. Maintenant, vous pouvez simplement rediriger vers la valeur de cette expansion - exactement comme vous le faites avec la substitution de processus. Et donc le shell POSIX fournit tous les outils dont vous avez besoin bien sûr - tout ce qui est requis est que vous les utilisiez d'une manière qui vous convient.

Les deux versions ci-dessus garantissent qu'elles détruisent le lien du système de fichiers vers les canaux qu'elles créent / utilisent avant de les utiliser. Cela signifie qu'aucun nettoyage n'est requis après coup, et, plus important encore, leurs flux ne sont disponibles que pour les processus qui les ouvrent initialement - et donc leurs liens de système de fichiers ne peuvent pas être utilisés comme un moyen pour espionner / détourner votre activité de journalisation. Laisser leurs liens fs dans le système de fichiers est une faille de sécurité potentielle.


Une autre façon est de l'envelopper. Cela peut être fait à partir du script.

x=${x##*[!0-9]*}
_log(){ 
    logger --priority user."$1" --tag "${0##*/}"
}   2>/dev/null >&2

cd ../"$PPID.$x" 2>/dev/null &&
trap 'rm -rf -- "${TMPDIR:-/tmp}/$PPID.$x"' 0 || 
{   until cd -- "${TMPDIR:=/tmp}/$$.$x"
    do    mkdir -- "$TMPDIR/$$.$((x+=1))"
    done  && 
    x=$x "$0" "$@" | _log notice
    exit
}   2>&1 | _log error

Cela permettrait essentiellement à votre script de s'appeler s'il ne l'a pas encore fait et de vous obtenir un répertoire de travail dans temp pour démarrer.


1
Terminé , merci!
l0b0

@ l0b0 - Je suis sur le point de mettre à jour un autre type de ce genre ... C'est un objectif plus général, et j'ai finalement réussi à lui donner la possibilité de gérer certaines options liées aux descripteurs / fichiers d' E / S.
mikeserv

@ l0b0 - si vous vouliez utiliser mktempet d' autres commandes non standard, il pourrait être: _log()( p=; mkfifo "${p:=$(mktemp -u)}"; printf %s "$p"; exec <&- >&- <>/dev/null >&0; { rm "$p"; logger --priority user."$1" --tag "${0##*/}"; } <"$p" &). Je l'ai écrit en utilisant un langage de commande entièrement portable de toutes les manières - à l'exception du vôtre. Ce basenamen'est pas non plus un utilitaire très utile. "${0##*/}"est à la fois plus robuste et plus rapide.
mikeserv

Je l'ai changé car je n'en ai besoin que pour travailler sur des plateformes modernes; la question principale était que .xprofile doit être exécuté avec sh.
l0b0

1
@Wildcard - juste une minute et je vais essayer de trier la première partie de la question, mais la réponse à la seconde est oui: le cas de bord est un zshw / multios activé. En ce qui concerne la première partie: { this; can\'t; happen; before; } < this. Est ce que ça aide?
mikeserv
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.