Je cherchais une commande pour limiter les nombres lus depuis stdin
.
J'ai écrit un petit script à cet effet (la critique est la bienvenue), mais je me demandais s'il n'y avait pas de commande standard pour ce cas d'utilisation simple et (je pense) courant.
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
Vous pouvez comparer seulement deux nombres avec dc
comme:
dc -e "[$1]sM $2d $1<Mp"
... où "$1"
est votre valeur maximale et "$2"
est le nombre que vous imprimeriez s'il est 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 sur autre chose que 0 (par défaut) comme ${desired_precision}k
. Pour les deux, il est également impératif de vérifier que les deux valeurs sont certainement des nombres car dc
peut effectuer des appels system()
avec l'opérateur !
.
Avec le petit script suivant (et le suivant) vous devriez également vérifier l'entrée - comme grep -v \!|dc
Ou quelque chose pour gérer de manière robuste l'entrée arbitraire. Vous devez également savoir que dc
interprète les nombres négatifs avec un préfixe _
Plutôt qu'un préfixe -
- car ce dernier est l'opérateur de soustraction.
Mis à part cela, avec ce script dc
lira autant de numéros séquentiels \n
Que vous voudriez le fournir, et imprimera pour chacun soit votre valeur $max
Ou l'entrée, selon laquelle est la moindre des deux:
dc -e "${max}sm
[ z 0=? d lm<M p s0 lTx ]ST
[ ? z 0!=T q ]S?
[ s0 lm ]SM lTx"
Donc ... chacune de ces étendues [
Entre crochets ]
Est un dc
chaîne objet qui est S
enregistré chacune à son tableau respectif - n'importe lequel parmi T
, ?
ou M
. Outre quelques autres choses que dc
pourrait faire avec une chaîne, il peut également e x
en exécuter une comme macro. Si vous l'arrangez correctement, un petit script dc
pleinement fonctionnel est assemblé assez simplement.
dc
fonctionne sur une pile. Tous les objets d'entrée sont empilés chacun sur le dernier - chaque nouvel objet d'entrée poussant le dernier objet supérieur et tous les objets en dessous vers le bas sur la pile par un lors 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 pop au sommet de la pile (qui tire tous les objets en dessous de lui).
Outre la pile principale, il existe également (au moins) 256 tableaux et chaque élément de tableau est livré avec une pile qui lui est propre. Je n'utilise pas beaucoup de cela ici. Je stocke juste les chaînes comme mentionné afin que je puisse l
les charger quand on le souhaite et e x
les exécuter conditionnellement, et je s
tore $max
La valeur en haut de le tableau m
.
Quoi qu'il en soit, ce petit morceau de dc
fait, en grande partie, ce que fait votre script Shell. Il utilise l'option GNU-ism -e
- comme dc
prend généralement ses paramètres de standard-in - mais vous pouvez faire la même chose comme:
echo "$script" | cat - /dev/tty | dc
... si $script
ressemblait au bit ci-dessus.
Cela fonctionne comme:
lTx
- Cette l
charge et e x
exécute la macro stockée en haut de T
(pour le test, je suppose - je choisis généralement ces noms arbitrairement).z 0=?
- T
est teste ensuite la profondeur de la pile w/z
et, si la pile est vide (lire: contient 0 objets) il appelle le Macro ?
.? z0!=T q
- La macro ?
Est nommée pour la commande intégrée ?
dc
qui lit une ligne d'entrée depuis stdin, mais j'ai également ajouté une autre z
teste la profondeur de la pile pour qu'il puisse q
uit tout le petit programme s'il tire une ligne vierge ou frappe EOF. Mais s'il ne fait pas !
Et remplit correctement la pile, il appelle à nouveau T
est.d lm<M
- T
est va alors d
upliquer le haut de la pile et le comparer à $max
(tel que stocké dans m
). Si m
est la valeur inférieure, dc
appelle la macro M
.s0 lm
- M
fait simplement apparaître le haut de la pile et la vide dans le scalaire factice 0
- juste une façon peu coûteuse de faire sauter la pile. Il a également l
ajoute m
à nouveau avant de revenir à T
est.p
- Cela signifie que si m
est inférieur au sommet actuel de la pile, alors m
le remplace (le d
en double, de toute façon) et est ici p
rinted, sinon ce n'est pas le cas et quelle que soit l'entrée était p
rinted à la place.s0
- Après (parce que p
ne fait pas éclater la pile) nous vidons le haut de la pile dans 0
À nouveau, puis ...lTx
- récursivement l
oad T
est à nouveau puis e x
exécutez-le à nouveau.Vous pouvez donc exécuter ce petit extrait et taper interactivement des nombres sur votre terminal et dc
vous affichera soit le numéro que vous avez entré, soit la valeur de $max
Si le nombre que vous avez tapé était plus grand. Il accepterait également n'importe quel fichier (comme un tuyau) comme entrée standard. Il continuera la boucle de lecture/comparaison/impression jusqu'à ce qu'il rencontre une ligne vierge ou EOF.
Quelques notes à ce sujet - j'ai écrit cela juste pour émuler le comportement de votre fonction Shell, donc il ne gère que de manière robuste le seul numéro par ligne. dc
peut, cependant, gérer autant de nombres séparés par des espaces par ligne que vous voudriez y jeter. Cependant, en raison de sa pile, le dernier nombre sur une ligne se termine comme le premier sur lequel il opère, et ainsi, comme écrit, dc
imprimerait sa sortie à l'envers si vous imprimiez/tapé plus d'un numéro par ligne. La bonne façon de gérer cela est de stocker une ligne dans un tableau, puis de 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 avec autant de profondeur. Il suffit de dire que dc
lit chaque valeur de la pile, il stocke sa valeur ou la valeur de $max
Dans un tableau indexé et, une fois qu'il détecte, la pile est à nouveau vide , il imprime ensuite chaque objet indexé avant d'essayer de lire une autre ligne d'entrée.
Et donc, alors que le premier script fait ...
10 15 20 25 30 ##my input line
20
20
20
15
10 ##see what I mean?
Le second:
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 commande k
. Et vous pouvez modifier les radices i
nput ou o
utput indépendamment - ce qui peut parfois être utile pour des raisons inattendues. Par exemple:
echo 100000o 10p|dc
00010
... qui définit tout d'abord le radix de sortie de dc
sur 100000 puis imprime 10.
Si vous savez que vous avez affaire à deux entiers a
et b
, alors ces simples expansions arithmétiques Shell utilisant l'opérateur ternaire sont suffisantes pour donner le maximum numérique:
$(( a > b ? a : b ))
et min numérique:
$(( 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 qui le démontre:
#!/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 ))
sort
et head
peuvent le faire:
numbers=(1 4 3 5 7 1 10 21 8)
printf "%d\n" "${numbers[@]}" | sort -rn | head -1 # => 21
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)
}
}
Vous pouvez maintenant appeler bc
en:
> echo 'max(60,54)' | bc ~/MyExtensions.bc
60
Pour info, il existe des fonctions de bibliothèque mathématique gratuites comme celle-ci disponibles en ligne.
En utilisant ce fichier, vous pouvez facilement calculer des fonctions plus compliquées telles que GCD
:
> echo 'gcd (60,54)' | bc ~/extensions.bc -l
6
Trop long pour un commentaire:
Bien que vous puissiez faire ces choses, par exemple avec les combos sort | head
ou sort | tail
, cela semble plutôt sous-optimal en termes de ressources et de gestion des erreurs. En ce qui concerne l'exécution, le combo signifie générer 2 processus juste 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 astucieusement cela, car printf %d
devrait barf sur des non-entiers. Cela ne fonctionnera pas non plus avec les flottants (sauf si vous changez 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 2 signifie qu'il y a eu une erreur pendant le test. Comme il s'agit généralement d'un shell intégré, il n'y a pas de processus supplémentaire généré - nous ' parler d'un ordre d'exécution des centaines de fois plus rapide. Fonctionne uniquement avec des entiers.
Si vous avez besoin de comparer quelques nombres à virgule flottante, l'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 dans bc
, vous pouvez également utiliser la fonction read()
pour lire les nombres directement dans bc
sript.
Vous pouvez définir une fonction comme
maxnum(){
if [ $2 -gt $1 ]
then
echo $2
else
echo $1
fi
}
Appelez-le comme maxnum 54 42
et ça fait écho 54
. Vous pouvez ajouter des informations de validation à l'intérieur de la fonction (comme deux arguments ou des nombres comme arguments) si vous le souhaitez.
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 cela, vous n'avez probablement pas l'intention d'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 affecter 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 variation de fonction vous donne également la possibilité d'ajouter une vérification des erreurs d'entrée parfaitement.
Pour renvoyer le maximum de deux nombres décimaux/à virgule flottante, 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 fonctionne dans l'autre sens selon votre édition/note. Cette fonction renverra 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.funcs
et l'utiliser 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, cela fait toujours ce que vous avez fait, et votre version, même si elle est plus verbeuse, est tout aussi efficace.
La forme plus compacte n'est pas vraiment meilleure, mais empêche l'encombrement dans vos scripts. Si vous avez de nombreuses constructions simples if-then-else-fi, le script se développe rapidement.
Si vous souhaitez réutiliser plusieurs fois la vérification des nombres plus grands/plus petits dans un même script, mettez-la dans une fonction. Le format de fonction facilite le débogage et la réutilisation et vous permet de remplacer facilement cette partie du script, par exemple par une commande awk pour pouvoir gérer les nombres décimaux non entiers.
S'il s'agit d'un cas d'utilisation unique, codez-le simplement en ligne.
A partir d'un script Shell, il existe un moyen d'utiliser any Java méthode statique publique (et par exemple Math.min ()). Depuis bash sous 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 internes piped; aucun processus requis.
La plupart des gens feraient juste sort -n input | head -n1
(Ou queue), c'est assez bon pour la plupart des situations de script. Cependant, c'est un peu maladroit si vous avez des nombres dans une ligne au lieu d'une colonne - vous devez l'imprimer dans un format approprié (tr ' ' '\n'
Ou quelque chose de similaire).
Les shells ne sont pas exactement idéaux pour le traitement numérique, mais vous pouvez facilement simplement diriger vers un autre programme qui est meilleur. Selon vos préférences, vous appelez max dc
(un peu obscurci, mais si vous savez ce que vous faites, ça va - voir la réponse de mikeserv), ou awk 'NR==1{max=$1} {if($1>max){max=$1}} END { print max }'
. Ou éventuellement Perl
ou python
si vous préférez. Une solution (si vous souhaitez installer et utiliser des logiciels moins connus) serait ised
(surtout si vos données sont sur une seule ligne: il vous suffit de faire ised --l input.dat 'max$1'
).
Parce que vous demandez deux chiffres, tout cela est exagéré. Cela devrait suffire:
python -c "print(max($j,$k))"