Une manière élégante de construire un pipeline basé sur la valeur de retour et non sur le code de sortie?


8

Lorsque le code d'état est inutile, existe-t-il de toute façon de construire un pipeline basé sur la sortie de stdout?

Je préférerais que la réponse ne traite pas du cas d'utilisation, mais de la question relative à la portée des scripts shell. Ce que j'essaie de faire, c'est de trouver le package le plus spécifique disponible dans le référentiel en devinant le nom en fonction des codes de pays et de langue.

Prenez par exemple ceci,

  • $PACKAGE1=hunspell-en-zz
  • $PACKAGE2=hunspell-en

La première supposition est plus appropriée mais elle peut ne pas exister. Dans ce cas, je veux retourner hunspell-en( $PACKAGE2) car la première option hunspell-en-zz( $PACKAGE1) n'existe pas .

pipelines d'apt-cache

La commande apt-cacherenvoie succès (qui est défini par shell comme code de sortie zéro) chaque fois que la commande est en mesure de s'exécuter (à partir des documents de apt-cache)

apt-cache renvoie zéro en fonctionnement normal, 100 décimal en cas d'erreur.

Cela rend l'utilisation de la commande dans un pipeline plus difficile. Normalement, je m'attends à ce que l'équivalent de recherche de package d'un 404 entraîne une erreur (comme cela se produirait avec curlou wget). Je veux rechercher pour voir si un paquet existe, et sinon, revenir à un autre paquet s'il existe .

