Esiste un comando unix che fornisce il minimo / massimo di due numeri?


37

Stavo cercando un comando per limitare i numeri letti da stdin.

Ho scritto una piccola sceneggiatura per questo scopo (la critica è benvenuta), ma mi chiedevo se non ci fosse un comando standard per questo, semplice e (credo) caso d'uso comune.

La mia sceneggiatura che trova il minimo di due numeri:

#!/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

Risposte:


20

Puoi confrontare solo due numeri con dclike:

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

... dov'è il "$1"tuo valore massimo ed "$2"è il numero che stamperesti se fosse inferiore a "$1". Ciò richiede anche GNU dc, ma puoi fare la stessa cosa in modo portabile come:

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

In entrambi i casi precedenti puoi impostare la precisione su qualcosa di diverso da 0 (impostazione predefinita) come ${desired_precision}k. Per entrambi è anche indispensabile verificare che entrambi i valori siano sicuramente numeri perché dcpossono effettuare system()chiamate con l' !operatore.

Con il seguente piccolo script (e il successivo) dovresti verificare anche l'input - like grep -v \!|dco qualcosa del genere per gestire in modo sicuro input arbitrari. Dovresti anche sapere che dcinterpreta i numeri negativi con un _prefisso anziché con un -prefisso, poiché quest'ultimo è l'operatore di sottrazione.

A parte questo, con questo script dcleggerai tutti i \nnumeri separati di ewline sequenziali che ti piacerebbe fornirli e stamperai per ciascuno il tuo $maxvalore o l'input, a seconda di quale è il minore del guaio:

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

Quindi ... ciascuno di tali [parentesi quadre ]distese è una dc stringa oggetto che viene SAVED ciascuna alla rispettiva matrice - qualsiasi delle T, ?o M. Oltre ad alcune altre cose che dcpotrebbero fare con una stringa , può anche xcambiarne una come macro. Se lo organizzi correttamente, un piccolo dcscript perfettamente funzionante viene assemblato in modo abbastanza semplice.

dcfunziona in pila . Tutti gli oggetti di input sono impilati uno sull'ultimo: ogni nuovo oggetto di input spinge l'ultimo oggetto in alto e tutti gli oggetti sottostanti sullo stack di uno man mano che viene aggiunto. La maggior parte dei riferimenti a un oggetto sono al valore di stack superiore, e la maggior parte i riferimenti pop che cima alla pila (che tira tutti gli oggetti sotto di esso da uno) .

Oltre allo stack principale, ci sono anche (almeno) 256 array e ogni elemento dell'array viene fornito con uno stack tutto suo. Non ne uso molto qui. Conservo semplicemente le stringhe come indicato in modo da poterle ldoverle quando desiderato ed xecute in modo condizionale, e ho sstrappato $maxil valore nella parte superiore mdell'array.

Ad ogni modo, questa piccola parte dcfa, in gran parte, ciò che fa il tuo script di shell. Utilizza l' -eopzione GNU-ism - come dcgeneralmente prende i suoi parametri da standard-in - ma potresti fare lo stesso come:

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

... se $scriptsembrava il bit sopra.

