Existe-t-il une commande unix qui donne le minimum / maximum de deux nombres?

37

Je cherchais une commande pour limiter le nombre de lectures stdin.

J'ai écrit un petit script à cet effet (la critique est la bienvenue), mais je me demandais s'il n'existait pas de commande standard, simple et (je pense) d'usage.

Mon script qui trouve le minimum de deux nombres:

#!/bin/bash
# $1 limit

[ -z "$1" ] && { echo "Needs a limit as first argument." >&2; exit 1; }

read number

if [ "$number" -gt "$1" ]; then
        echo "$1"
else
        echo "$number"
fi
Minix
la source

Réponses:

20

Vous pouvez comparer seulement deux nombres avec dccomme:

dc -e "[$1]sM $2d $1<Mp"

... où "$1"est votre valeur maximale et "$2"le nombre que vous imprimeriez s'il était inférieur à "$1". Cela nécessite également GNU dc- mais vous pouvez faire la même chose de manière portable comme:

dc <<MAX
    [$1]sM $2d $1<Mp
MAX

Dans les deux cas ci - dessus , vous pouvez définir la précision autre chose que 0 (la valeur par défaut) comme ${desired_precision}k. Pour les deux, il est également impératif de vérifier que les deux valeurs sont bien des nombres car vous dcpouvez effectuer des system()appels avec l' !opérateur.

Avec le petit script suivant (et le suivant), vous devez également vérifier l’entrée - comme grep -v \!|dcun outil permettant de gérer de manière robuste une entrée arbitraire. Vous devez également savoir que dcinterprète les nombres négatifs avec un _préfixe plutôt qu'un -préfixe - car ce dernier est l'opérateur de soustraction.

En plus de cela, avec ce script dc, vous lirez autant de nombres séquentiels \nséparés par ligne électronique que vous voudriez bien le fournir, et vous imprimerez pour chacun votre $maxvaleur ou l'entrée, selon le plus petit des deux:

dc -e "${max}sm
       [ z 0=? d lm<M p s0 lTx ]ST
       [ ? z 0!=T q ]S?
       [ s0 lm ]SM lTx"

Alors ... chacun de ces [carrés bracketing ]étendues est une dc chaîne objet qui est SAved chacun à son réseau respectif - l'une des T, ?ou M. À part quelques petites choses que l’on dcpourrait faire avec une chaîne , elle peut aussi être xutilisée comme une macro. Si vous organisez cela correctement, un petit dcscript entièrement fonctionnel est assemblé de manière assez simple.

