Contenu | Rechercher | Menus

Annonce

Si vous avez des soucis pour rester connecté, déconnectez-vous puis reconnectez-vous depuis ce lien en cochant la case
Me connecter automatiquement lors de mes prochaines visites.

À propos de l'équipe du forum.

#1 Le 26/12/2024, à 01:50

Totor

[Script/bash] Sortie standard & d'erreur dans des variables distinctes

Hello,

Suite à la demande d'un collègue, je partage ici une fonction permettant de récupérer la sortie standard et la sortie d'erreur d'une commande dans des variables distinctes.

#!/bin/bash

function fReadStdAndErr() {
        declare -r nullChar=$'\0'
        function myRead() {
                local _myLine=""
                local _fd=$1
                local _endChar="$2"
                while read -u ${_fd}
                do
                        [[ ${REPLY} == ${_endChar} ]] && break
                        _myLine="$(printf "${_myLine}\n${REPLY}")"
                done
                echo "${_myLine/$'\n'}"
        }

        # Instruction à exécuter (avec paramètres)
        local _cmd="$1"
        # Nom de la variable dans laquelle mettre le contenu de la sortie standard
        local _varStd="$2"
        # Nom de la variable dans laquelle mettre le contenu de la sortie d'erreur
        local _varErr="$3"

        coproc MY_COPROC_READ {
                # exécution de la commande avec récupération de la sortie d'erreur+\0+status dans la variable "err"
                # une fois la commande exécutée, on envoie le caractère de \0 afin de signaler à la méthode "myRead" que c'est tout bon pour la sortie standard
                { local err="$( eval ${_cmd} 2>&1 >&3; local _status=$?; echo ${nullChar}; echo ${_status})"; echo "${nullChar}"; } 3>&1

                # On attend que quelque chose arrive en entrée standard pour envoyer le contenu de la variable "err" + nullChar
                read
                echo "${err}"
                echo "${nullChar}"

                # On attend que quelque chose arrive en entrée standard (donc fin de récupération de la variable "err + status") pour terminer le coproc
                read
        }

        # On récupère le contenu de la sortie standard du coproc (ie. sortie standard de la commande)
        local _std="$(myRead ${MY_COPROC_READ[0]} "${nullChar}")"
        # on indique au coproc que l'on a fini de lire la sortie standard et qu'il peut envoyer le contenu de la variable err qui contient la sortie d'erreur + nullChar + status
        echo >&${MY_COPROC_READ[1]}

        # Récupération de la sortie d'erreur
        local _err="$(myRead ${MY_COPROC_READ[0]} "${nullChar}")"

        # récupération du status de fin
        local _st="$(myRead ${MY_COPROC_READ[0]} "${nullChar}")"

        # On indique au coproc que l'on a tout récupéré
        echo >&${MY_COPROC_READ[1]}

        # on valorise les 2 variables fournies en paramètres
        eval "${_varStd}=$'$(cat <<QUOTE
${_std//"'"/"\'"}
QUOTE
)'"
        eval "${_varErr}=$'$(cat <<QUOTE
${_err//"'"/"\'"}
QUOTE
)'"
        return ${_st}
}
Utilisation a écrit :

fReadStdAndErr "Commande à exécuter (avec ses arguments)" nomVariableDestinationSortieStandard nomVariableDestinationSortieErreur

Note : la foncton fReadStdAndErr retourne le status de la commande à exécuter

Même si je ne suis pas fan, "eval" a été utilisé pour :
- exécuter la commande (il est possible de s'en passer mais cela est limitant --> la 2nde forme de l'exemple ci-dessous ne serait pas possible)
- définir la valeur des variables contenant les sorties standard et d'erreur

Exemples d'utilisation :

cmd='ls -ld b* _W* D*'
fReadStdAndErr "${cmd}" standard erreur
status=$?
echo "cmd : ${cmd}"
echo "Standard : ${standard}"
echo "Erreur : ${erreur}"
echo "Status : ${status}"
echo
echo "-------------------------------"


cmd="{ echo erreur >&2 ; echo standard; }"
fReadStdAndErr "${cmd}" standard erreur
status=$?
echo "cmd : ${cmd}"
echo "Standard : ${standard}"
echo "Erreur : ${erreur}"
echo "Status : ${status}"

-- Lucid Lynx --

Hors ligne

#2 Le 26/12/2024, à 03:42

Watael

Re : [Script/bash] Sortie standard & d'erreur dans des variables distinctes

d'un point de vue formel :