Cela ne renvoie rien, car la première commande retourne le succès (donc le rhs dans le ||ne s'exécute jamais)

apt-cache search hunspell-en-zz || apt-cache search hunspell-en

apt-cache search avec deux arguments

Cela ne renvoie rien, comme apt-cacheANDs ses arguments,

apt-cache search hunspell-en-zz hunspell-en

D'après les documents de apt-cache

Des arguments séparés peuvent être utilisés pour spécifier plusieurs modèles de recherche qui sont and'és ensemble.

Donc, comme l'un de ces arguments n'existe clairement pas, cela ne renvoie rien.

La question

Quel est l'idiome du shell pour gérer des conventions comme celles trouvées apt-cachelà où le code de retour est inutile pour la tâche? Et le succès n'est déterminé que par la présence de sortie sur STDOUT?

Semblable à

  • faire échouer quand rien n'a été trouvé

    ils découlent tous deux du même problème. La réponse choisie y mentionne find -zce qui n'est malheureusement pas applicable ici et est spécifique au cas d'utilisation. Il n'y a aucune mention d'un idiome ou de la construction d'un pipeline sans utiliser la terminaison nulle (pas une option sur apt-cache)


Êtes-vous sûr que cela hunspell-enexiste? Quoi qu'il en soit, vous pouvez utiliser apt-cache policyet grep for ^$PACKAGENAME:.
AlexP

@AlexP ce ne sont que des exemples où hunspell-en n'existe pas car ils regroupent avec des noms de pays, hunspell-arexiste et il n'y a pas de packages de noms de pays. J'ai besoin de trouver le package le plus précis pour un pays et une langue donnés.
Evan Carroll

2
findest comme apt-cacheà cet égard - code retour inutile, le succès est basé sur la sortie.
muru

1
Oui, je suis d'accord, ils découlent tous les deux du même problème. La réponse choisie y mentionne -zce qui n'est malheureusement pas une solution ici, donc le problème spécifique au cas d'utilisation n'est pas applicable. Et il n'y a aucune mention d'un idiome ou de la construction d'un pipeline sans utiliser la terminaison nulle (pas une option sur apt-cache)
Evan Carroll

1
@EvanCarroll la terminaison nulle est entièrement facultative. Je ne l'ai utilisé que parce que c'est le moyen le plus sûr de gérer les noms de fichiers, donc on s'attendrait findà être utilisé avec -print0et donc à utiliser -z. Puisque apt-cache ne donne pas de sortie terminée par null, vous n'en avez pas besoin -z.
muru

Réponses:


5

Créez une fonction qui prend une commande et renvoie true si elle a une sortie.

r() { local x=$("$@"); [ -n "$x" ] && echo "$x"; }

( ( r echo -n ) || echo 'nada' ) | cat      # Prints 'nada'
( ( r echo -n foo ) || echo 'nada' ) | cat  # Prints 'foo'

Donc, pour ce cas d'utilisation, cela fonctionnera comme ceci,

r apt-cache search hunspell-en-zz || r apt-cache search hunspell-en

Notez que r printf '\n\n\n'cela retournerait faux. Avec des obus autres que zsh, r printf '\0\0\0'reviendrait également faux. Il en serait de même r printf '\0a\0b\0c'pour certains obus.
Stéphane Chazelas

3

Pour autant que je sache, il n'existe aucun moyen standard de traiter les cas où le succès d'une commande est déterminé par la présence de sortie. Vous pouvez cependant écrire des solutions de contournement.

Par exemple, vous pouvez enregistrer la sortie de la commande dans une variable, puis vérifier si cette variable est vide ou non:

output="$(command)"

if [[ -n "${output}" ]]; then
  # Code to execute if command succeded
else
  # Code to execute if command failed
fi

Je pense que cela répond à la question d'une manière générale, mais si nous parlons de apt-cache searchcertaines solutions me viennent à l'esprit.

J'ai un script qui facilite la gestion des packages. Certaines de ses fonctions sont les suivantes:

search() {
  local 'package' 'packages'
  packages="$( apt-cache search '.*' | cut -d ' ' -f '1' | sort )"
  for package; do
    grep -F -i -e "${package}" <<< "${packages}"
  done
}


search_all() {
  local 'package'
  for package; do
    apt-cache search "${package}" | sort
  done
}


search_description() {
  local 'package' 'packages'
  packages="$( apt-cache search '.*' | sort )"
  for package; do
    grep -F -i -e "${package}" <<< "${packages}"
  done
}


search_names_only() {
  local 'package'
  for package; do
    apt-cache search --names-only "${package}" | sort
  done
}

Ceux-ci vous permettent d'effectuer plusieurs recherches en une seule commande. Par exemple:

$ search hunspell-en-zz hunspell-en
hunspell-en-au
hunspell-en-ca
hunspell-en-gb
hunspell-en-med
hunspell-en-us
hunspell-en-za

Chaque fonction recherche la base de données d'une manière différente, donc les résultats peuvent varier selon la fonction que vous utilisez:

$ search gnome | wc -l
538
$ search_all gnome | wc -l
1322
$ search_description gnome | wc -l
822
$ search_names_only gnome | wc -l
550

2

Je n'appellerais pas cela élégant mais je pense que cela pourrait faire l'affaire:

search_packages () {
    local packages=($@)
    local results=()
    for package in "${packages[@]}"; do
        results=($(apt-cache -n search "$package"))
        if [[ "${#results[@]}" -eq 0 ]]; then
            echo "$package not found."
        elif [[ "${#results[@]}" -eq 1 ]]; then
            do stuff with "$package"
        else
            echo "Warning! Found multiple packages for ${package}:"
            printf '\t-> %s\n' "${results[@]}"
        fi
    done
}

Je n'ai malheureusement pas de machine Debian sur laquelle tester. J'ai inclus l' -noption for "names-only" apt-cachepour essayer de limiter les résultats de la recherche car il semble que vous soyez presque sûr de ce que vous recherchez.

Peut être exécuté comme:

$ search_packages hunspell-en-zz hunspell-en
$ my_packages=('hunspell-en-zz' 'hunspell-en')
$ search_packages "${my_packages[@]}"

1
C'est exactement ce que je pensais faire, mais je cherchais quelque chose d'aussi élégant, alors voyons si quelqu'un a autre chose d'intelligent (comme une solution plus abstraite loin du cas d'utilisation) sinon je marquerai comme choisi.
Evan Carroll

1
Idéalement, apt-cache retournerait juste quelque chose de moins stupide.
Evan Carroll

1
@EvanCarroll, Avez-vous essayé de jouer avec l' -qoption silencieuse? La page de manuel n'est pas très détaillée, mais peut-être qu'elle modifie les valeurs de retour?
jesse_b

1
renvoie toujours 0. = (
Evan Carroll

2

Muru a précisé cela dans les commentaires greprenverra un état de 1 s'il n'y a pas d'entrée. Vous pouvez donc ajouter grep .dans le flux et s'il n'y a pas d'entrée correspondant au modèle ., cela changera le code d'état:

( ( echo -n | grep . ) || echo 'nada' ) | cat      # prints 'nada'
( ( echo -n foo | grep . ) || echo 'nada' ) | cat  # prints 'foo'

Dans le cas d'utilisation qui ressemble à ceci. Dans le bas, il n'y a pas -pl-pldonc il retombe et revienthunspell-pl

apt-cache search hunspell-pl-pl | grep . || apt-cache search hunspell-pl

Ou,

apt-cache search hunspell-en-US | grep . || apt-cache search hunspell-en

Il y a -en-USdonc un retour hunspell-en-us.

Voir également,


grep .renvoie true si l'entrée contient au moins une ligne (entièrement délimitée avec certaines implémentations) qui contient au moins un caractère (bien formé avec la plupart des implémentations) et supprimera sinon les lignes vides. grep '^'fonctionnerait mieux pour vérifier qu'il y a une sortie, mais avec une implémentation, elle pourrait toujours retourner false si l'entrée est une ligne non délimitée (et pourrait supprimer cette ligne, ou avec d'autres implémentations, retourner true mais ajouter la nouvelle ligne manquante). Certaines implémentations grep étouffent également le caractère NUL.
Stéphane Chazelas

2

Vous pouvez définir un:

has_output() {
  LC_ALL=C awk '1;END{exit!NR}'
}

Et alors:

if cmd | has_output; then
  echo cmd did produce some output
fi

Certaines awkimplémentations peuvent s'étouffer avec des caractères NUL dans l'entrée.

Contrairement à grep '^', ce qui précède serait garanti pour fonctionner sur une entrée qui ne se termine pas par un caractère de nouvelle ligne, mais ajouterait la nouvelle ligne manquante.

Pour éviter cela et être portable sur des systèmes où awks'étouffent NUL, vous pouvez utiliser à la perlplace:

has_output() {
  perl -pe '}{exit!$.'
}

Avec perl, vous pouvez également définir une variante qui gère plus élégamment les fichiers arbitraires:

has_output() {
  PERLIO=:unix perl -pe 'BEGIN{$/=\65536} END{exit!$.}'
}

Cela limite l'utilisation de la mémoire (comme pour les fichiers qui n'ont pas de caractères de nouvelle ligne comme les gros fichiers épars).

Vous pouvez également créer des variantes comme:

has_at_least_one_non_empty_line() {
  LC_ALL=C awk '$0 != "" {n++};1; END{exit!n}'
}

ou:

has_at_least_one_non_blank_line() {
  awk 'NF {n++};1; END{exit!n}'
}

(attention, la définition du blanc varie entre les awkimplémentations, certaines où elles sont limitées à l'espace et à la tabulation, d'autres où elles incluent également des caractères d'espacement vertical ASCII comme CR ou FF, d'autres où elle considère les blancs des paramètres régionaux)

Idéalement, sous Linux, vous souhaitez utiliser l' splice()appel système pour maximiser les performances. Je ne sais pas d'une commande qui exposerait mais vous pouvez toujours utiliser pythonest ctypes:

has_output() {
  python -c 'if 1:
    from ctypes import *
    import sys
    l = CDLL("libc.so.6")
    ret = 1
    while l.splice(0,0,1,0,65536,0) > 0:
      ret = 0
    sys.exit(ret)'
}

(notez que soit has_outputstdin soit stdout (ou les deux) doit être un tube pour splice()fonctionner).


0

Je suggère d'utiliser des fonctions intégrées très basiques du shell:

ck_command() { [ -n $("$@") ] ; }

Voici le cas de test le plus simple:

ck_command echo 1 ; echo $?

ck_command echo ; echo $?

Ensuite, vous pouvez facilement l'utiliser avec la ||construction à laquelle vous êtes habitué:

ck_command command_1 || ck_command command_2

Cette fonction simple fonctionnera comme vous le souhaitez avec votre apt_cachecomportement, quel que soit le nombre d'arguments.


Sauf que cela perd STDOUT dans le processus, ck_command echo 'asdf' | catne produit rien.
Evan Carroll

2
→ EvanCarroll: ce n'était pas dans votre § "La question". Pour obtenir également cette conservation de sortie, consultez la réponse très élégante et simple de @roaima: unix.stackexchange.com/a/413344/31707 .
dan
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.