dcfonctionne sur une pile . Tous les objets en entrée sont empilés les uns sur les autres - chaque nouvel objet en entrée repoussant le dernier objet en haut et tous les objets en dessous de celui-ci, un par un au fur et à mesure de son ajout. La plupart des références à un objet se rapportent à la valeur de pile supérieure, et la plupart des références insèrent cette pile en haut de la pile (qui extrait tous les objets situés en dessous d'un objet) .

En plus de la pile principale, il y a aussi (au moins) 256 tableaux et chaque élément de tableau est livré avec une pile qui lui est propre. Je n'en utilise pas beaucoup ici. Je stocke simplement les chaînes comme indiqué afin de pouvoir les lutiliser quand bon me semble et les xéchanger sous condition, et j’ai sdéchiré $maxla valeur en haut du mtableau.

Quoi qu'il en soit, ce petit peu dcfait, en grande partie, ce que fait votre script shell. Il utilise l' -eoption GNU-ism - comme dcses paramètres sont généralement pris dans l'entrée standard - mais vous pouvez faire la même chose de la manière suivante:

echo "$script" | cat - /dev/tty | dc

... Si $scriptressemblait à ce qui précède.

Cela fonctionne comme:

  • lTx- Cela permet d' lexécuter et d' xexécuter la macro stockée dans le haut de T (pour tester, je suppose - je choisis habituellement ces noms de manière arbitraire) .
  • z 0=?- Test ensuite teste la profondeur de la pile w / zet, si la pile est vide (read: contient 0 objets), elle appelle la ?macro.
  • ? z0!=T q- La ?macro porte le nom de la ? dccommande intégrée qui lit une ligne d’entrée à partir de stdin, mais j’y ai également ajouté un autre ztest de profondeur de la pile, de sorte qu’elle puisse qgérer tout le petit programme s’il affiche une ligne vierge ou atteint EOF. Mais si ce n'est !pas le cas et que la pile est correctement remplie, elle appelle à Tnouveau est.
  • d lm<M- Test alors va dutiliser le haut de la pile et le comparer à $max (tel que stocké dans m) . Si mest la plus petite valeur, dcappelle la Mmacro.
  • s0 lm- Maffiche simplement le haut de la pile et le vide dans le scalaire factice 0- juste un moyen peu coûteux de faire éclater la pile. Il a également lOADS à mnouveau avant de revenir à Test.
  • p- Cela signifie que si mest inférieur au sommet actuel de la pile, il le mremplace (son duplicate, en tout cas) et est ici pimprimé, sinon ce n'est pas le cas et quelle que soit l'entrée, il est pimprimé à la place.
  • s0- Ensuite (parce pque la pile n'apparaît pas), nous vidons à nouveau le haut de la pile 0, puis ...
  • lTx- récursive lOAD THNE fois plus de e xecute nouveau.

Ainsi, vous pouvez exécuter ce petit extrait et taper les numéros de manière interactive sur votre terminal et vous dcimprimer le numéro que vous avez entré ou la valeur de $maxsi le nombre que vous avez entré est plus grand. Il accepterait également n'importe quel fichier (tel qu'un tuyau) comme entrée standard. La boucle lecture / comparaison / impression se poursuivra jusqu'à ce qu'elle rencontre une ligne vierge ou une fin de fichier.

Quelques notes à ce sujet, par contre - j’ai écrit ceci uniquement pour émuler le comportement de votre fonction shell, afin qu’il ne gère que de manière robuste le nombre par ligne. dcpeut cependant traiter autant de numéros d'espaces séparés par ligne que vous le souhaitez. Cependant , à cause de sa pile, le dernier chiffre d’une ligne devient le premier sur lequel il opère et, ainsi qu’il est écrit, dcil imprimerait sa sortie en sens inverse si vous imprimiez / tapiez plus d’un nombre par ligne. gérer cela consiste à stocker une ligne dans un tableau, puis à la travailler.

Comme ça:

dc -e "${max}sm
    [ d lm<M la 1+ d sa :a z0!=A ]SA
    [ la d ;ap s0 1- d sa 0!=P ]SP 
    [ ? z 0=q lAx lPx l?x ]S?
    [q]Sq [ s0 lm ]SM 0sa l?x"

Mais ... je ne sais pas si je veux expliquer cela plus en profondeur. Il suffit de dire que, comme dcchaque valeur de la pile est lue, elle stocke sa valeur ou sa valeur $maxdans un tableau indexé et, une fois détectée, la pile est à nouveau vide, elle affiche ensuite chaque objet indexé avant de tenter d'en lire un autre. ligne d'entrée.

Et alors, alors que le premier script fait ...

10 15 20 25 30    ##my input line
20
20
20
15
10                ##see what I mean?

La seconde fait:

10 15 20 25 30    ##my input line
10                ##that's better
15
20
20                ##$max is 20 for both examples
20

Vous pouvez gérer des flottants de précision arbitraire si vous le définissez d'abord avec la kcommande. Et vous pouvez modifier les radices d' ientrée ou de osortie de manière indépendante - ce qui peut parfois être utile pour des raisons inattendues. Par exemple:

echo 100000o 10p|dc
 00010

... qui définit d dc' abord le radix de sortie à 100000, puis en imprime 10.

Mikeserv
la source
3
+1 pour n'avoir aucune idée de ce qui vient de se passer après l'avoir lu deux fois. Devra prendre mon temps pour approfondir cela.
Minix
@Minix - meh - inutile de vous plonger dans le plus ancien langage de programmation Unix si vous le trouvez déroutant. Peut-être que quelques chiffres à la dcfois de temps en temps pour le garder sur ses gardes, cependant.
mikeserv
1
@ mikeserv C'est trop tard pour moi. J'espère que la génération future prendra mon récit édifiant. Des crochets et des lettres partout ...
Minix le
@Minix - que voulez-vous dire? Vous êtes allé pour ça? Très bon - dcc'est une bête volage, mais il pourrait bien être l'utilitaire commun le plus rapide et le plus étrangement capable sur n'importe quel système Unix. Lorsque jumelé avec sedpeut faire des choses extraordinaires. J'ai joué avec et ddrécemment pour pouvoir remplacer la monstruosité qui est readline. Voici un petit échantillon de certaines choses que j'ai faites. Faire un revdans dcest presque un jeu d'enfant.
mikeserv
1
@Minix - Attention w / les crochets, cependant. Il n'y a aucun moyen de mettre un crochet dans une chaîne - le mieux que vous puissiez faire est [string]P91P93P[string]P. Donc, j'ai ce petit bout de sedchose que vous pourriez trouver utile: sed 's/[][]/]P93]&[1P[/g;s/[]3][]][[][1[]//g'qui devrait toujours remplacer les carrés correctement par un crochet de fermeture de chaîne, puis un P, puis la valeur décimale ascii du carré et un autre P; puis un [crochet ouvert pour continuer la chaîne. Je ne sais pas si vous avez déconné avec dcles capacités de conversion chaîne / numérique de w / , mais - surtout lorsque combiné avec w / od- cela peut être assez amusant.
mikeserv
88

Si vous savez que vous avez affaire à deux entiers aet b, alors ces simples développements arithmétiques en shell à l'aide de l'opérateur ternaire suffisent pour donner le max numérique:

$(( a > b ? a : b ))

et numérique min .:

$(( a < b ? a : b ))

Par exemple

$ a=10
$ b=20
$ max=$(( a > b ? a : b ))
$ min=$(( a < b ? a : b ))
$ echo $max
20
$ echo $min
10
$ a=30
$ max=$(( a > b ? a : b ))
$ min=$(( a < b ? a : b ))
$ echo $max
30
$ echo $min
20
$ 

Voici un script shell démontrant ceci:

#!/usr/bin/env bash
[ -z "$1" ] && { echo "Needs a limit as first argument." >&2; exit 1; }
read number
echo Min: $(( $number  < $1 ? $number : $1 ))
echo Max: $(( $number  > $1 ? $number : $1 ))
Trauma numérique
la source
Bonne réponse. S'il vous plaît, un mod mineur: cela pourrait-il être utilisé aussi pour "> ="?
Sopalajo de Arrierez
@SopalajodeArrierez Je ne suis pas tout à fait sûr de ce que vous voulez dire. Vous pouvez également le faire max=$(( a >= b ? a : b )), mais le résultat est tout à fait le même: si a et b sont égaux, le résultat renvoyé importe peu. Est-ce ce que vous demandez?
Digital Trauma
En effet, merci, traumatisme sexuel. Je me demandais simplement si l'opérateur booléen "> =" était possible ici.
Sopalajo de Arrierez
@SopalajodeArrierez if (( a >= b )); then echo a is greater than or equal to b; fi- c'est ce que vous demandez? (notez l'utilisation (( ))ici au lieu de $(( )))
Digital Trauma,
Ah oui, d'accord. Je comprends maintenant. Je ne connais pas grand chose à l’extension du shell, alors j’ai tendance à confondre les conditions. Merci encore.
Sopalajo de Arrierez
24

sortet headpeut le faire:

numbers=(1 4 3 5 7 1 10 21 8)
printf "%d\n" "${numbers[@]}" | sort -rn | head -1       # => 21
Glenn Jackman
la source
2
Notez que ceci est O(n log(n))une implémentation efficace de max O(n). n=2Cependant, c’est notre faible importance , car la création de deux processus représente une charge beaucoup plus importante.
Lie Ryan
1
Bien que vrai, @ Glenn-Jackman, je ne suis pas sûr que cela compte pour la question. Il n'y avait aucune demande pour le moyen le plus efficace de le faire. Je pense que la question portait plus sur la commodité.
David Hoelzer
1
@ DavidHoelzer - ce n'est pas le moyen le plus efficace de le faire, même parmi les réponses proposées ici. Si vous travaillez avec des ensembles de nombres, il existe au moins une autre réponse plus efficace que cela (par ordre de grandeur) , et si vous ne travaillez qu'avec deux nombres entiers, il existe une autre réponse plus efficace que cela (par ordre de grandeur) . C'est pratique cependant (mais je laisserais probablement de côté le tableau shell, personnellement) .
mikeserv
1
Cela peut être fait sans tableaux comme suit:numbers="1 4 3 5 7 1 10 21 8"; echo $numbers | tr ' ' "\n" | sort -rn | head -n 1
ngreen
1
La solution la plus efficace est probablement la suivante:max=0; for x in $numbers ; do test $x -gt $max && max=$x ; done
Green
6

Vous pouvez définir une bibliothèque de fonctions mathématiques prédéfinies pour bc, puis les utiliser dans la ligne de commande.

Par exemple, incluez les éléments suivants dans un fichier texte tel que ~/MyExtensions.bc:

define max(a,b){
  if(a>b)
  { 
   return(a)
  }else{
   return(b)
  }
}

Maintenant, vous pouvez appeler bcpar:

> echo 'max(60,54)' | bc ~/MyExtensions.bc
60

Pour votre information, il existe des fonctions gratuites de la bibliothèque mathématique comme celle-ci disponibles en ligne.

En utilisant ce fichier, vous pouvez facilement calculer des fonctions plus complexes telles que GCD:

> echo 'gcd (60,54)' | bc ~/extensions.bc -l
6
Ari
la source
Si je ne me trompe pas, de telles fonctions peuvent également être compilées avec l'exécutable si nécessaire. Je pense que la plupart des bcs ne sont plus que des dcinterfaces à ce jour, même si GNUbc ne l’est plus (mais GNU dcet GNU bcpartagent une quantité prodigieuse de leur base de code) . Quoi qu'il en soit, cela pourrait être la meilleure réponse ici.
mikeserv
Pour appeler facilement cela dans le fichier d'un script shell, vous pouvez également diriger la définition de fonction vers bc, juste avant l'appel de la fonction. Pas de deuxième fichier nécessaire alors :)
tanius
5