function et () sont redondants (c'est l'un ou l'autre)
il n'y a pas de gain à déclarer une fonction dans une autre fonction
une substitution de commandes ($(...)) s'exécutant dans un sous-shell, les variables qui y sont assignées lui sont forcément "locales"
tu pourrais déclarer toutes les variables au début de la fonction, avant leur utilisation; si toutes les variables de la fonction sont locales, alors la fonction peut être exécutée dans une sous-shell (foncLocaleVar() ( ...))

le script n'appelle pas la fonction, alors comment est-elle exécutée ?

ton exemple ne montre pas le fonctionnement du script :
comment on lui passe la commande
la disponibilité des variables dont le nom est passé en arguments

je me serais contenté, pour faire simple (KISS), de rediriger stdout et stderr vers des fichiers et de lire ces fichiers dans des variables...
c'est lisible, et il n'y a pas de temps de traitements supplémentaire

Dernière modification par Watael (Le 26/12/2024, à 03:45)


Connected \o/
Welcome to sHell. · eval is evil.

Hors ligne

#3 Le 26/12/2024, à 06:58

Tawal

Re : [Script/bash] Sortie standard & d'erreur dans des variables distinctes

Hello.

Ou si on ne veut pas d'écriture sur le disque, on peut utiliser des fifos (un par sortie).


Le savoir n'a d’intérêt que si on le transmet.
Useless Use of Cat Award
Filenames and Pathnames in Shell: How to do it Correctly
À chaque problème sa solution, à chaque solution son moyen, si pas de moyen, toujours le problème !

Hors ligne

#4 Le 27/12/2024, à 11:09

Totor

Re : [Script/bash] Sortie standard & d'erreur dans des variables distinctes

Watael a écrit :

d'un point de vue formel :

function et () sont redondants (c'est l'un ou l'autre)
il n'y a pas de gain à déclarer une fonction dans une autre fonction
une substitution de commandes ($(...)) s'exécutant dans un sous-shell, les variables qui y sont assignées lui sont forcément "locales"
tu pourrais déclarer toutes les variables au début de la fonction, avant leur utilisation; si toutes les variables de la fonction sont locales, alors la fonction peut être exécutée dans une sous-shell (foncLocaleVar() ( ...))

le script n'appelle pas la fonction, alors comment est-elle exécutée ?

ton exemple ne montre pas le fonctionnement du script :
comment on lui passe la commande
la disponibilité des variables dont le nom est passé en arguments

je me serais contenté, pour faire simple (KISS), de rediriger stdout et stderr vers des fichiers et de lire ces fichiers dans des variables...
c'est lisible, et il n'y a pas de temps de traitements supplémentaire

C'est bien, je suis content, ça me fait de belles jambes...

Tawal a écrit :

Hello.

Ou si on ne veut pas d'écriture sur le disque, on peut utiliser des fifos (un par sortie).

Hello,

Oui, tout à fait. c'est également une piste que j'avais envisagé mais elle implique la même problématique que les fichiers (que je n'aime pas): il faut gérer la création / suppression afin de laisser le système dans le même état qu'avant l'exécution de la fonction (je pense particulièrement à un arrêt non maitrisé du script durant l'exécution de la commande). Alors que là, c'est le coproc qui s'en charge.
Mais quoi qu'il en soit, l'utilisation de fifo te fera retomber dans une gestion asynchrone de lecture des flux. Il te faudra également récupérer le statut de la commande.
Au final, je pense que ce cela reviendra au même. Cela reste une question d'appréciation / préférence de codage.
Merci de l'avoir souligné.


-- Lucid Lynx --

Hors ligne

#5 Le 27/12/2024, à 16:08

Tawal

Re : [Script/bash] Sortie standard & d'erreur dans des variables distinctes

Une solution en utilisant les fifos (pas de gestion de création/suppression : aussitôt créé, aussitôt supprimé)

get_sdt_err_status()
{
    local -n std="$2" err="$3" stat="$4"

    standard="$(mktemp -up /tmp sdtXXXXXXXX)"
    mkfifo $standard
    exec 8<>$standard 4<$standard 5>$standard 8>&-
    rm $standard

    erreur="$(mktemp -up /tmp errXXXXXXXX)"
    mkfifo $erreur
    exec 8<>$erreur 6<$erreur 7>$erreur 8>&-
    rm $erreur

    1>&5 2>&7 eval "$1"
    stat=$?
    >&5 echo "EOF"
    >&7 echo "EOF"

    while read -r e <&6
    do
        [ "$e" = "EOF" ] && break
        printf -v tmp "%s\n" "$e"
        err+="$tmp"
    done

    while read -r s <&4
    do
        [ "$s" = "EOF" ] && break
        printf -v tmp "%s\n" "$s"
        std+="$tmp"
    done

    exec 4>&- 5>&- 6>&- 7>&-
}



# Exemple d'utilisation :
cmd="ls -l t* zzz yyy"

get_sdt_err_status "$cmd" norm rate code

echo "N= $norm"
echo "E= $rate"
echo "C= $code"

Dernière modification par Tawal (Le 27/12/2024, à 17:04)


Le savoir n'a d’intérêt que si on le transmet.
Useless Use of Cat Award
Filenames and Pathnames in Shell: How to do it Correctly
À chaque problème sa solution, à chaque solution son moyen, si pas de moyen, toujours le problème !

Hors ligne

#6 Le 27/12/2024, à 16:44

Watael

Re : [Script/bash] Sortie standard & d'erreur dans des variables distinctes

tu ne voulais pas faire mkfifo $standard ?
sinon, à quoi sert le mktemp ?


Connected \o/
Welcome to sHell. · eval is evil.

Hors ligne

#7 Le 27/12/2024, à 17:03

Tawal

Re : [Script/bash] Sortie standard & d'erreur dans des variables distinctes

Euh si !
Je corrige wink
Merci Debian Facile

Edit:
@Totor: Tu devrais utiliser les "belles jambes" que t'a fourni Watael roll

Dernière modification par Tawal (Le 27/12/2024, à 17:20)


Le savoir n'a d’intérêt que si on le transmet.
Useless Use of Cat Award
Filenames and Pathnames in Shell: How to do it Correctly
À chaque problème sa solution, à chaque solution son moyen, si pas de moyen, toujours le problème !

Hors ligne