Funziona come:

  • lTx- Questo ldeve ed xecare la macro memorizzata nella parte superiore di T (per test, immagino - di solito scelgo quei nomi arbitrariamente) .
  • z 0=?- Test quindi verifica la profondità dello stack con / ze, se lo stack è vuoto (leggi: contiene 0 oggetti) chiama la ?macro.
  • ? z0!=T q- La ?macro prende il nome dal ? dccomando incorporato che legge una riga di input da stdin, ma ho anche aggiunto un altro ztest di profondità dello stack, in modo che possa utilizzare ql'intero piccolo programma se inserisce una riga vuota o colpisce EOF. Ma se non lo fa !e invece popola correttamente lo stack, chiama di Tnuovo est.
  • d lm<M- Test eseguirà quindi l' duplicing della parte superiore dello stack e lo confronterà con $max (come memorizzato in m) . Se mè il valore minore, dcchiama la Mmacro.
  • s0 lm- Mfa semplicemente scoppiare la parte superiore della pila e la scarica sul manichino scalare 0- solo un modo economico per far scoppiare la pila. lDovrebbe anche remi mprima di tornare a Test.
  • p- Ciò significa che se mè inferiore all'attuale cima dello stack, quindi lo msostituisce (l' duplicate di esso, comunque) e viene qui pstampato, altrimenti non lo fa e qualunque cosa sia stato immesso viene pinvece stampato.
  • s0- Successivamente (perché pnon fa scoppiare la pila) scarichiamo di nuovo la parte superiore della pila 0e poi ...
  • lTx- ricorsivamente load Test ancora una volta, quindi xeseguirlo di nuovo.

Quindi potresti eseguire questo piccolo frammento e digitare in modo interattivo i numeri sul tuo terminale e dcstamparti o il numero inserito o il valore di $maxse il numero digitato fosse più grande. Accetterebbe anche qualsiasi file (come una pipe) come input standard. Continuerà il ciclo di lettura / confronto / stampa fino a quando non incontra una riga vuota o EOF.

Alcune note su questo però - ho scritto questo solo per emulare il comportamento nella funzione della shell, quindi gestisce in modo affidabile solo un numero per riga. dcpuò, tuttavia, gestire tutti i numeri separati da spazi per riga quanti ne vorresti lanciare. Tuttavia , a causa della sua pila, l'ultimo numero su una riga finisce per essere il primo su cui opera, e quindi, come scritto, dcstamperebbe il suo output al contrario se tu stampassi / digitassi più di un numero per riga. gestire ciò significa memorizzare una linea in un array, quindi lavorarla.

Come questo:

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"

Ma ... non so se voglio spiegarlo con la stessa profondità. Basti dire che mentre dclegge in ogni valore nello stack memorizza il suo valore o $maxil suo valore in un array indicizzato e, una volta rilevato lo stack è ancora una volta vuoto, quindi stampa ogni oggetto indicizzato prima di provare a leggerne un altro linea di input.

E così, mentre il primo script fa ...

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

Il secondo fa:

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

Puoi gestire float di precisione arbitraria se lo imposti per la prima volta con il kcomando. E puoi modificare i nices io l' ooutput radices in modo indipendente, il che a volte può essere utile per motivi che potresti non aspettarti. Per esempio:

echo 100000o 10p|dc
 00010

... che imposta prima dcil radix di output su 100000, quindi stampa 10.


3
+1 per non avere idea di cosa sia appena successo dopo averlo letto due volte. Dovrà impiegare il mio tempo per approfondire questo.
Minix,

@Minix - meh - non c'è bisogno di approfondire il più antico linguaggio di programmazione Unix se lo trovi confuso. Forse, però, basta solo inchiodare un paio di numeri dcogni tanto per tenerlo sulle punte.
Mikeserv,

1
@mikeserv È troppo tardi per me. Spero che le generazioni future prendano il mio come un ammonimento. Parentesi quadre e lettere ovunque ...
Minix,

@Minix - cosa intendi? Ci sei andato? Molto buono - dcè una bestia volubile, ma potrebbe essere l'utilità comune più veloce e stranamente capace su qualsiasi sistema Unix. Se associato con sedesso può fare alcune cose straordinarie. Ci ho giocato e ddultimamente per poter sostituire la mostruosità che è readline. Ecco un piccolo esempio di alcune cose che ho fatto. Fare un revin dcè quasi un gioco da ragazzi.
Mikeserv,