Trop long pour un commentaire:

Bien que vous puissiez faire ces choses, par exemple avec les combinaisons sort | headou sort | tail, cela semble plutôt sous-optimal en termes de gestion des ressources et d'erreur. En ce qui concerne l'exécution, la liste déroulante signifie que deux processus sont créés pour vérifier deux lignes. Cela semble être un peu exagéré.

Le problème le plus grave est que, dans la plupart des cas, vous devez savoir que l'entrée est saine, c'est-à-dire qu'elle ne contient que des chiffres. La solution de @ glennjackmann résout intelligemment ce problème, car il printf %ddevrait fonctionner avec des valeurs non entières. Cela ne fonctionnera pas non plus avec des flottants (sauf si vous modifiez le spécificateur de format en %f, où vous rencontrerez des problèmes d'arrondi).

test $1 -gt $2 vous indiquera si la comparaison a échoué ou non (l'état de sortie de 2 signifie qu'il y a eu une erreur pendant le test. Comme il s'agit généralement d'un shell intégré, aucun processus supplémentaire n'est généré - nous parlons d'un ordre de centaines exécution plus rapide. Ne fonctionne que sur les entiers.

Si vous avez besoin de comparer quelques nombres à virgule flottante, une option intéressante pourrait être bc:

define x(a, b) {
    if (a > b) {
       return (a);
    }
    return (b);
 }

serait l'équivalent de test $1 -gt $2, et en utilisant dans shell:

max () { printf '
    define x(a, b) {
        if (a > b) {
           return (a);
        }
        return (b);
     }
     x(%s, %s)
    ' $1 $2 | bc -l
}

est toujours presque 2,5 fois plus rapide que printf | sort | head(pour deux nombres).

Si vous pouvez compter sur les extensions GNU bc, vous pouvez également utiliser la read()fonction pour lire les numéros directement dans le bcscript.

Peterph
la source
Je pensais exactement - j’étais en train de résoudre le problème , mais vous m’y êtes battu: dc -e "${max}sm[z0=?dlm<Mps0lTx]ST[?z0!=Tq]S?[s0lm]SMlTx"- oh, sauf que ça dcfait tout (sauf l’écho, même s’il le pourrait) - il lit stdin et affiche soit $maxle numéro d’entrée en fonction de est plus petit. Quoi qu'il en soit, je ne tiens pas vraiment à l'expliquer et votre réponse est meilleure que ce que j'allais écrire. Alors, ayez mon vote positif, s'il vous plaît.
mikeserv
@mikeserv ayant un dcscript expliqué serait vraiment bien, RPN n'est pas vu aussi souvent ces jours-ci.
Peter
Notation polonaise inversée (notation Postfix). De plus, si dcje peux faire l’E / S seul, ce sera encore plus élégant que.
Peter
4

Pour obtenir la plus grande valeur de $ a et $ b, utilisez ceci:

[ "$a" -gt "$b" ] && $a || $b

Mais vous avez besoin de quelque chose autour de ça, vous ne voulez probablement pas exécuter le nombre, donc pour afficher la plus grande valeur des deux, utilisez "echo"

[ "$a" -gt "$b" ] && echo $a || echo $b

Ce qui précède s’intègre parfaitement dans une fonction shell, par exemple

max() {
   [ "$1" -gt "$2" ] && echo $1 || echo $2
}

Pour assigner le plus grand des deux à la variable, utilisez cette version modifiée:

[ "$a" -gt "$b" ] && biggest=$a || biggest=$b

ou utilisez la fonction définie:

biggest=$( max $a $b )

La variante de fonction vous donne également la possibilité d’ajouter une vérification nette des erreurs d’entrée.

Pour renvoyer le maximum de deux nombres décimaux / flottants que vous pouvez utiliser awk

decimalmax() { 
   echo $1 $2 | awk '{if ($1 > $2) {print $1} else {print $2}}'; 
}

EDIT: En utilisant cette technique, vous pouvez créer une fonction "limite" qui opère dans le sens contraire de votre édition / note. Cette fonction retournera le plus bas des deux, par exemple:

limit() {
   [ "$1" -gt "$2" ] && echo $2 || echo $1
}

J'aime mettre les fonctions utilitaires dans un fichier séparé, appelez-le myprogram.funcset utilisez-le dans un script comme suit:

#!/bin/bash

# Initialization. Read in the utility functions
. ./myprogram.funcs

# Do stuff here
#
[ -z "$1" ] && { echo "Needs a limit as first argument." >&2; exit 1; }

read number
echo $( limit $1 $number )

FWIW fait toujours ce que vous avez fait et votre version, même si elle est plus détaillée, est tout aussi efficace.

La forme la plus compacte n’est pas vraiment meilleure, mais évite d’encombrer vos scripts. Si vous avez plusieurs constructions simples if-then-else-fi, le script se développe rapidement.

Si vous souhaitez réutiliser plusieurs fois la recherche de nombres plus grands / plus petits dans un même script, placez-la dans une fonction. Le format de la fonction facilite le débogage et la réutilisation et vous permet de remplacer facilement cette partie du script, par exemple avec une commande awk pour pouvoir traiter des nombres décimaux non entiers.

S'il s'agit d'un cas d'utilisation unique, codez-le simplement en ligne.

Johan
la source
4

Vous pouvez définir une fonction comme

maxnum(){
    if [ $2 -gt $1 ]
    then
        echo $2
    else
        echo $1
    fi
}

Appelez comme maxnum 54 42et ça résonne 54. Vous pouvez ajouter des informations de validation dans la fonction (telles que deux arguments ou des nombres comme arguments) si vous le souhaitez.

unxnut
la source
La plupart des coquillages ne font pas d'arithmétique à virgule flottante. Mais cela fonctionne pour les entiers.
orion
1
Cette fonction est inutilement incompatible avec POSIX. Changez function maxnum {en maxnum() {et ça fonctionnera pour beaucoup plus de coquillages.
Charles Duffy
2

A partir d'un script shell, il existe un moyen d'utiliser n'importe quelle méthode statique publique Java (et par exemple Math.min () ). De bash sur Linux:

. jsbInit
jsbStart 
A=2 
B=3 
C=$(jsb Math.min "$A" "$B")
echo "$C"

Cela nécessite Java Shell Bridge https://sourceforge.net/projects/jsbridge/

Très rapide, car les appels de méthode sont acheminés en interne ; aucun processus requis.

Fil
la source
0

La plupart des gens feraient juste sort -n input | head -n1(ou la queue), c'est assez bon pour la plupart des situations de script. Cependant, c'est un peu maladroit si vous avez des chiffres dans une ligne plutôt que dans une colonne - vous devez l'imprimer dans un format approprié ( tr ' ' '\n'ou quelque chose de similaire).

Les coques ne sont pas tout à fait idéales pour le traitement numérique, mais vous pouvez facilement vous connecter à un autre programme plus performant. En fonction de vos préférences, vous pouvez téléphoner au maximum dc(un peu brouillé, mais si vous savez ce que vous faites, tout va bien - voir la réponse de mikeserv), ou awk 'NR==1{max=$1} {if($1>max){max=$1}} END { print max }'. Ou éventuellement perlou pythonsi vous préférez. Une solution (si vous êtes prêt à installer et à utiliser un logiciel moins connu) serait ised(surtout si vos données sont sur une seule ligne: il vous suffit de le faire ised --l input.dat 'max$1').


Parce que vous demandez deux chiffres, tout cela est excessif. Cela devrait suffire:

python -c "print(max($j,$k))"
orion
la source
1
Peut-être mieux si vous avez utilisé sys.argv:python2 -c 'import sys; print (max(sys.argv))' "$@"
muru
1
Les arguments qui sort + headsont excessifs mais pythonne sont pas calculés.
mikeserv
Toutes les méthodes situées au-dessus de la ligne sont conçues pour gérer d’énormes ensembles de nombres et suggèrent explicitement ce type d’utilisation (lecture dans un tube ou un fichier). min / max pour 2 arguments est une question qui a un sens différent - elle appelle une fonction plutôt qu'un flux. Je voulais juste dire que l'approche par flux est excessive - l'outil que vous utilisez est arbitraire, je l'ai utilisé pythonparce qu'il est ordonné.
orion
Je dirais que cette solution suggérée est plus simple , mais c'est peut-être parce que je suis un pythonbigot (ou parce qu'elle ne nécessite ni fourchette ni interpréteur gigantesque supplémentaire) . Ou peut-être les deux.
mikeserv
@ mikeserv, je l'utiliserais aussi si je savais qu'il s'agissait d'entiers. Toutes les solutions que j'ai mentionnées sont basées sur l'hypothèse que les nombres peuvent être des flottants - bash ne fait pas de virgule flottante et à moins que zsh ne soit votre shell natif, vous aurez besoin d'une fourchette (et éventuellement d'un couteau).
orion