1
@Minix - attento con le parentesi, però. Non c'è modo di inserire una parentesi quadra all'interno di una stringa: il meglio che puoi fare è [string]P91P93P[string]P. Quindi ho questo piccolo di sedvoi che potrebbe essere utile: sed 's/[][]/]P93]&[1P[/g;s/[]3][]][[][1[]//g'che dovrebbe sempre sostituire correttamente i quadrati con una parentesi chiusa di stringa, quindi a P, quindi il valore ASCII decimale del quadrato e un altro P; quindi una [parentesi quadra aperta per continuare la stringa. Non so se hai incasinato le dcfunzionalità di conversione stringa / numerica di w / , ma - specialmente se combinato con w / od- può essere abbastanza divertente.
Mikeserv,

87

Se sai che hai a che fare con due numeri interi ae b, quindi, queste semplici espansioni aritmetiche della shell usando l'operatore ternario sono sufficienti per dare il massimo numerico:

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

e min numerici:

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

Per esempio

$ 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
$ 

Ecco uno script di shell che dimostra questo:

#!/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 ))

Bella risposta. Per favore, una mod minore: potrebbe essere usata anche per "> ="?
Sopalajo de Arrierez,

@SopalajodeArrierez Non sono del tutto sicuro di cosa intendi. Puoi anche fare max=$(( a >= b ? a : b )), ma il risultato è completamente lo stesso: se aeb sono uguali, non importa quale venga restituito. È quello che stai chiedendo?
Digital Trauma,

Grazie, DIgital Trauma. Mi chiedevo solo se l'operatore booleano "> =" fosse possibile qui.
Sopalajo de Arrierez,

@SopalajodeArrierez if (( a >= b )); then echo a is greater than or equal to b; fi- è quello che stai chiedendo? (nota l'uso di (( ))qui invece di $(( )))
Digital Trauma,

Ah, sì, ok. Ora capisco. Non so molto sull'espansione della shell, quindi di solito mi confondo tra le condizioni. Grazie ancora
Sopalajo de Arrierez,

24

sorte headpuò fare questo:

numbers=(1 4 3 5 7 1 10 21 8)
printf "%d\n" "${numbers[@]}" | sort -rn | head -1       # => 21

2
Si noti che questo sarebbe O(n log(n))mentre un'implementazione efficiente di max sarebbe O(n). Il nostro piccolo significato è n=2però, dal momento che la generazione di due processi è molto più grande.
Sdraiati Ryan il

1
Sebbene sia vero, @ glenn-jackman, non sono sicuro che sia importante data la domanda. Non è stato richiesto il modo più efficiente per farlo. Penso che la domanda riguardasse più la convenienza.
David Hoelzer,

1
@DavidHoelzer - questo non è il modo più efficiente per farlo anche tra le risposte offerte qui. Se si lavora con gruppi di numeri, qui c'è almeno un'altra risposta più efficiente di questa (per ordini di grandezza) , e se si lavora solo con due numeri interi, c'è un'altra risposta qui più efficiente di quella (per ordini di grandezza) . È conveniente comunque (ma probabilmente tralascerei l'array di shell, personalmente) .
Mikeserv,

1
Questo può essere fatto senza array come segue:numbers="1 4 3 5 7 1 10 21 8"; echo $numbers | tr ' ' "\n" | sort -rn | head -n 1
ngreen

1
Un approccio più efficiente è probabilmente questo:max=0; for x in $numbers ; do test $x -gt $max && max=$x ; done
ngreen

6

È possibile definire una libreria di funzioni matematiche predefinite per bce quindi utilizzarle nella riga di comando.

Ad esempio, includere quanto segue in un file di testo come ~/MyExtensions.bc:

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

Ora puoi chiamare bctramite:

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

Cordiali saluti, ci sono funzioni di libreria matematica gratuite come questa disponibile online.

Utilizzando quel file, puoi facilmente calcolare funzioni più complicate come GCD:

> echo 'gcd (60,54)' | bc ~/extensions.bc -l
6

Se non sbaglio, tali funzioni possono anche essere compilate con l'eseguibile, se necessario. Penso che la maggior parte dei bcs siano solo dcfrontend fino ad oggi, anche se GNUbc non è più tale (ma GNU dce GNU bccondividono una quantità prodigiosa della loro base di codice) . Comunque, questa potrebbe essere la migliore risposta qui.
Mikeserv,

Per chiamarlo comodamente all'interno del file di uno script di shell, è possibile reindirizzare anche la definizione della funzione bc, subito prima della chiamata della funzione. Nessun secondo file necessario allora :)
tanius

5

Troppo a lungo per un commento:

Mentre puoi fare queste cose, ad esempio con le combo sort | heado sort | tail, sembra piuttosto non ottimale sia dal punto di vista delle risorse che da quello dell'errore. Per quanto riguarda l'esecuzione, la combinazione significa generare 2 processi solo per controllare due linee. Sembra un po 'eccessivo.

Il problema più serio è che nella maggior parte dei casi è necessario sapere che l'input è sano, ovvero contiene solo numeri. La soluzione di @ glennjackmann risolve abilmente questo, dal momento che printf %ddovrebbe riscuotere valori non interi. Non funzionerà neanche con i float (a meno che non si cambi l'identificatore di formato in %f, dove si verificheranno problemi di arrotondamento).

test $1 -gt $2 ti darà l'indicazione se il confronto non è riuscito o meno (lo stato di uscita di 2 significa che si è verificato un errore durante il test. Poiché di solito si tratta di una shell integrata, non viene generato alcun processo aggiuntivo - stiamo parlando di centinaia esecuzione più veloce. Funziona solo con numeri interi.

Se ti capita di dover confrontare un paio di numeri in virgola mobile, l'opzione interessante potrebbe essere bc:

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

sarebbe l'equivalente di test $1 -gt $2, e usando in in shell:

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

è ancora quasi 2,5 volte più veloce di printf | sort | head(per due numeri).

Se puoi fare affidamento sulle estensioni GNU in bc, allora puoi anche usare la read()funzione per leggere i numeri direttamente nello bcscript.


I miei pensieri esattamente - Lo stavo solo risolvendo, ma mi hai battuto: dc -e "${max}sm[z0=?dlm<Mps0lTx]ST[?z0!=Tq]S?[s0lm]SMlTx"- oh, tranne che dcfa tutto (tranne l'eco, anche se potrebbe) - legge lo stdin e stampa uno $maxo il numero di input a seconda di quale è più piccolo. Ad ogni modo, non mi interessa davvero spiegarlo e la tua risposta è migliore di quella che avrei scritto. Quindi, abbi il mio voto, per favore.
Mikeserv,

@mikeserv effettivamente avere uno dcscript spiegato sarebbe davvero bello, RPN non è visto così spesso in questi giorni.
peterph,

Notazione polacca inversa (nota anche come notazione Postfix). Inoltre, se dcpuò fare l'I / O da solo, sarebbe ancora più elegante di.
peterph,

4

Per ottenere il valore maggiore di $ ae $ b, utilizzare questo:

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

Ma hai bisogno di qualcosa intorno a quello, probabilmente non intendi eseguire il numero, quindi per visualizzare il valore maggiore dei due usa "echo"

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

Quanto sopra si adatta perfettamente a una funzione shell, ad es

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

Per assegnare il maggiore dei due alla variabile, utilizzare questa versione modificata:

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

o usa la funzione definita:

biggest=$( max $a $b )

La variazione della funzione offre anche la possibilità di aggiungere un controllo degli errori di input in modo ordinato.

Per restituire il massimo di due numeri decimali / in virgola mobile che è possibile utilizzare awk

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

EDIT: Usando questa tecnica puoi creare una funzione "limit" che agisce al contrario secondo la tua modifica / nota. Questa funzione restituirà la parte inferiore delle due, ad esempio:

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

Mi piace mettere le funzioni di utilità in un file separato, chiamarlo myprogram.funcse usarlo in uno script come segue:

#!/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 fa ancora quello che hai fatto, e la tua versione, anche se è più dettagliata, è altrettanto efficiente.

La forma più compatta non è davvero migliore, ma previene il disordine negli script. Se hai molti costrutti if-then-else-fi semplici, lo script si espande rapidamente.

Se si desidera riutilizzare il controllo per numeri più grandi / più piccoli più volte in un singolo script, inserirlo in una funzione. Il formato della funzione semplifica il debug e il riutilizzo e consente di sostituire facilmente quella parte dello script, ad esempio con un comando awk per essere in grado di gestire numeri decimali non interi.

Se si tratta di un caso monouso, basta codificarlo in linea.


4

È possibile definire una funzione come

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

Chiamalo come maxnum 54 42ed echeggia 54. È possibile aggiungere informazioni di convalida all'interno della funzione (come due argomenti o numeri come argomenti) se lo si desidera.


La maggior parte delle shell non esegue aritmetiche in virgola mobile. Ma funziona per numeri interi.
Orione,

1
Questa funzione è inutilmente incompatibile con POSIX. Passa function maxnum {a maxnum() {e funzionerà per molte più shell.
Charles Duffy,

2

Da uno script shell, esiste un modo per utilizzare qualsiasi metodo statico pubblico Java (e ad esempio Math.min () ). Da bash su Linux:

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

Ciò richiede Java Shell Bridge https://sourceforge.net/projects/jsbridge/

Molto veloce, perché le chiamate del metodo vengono convogliate internamente ; nessun processo richiesto.


0

La maggior parte delle persone lo farebbe sort -n input | head -n1(o la coda), è abbastanza buono per la maggior parte delle situazioni di script. Tuttavia, questo è un po 'goffo se hai numeri in una riga anziché in una colonna: devi stamparlo in un formato appropriato ( tr ' ' '\n'o qualcosa di simile).

Le conchiglie non sono esattamente l'ideale per l'elaborazione numerica, ma puoi semplicemente inserire in qualche altro programma che è meglio in esso. A seconda delle tue preferenze, puoi chiamare al massimo dc(un po 'offuscato, ma se sai cosa stai facendo, va bene - vedi la risposta di Mikeserv), oppure awk 'NR==1{max=$1} {if($1>max){max=$1}} END { print max }'. O forse perlo pythonse preferisci. Una soluzione (se si è disposti a installare e utilizzare software meno conosciuti) sarebbe ised(soprattutto se i dati si trovano su un'unica riga: è sufficiente farlo ised --l input.dat 'max$1').


Perché stai chiedendo due numeri, questo è eccessivo. Questo dovrebbe essere sufficiente:

python -c "print(max($j,$k))"

1
Potrebbe andare meglio se hai usato sys.argv:python2 -c 'import sys; print (max(sys.argv))' "$@"
muru,

1
Gli argomenti che sort + headsono eccessivi ma pythonnon vengono calcolati.
Mikeserv,

Tutti i metodi sopra la linea sono progettati per gestire enormi serie di numeri e suggeriscono esplicitamente questo tipo di utilizzo (lettura da una pipe o un file). min / max per 2 argomenti è una domanda che si sente diversamente: richiede una funzione anziché uno stream. Volevo solo dire che l'approccio al flusso è eccessivo: lo strumento che usi è arbitrario, l'ho usato solo pythonperché è pulito.
Orione,

Definirei questa soluzione più semplice , ma potrebbe essere perché sono un pythonfanatico (o perché non richiede un fork e un interprete gigantesco aggiuntivo) . O forse entrambi.
Mikeserv,

@mikeserv Lo userei anche se sapessi che erano numeri interi. Tutte queste soluzioni che ho citato sono sotto ipotesi che i numeri possono essere galleggianti - bash non fa in virgola mobile e, a meno zsh è la shell nativa, è avrà bisogno di una forchetta (e forse un coltello).
Orione,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.