Czy istnieje polecenie uniksowe, które podaje minimum / maksimum dwóch liczb?


37

Szukałem polecenia ograniczającego liczbę odczytanych z stdin.

W tym celu napisałem mały skrypt (mile widziana krytyka), ale zastanawiałem się, czy nie było standardowego polecenia dla tego prostego i (jak sądzę) powszechnego przypadku użycia.

Mój skrypt, który znajduje minimum dwie liczby:

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

Odpowiedzi:


20

Możesz porównać tylko dwie liczby z dcpodobnymi:

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

... gdzie "$1"jest twoja maksymalna wartość i "$2"liczba, którą wydrukowałbyś, gdyby była mniejsza niż "$1". To także wymaga GNU dc- ale możesz zrobić to samo, przenośnie:

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

W obu powyższych przypadkach możesz ustawić dokładność na wartość inną niż 0 (domyślnie), np ${desired_precision}k. W obu przypadkach konieczne jest również sprawdzenie, czy obie wartości są zdecydowanie liczbami, ponieważ dcmogą nawiązywać system()połączenia z !operatorem.

Za pomocą następującego małego skryptu (i następnego) powinieneś również zweryfikować dane wejściowe - grep -v \!|dclub coś w celu solidnej obsługi dowolnych danych wejściowych. Powinieneś także wiedzieć, że dcinterpretuje liczby ujemne z _przedrostkiem, a nie -przedrostkiem - ponieważ ten ostatni jest operatorem odejmowania.

Poza tym, ten skrypt dcodczyta tyle kolejnych \nliczb oddzielonych ewline, ile chciałbyś go podać, i wydrukuje dla każdej $maxwartości lub danych wejściowych, w zależności od tego, która z nich jest mniejsza:

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

Więc ... każda z tych [kwadratowych nawiasach ]połacie jest dc ciąg obiekt, który jest Saved każdy z odpowiedniej tablicy - każdy jeden T, ?albo M. Poza kilkoma innymi rzeczami, które dcmogą mieć związek z łańcuchem , może również xdziałać jako makro. Jeśli dobrze to uporządkujesz, w pełni funkcjonalny mały dcskrypt jest składany w prosty sposób.

dcdziała na stosie . Wszystkie obiekty wejściowe są układane jeden na drugim - każdy nowy obiekt wejściowy popycha ostatni górny obiekt i wszystkie obiekty poniżej niego na stosie o jeden podczas dodawania. Większość odwołań do obiektu odnosi się do górnej wartości stosu, a większość odnośników wyskakuje na górze stosu (co powoduje pociągnięcie wszystkich obiektów pod nim o jeden) .

Oprócz głównego stosu istnieje również (co najmniej) 256 tablic, a każdy element tablicy ma własny stos. Nie używam dużo tego tutaj. Po prostu przechowuję ciągi, jak wspomniano, więc mogę lje xprzesadzić, kiedy chcę i warunkowo je wyrównać, i spodarłem $maxwartość w górnej części mtablicy.

W każdym razie ta niewielka część dcrobi w dużej mierze to, co robi twój skrypt powłoki. Używa opcji GNU-ism -e- jak dczwykle bierze swoje parametry ze standardowego wejścia - ale możesz zrobić to samo:

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

... gdyby $scriptwyglądało jak wyżej.

Działa jak:

  • lTx- To lprzysiada i xwylicza makro zapisane na górze T (chyba do testu - zwykle wybieram te nazwy arbitralnie) .
  • z 0=?- Test następnie sprawdza głębokość stosu w /, za jeśli stos jest pusty (odczyt: zawiera 0 obiektów) , wywołuje ?makro.
  • ? z0!=T q- ?Makro jest nazwane dla ? dcwbudowanego polecenia, które odczytuje wiersz wejścia ze standardowego wejścia, ale dodałem zdo niego również kolejny test głębokości stosu, aby mógł korzystać qz całego małego programu, jeśli wciągnie pusty wiersz lub uderzy w EOF. Ale jeśli !nie, i zamiast tego pomyślnie zapełni stos, wywołuje Tponownie est.
  • d lm<M- Test następnie dzastosuje górę stosu i porówna go $max (tak jak w pamięci m) . Jeśli mjest to mniejsza wartość, dcwywołuje Mmakro.
  • s0 lm- Mpo prostu wyskakuje z góry stosu i zrzuca go do manekina skalarnego 0- po prostu tani sposób na zerwanie stosu. To także ponownie lowija msię przed powrotem do Test.
  • p- Oznacza to, że jeśli mjest mniejszy niż aktualny wierzchołek stosu, to mzastępuje go (w dkażdym razie jego egzemplarz) i jest tutaj ppodszyty, w przeciwnym razie nie zostanie podany, a wszystko, co zostało wprowadzone, jest ppodszyte.
  • s0- Następnie (ponieważ pnie wyskakuje stos) ponownie wrzucamy górę stosu 0, a następnie ...
  • lTx- rekurencyjnie load Test jeszcze raz, a potem xponownie.

Abyś mógł uruchomić ten mały fragment kodu i interaktywnie wpisywać liczby w swoim terminalu i dcwydrukować ci albo wpisany numer, albo wartość, $maxjeśli wpisany numer był większy. Akceptuje również dowolny plik (taki jak potok) jako standardowe wejście. Będzie kontynuował pętlę odczytu / porównania / drukowania, aż napotka pustą linię lub EOF.

Kilka uwag na ten temat - napisałem to tylko w celu naśladowania zachowania w twojej funkcji powłoki, więc solidnie obsługuje tylko jedną liczbę w wierszu. dcmoże jednak obsłużyć tyle liczb oddzielonych spacjami w wierszu, ile chcesz w to rzucić. Jednak ze względu na stos, ostatnia liczba w linii kończy się jako pierwsza, na której działa, i tak, jak napisano, dcwydrukowałby swoje wyniki w odwrotnej kolejności, jeśli wydrukowałbyś / wpisała więcej niż jedną liczbę w linii. obsłużyć to, aby zapisać linię w tablicy, a następnie ją przetworzyć.

Lubię to:

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"

Ale ... nie wiem, czy chcę to wyjaśnić tak samo głęboko. Wystarczy powiedzieć, że podczas dcodczytu każdej wartości na stosie przechowuje albo swoją wartość, albo $maxwartość w indeksowanej tablicy, a gdy wykryje, że stos jest ponownie pusty, drukuje każdy indeksowany obiekt przed próbą odczytania innego linia wprowadzania.

I tak, podczas gdy pierwszy skrypt robi ...

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

Drugi robi:

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

Możesz obsługiwać zmiennoprzecinkowe o dowolnej dokładności, jeśli ustawisz je najpierw za pomocą kpolecenia. I możesz niezależnie zmieniać iradia nput lub output - co czasami może być przydatne z powodów, których możesz się nie spodziewać. Na przykład:

echo 100000o 10p|dc
 00010

... która najpierw ustawia podstawową dcwartość wyjściową na 100000, a następnie drukuje 10.


3
+1 za brak pojęcia, co się stało po dwukrotnym przeczytaniu. Będę musiał poświęcić trochę czasu, aby zagłębić się w to.
Minix

@Mixix - meh - nie musisz zagłębiać się w najstarszy język programowania Unix, jeśli uznasz go za mylący. Może po prostu od dcczasu do czasu wypuszcza kilka liczb, aby utrzymać go na palcach.
mikeserv

1
@mikeserv Jest dla mnie za późno. Mam nadzieję, że przyszłe pokolenie weźmie mnie za przestrogę. Wszędzie nawiasy kwadratowe i litery ...
Minix

@Mixix - co masz na myśli? Poszedłeś po to? Bardzo dobrze - dcjest kapryśną bestią, ale może być najszybszym i najdziwniej działającym wspólnym narzędziem w dowolnym systemie uniksowym. Po sparowaniu w / sedmoże robić niezwykłe rzeczy. Bawiłem się nim ddostatnio, aby zastąpić potworność readline. Oto mała próbka niektórych rzeczy, które robiłem. Robienie revin dcjest prawie dziecinnie proste.
mikeserv

1
@Mixix - ostrożnie z nawiasami klamrowymi. Nie ma sposobu, aby umieścić nawias kwadratowy w ciągu - najlepiej, co możesz zrobić [string]P91P93P[string]P. Mam więc trochę z sedwas, które mogą się przydać: sed 's/[][]/]P93]&[1P[/g;s/[]3][]][[][1[]//g'które zawsze powinny poprawnie zamieniać kwadraty na nawias zamykający łańcuch, następnie a P, następnie dziesiętną wartość ascii kwadratu i inną P; następnie otwarty [nawias kwadratowy, aby kontynuować ciąg. Nie wiem, czy masz problemy z dckonwersją ciągów znaków / liczb, ale - szczególnie w połączeniu z od- może być całkiem fajna.
mikeserv

88

Jeśli wiesz, że mają do czynienia z dwóch liczb całkowitych aa b, to te proste powłoki arytmetyczne rozszerzenia za pomocą operatora potrójny są wystarczające, aby dać maksymalną liczbowej:

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

i numeryczne min:

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

Na przykład

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

Oto skrypt powłoki, który to pokazuje:

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

Niezła odpowiedź. Proszę, drobny mod: czy można tego również użyć dla „> =”?
Sopalajo de Arrierez

@SopalajodeArrierez Nie jestem do końca pewien, co masz na myśli. Możesz także zrobić max=$(( a >= b ? a : b )), ale wynik jest całkowicie taki sam - jeśli aib są równe, to tak naprawdę nie ma znaczenia, który z nich zostanie zwrócony. Czy o to pytasz?
Cyfrowa trauma

Rzeczywiście, dziękuję, uraz cyfrowy. Zastanawiałem się tylko, czy operator logiczny "> =" był tutaj możliwy.
Sopalajo de Arrierez

@SopalajodeArrierez if (( a >= b )); then echo a is greater than or equal to b; fi- czy o to prosisz? (zwróć uwagę na użycie (( ))tutaj zamiast $(( )))
Digital Trauma

Ach tak, OK. Teraz rozumiem. Nie wiem dużo o rozszerzaniu powłoki, więc zwykle mylę się między warunkami. Jeszcze raz dziękuję
Sopalajo de Arrierez

25

sorti headmoże to zrobić:

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

2
Zauważ, że dzieje się tak, O(n log(n))podczas gdy wydajna implementacja max byłaby O(n). Jest to jednak nasze małe znaczenie n=2, ponieważ pojawienie się dwóch procesów jest znacznie większe.
Lie Ryan

1
Chociaż prawda, @ glenn-jackman, nie jestem pewien, czy to ważne, biorąc pod uwagę pytanie. Nie było prośby o najbardziej efektywny sposób na zrobienie tego. Myślę, że pytanie dotyczyło raczej wygody.
David Hoelzer

1
@DavidHoelzer - to nie jest najskuteczniejszy sposób, aby to zrobić, nawet wśród odpowiedzi tutaj. Jeśli pracujesz z zestawami liczb, istnieje tutaj co najmniej jedna inna odpowiedź, która jest bardziej wydajna (o rzędy wielkości) , a jeśli pracujesz tylko z dwiema liczbami całkowitymi, jest tutaj inna odpowiedź bardziej wydajna (o rzędy wielkości) . Jest to jednak wygodne (ale prawdopodobnie osobiście pominąłbym tablicę powłok) .
mikeserv

1
Można to zrobić bez tablic w następujący sposób:numbers="1 4 3 5 7 1 10 21 8"; echo $numbers | tr ' ' "\n" | sort -rn | head -n 1
ngreen

1
Bardziej wydajne podejście jest prawdopodobnie następujące:max=0; for x in $numbers ; do test $x -gt $max && max=$x ; done
ngreen

6

Możesz zdefiniować bibliotekę predefiniowanych funkcji matematycznych, bca następnie użyć ich w wierszu poleceń.

Na przykład w pliku tekstowym, takim jak ~/MyExtensions.bc:

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

Teraz możesz zadzwonić bc:

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

Do Twojej dyspozycji są bezpłatne funkcje biblioteki matematycznej, takie jak ta dostępna online.

Korzystając z tego pliku, możesz łatwo obliczyć bardziej skomplikowane funkcje, takie jak GCD:

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

Jeśli się nie mylę, takie funkcje można również skompilować z plikiem wykonywalnym, jeśli to konieczne. Myślę, że większość z nich bcto po prostu dcnakładki do dnia dzisiejszego, nawet jeśli GNUbc już nie jest takie (ale GNU dci GNU bcdzielą się ogromną ilością swojej bazy kodów) . W każdym razie może to być najlepsza odpowiedź tutaj.
mikeserv

Aby wygodnie wywołać to w pliku skryptu powłoki, możesz przesłać definicję funkcji bcrównież bezpośrednio przed wywołaniem funkcji. Nie potrzeba drugiego pliku :)
tanius

5

Za długo na komentarz:

Chociaż możesz robić te rzeczy np. Za pomocą kombinacji sort | headlub sort | tail, wydaje się to raczej nieoptymalne zarówno pod względem zasobów, jak i obsługi błędów. Jeśli chodzi o wykonanie, kombinacja oznacza odrodzenie 2 procesów tylko w celu sprawdzenia dwóch linii. To wydaje się trochę przesadzone.

Poważniejszym problemem jest to, że w większości przypadków trzeba wiedzieć, że dane wejściowe są rozsądne, to znaczy zawierają tylko liczby. Rozwiązanie @ glennjackmann sprytnie rozwiązuje ten problem, ponieważ printf %dpowinno obijać się liczbami innymi niż całkowite. To również nie będzie działać z liczbami zmiennoprzecinkowymi (chyba że zmienisz specyfikator formatu na %f, gdzie wystąpią problemy z zaokrąglaniem).

test $1 -gt $2 pokaże, czy porównanie się nie powiodło (status wyjścia 2 oznacza błąd podczas testu. Ponieważ zwykle jest to wbudowana powłoka, nie powstaje żaden dodatkowy proces - mówimy o kolejności setek razy szybsze wykonanie. Działa jednak tylko z liczbami całkowitymi.

Jeśli musisz porównać kilka liczb zmiennoprzecinkowych, ciekawą opcją może być bc:

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

byłoby równoważne test $1 -gt $2i użycie w powłoce:

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

jest wciąż prawie 2,5 razy szybszy niż printf | sort | head(dla dwóch liczb).

Jeśli możesz polegać na rozszerzeniach GNU w bc, możesz także użyć read()funkcji do odczytania liczb bezpośrednio do bcsript.


Dokładnie moje myśli - właśnie to prasowałem, ale pobiłeś mnie do tego: dc -e "${max}sm[z0=?dlm<Mps0lTx]ST[?z0!=Tq]S?[s0lm]SMlTx"- och, tyle że dcrobi to wszystko (z wyjątkiem echa, chociaż mogłoby) - czyta standardowe $maxwejście i wypisuje albo numer wejściowy, zależnie od tego jest mniejszy. W każdym razie tak naprawdę nie chcę tego wyjaśniać, a twoja odpowiedź jest lepsza, niż zamierzałam napisać. Więc proszę o moje głosowanie.
mikeserv

@ mikeserv faktycznie mając wyjaśniony dcskrypt byłby naprawdę fajny, RPN nie jest obecnie tak często widywany.
peterph

Odwrotna notacja polska (inaczej notacja Postfix). Dodatkowo, jeśli dcmoże samodzielnie wykonać operacje we / wy, byłoby to jeszcze bardziej eleganckie niż.
peterph

4

Aby uzyskać większą wartość $ a i $ b, użyj tego:

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

Ale potrzebujesz czegoś wokół tego, prawdopodobnie nie chcesz wykonać liczby, więc aby wyświetlić większą wartość dwóch, użyj „echo”

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

Powyższe ładnie pasuje do funkcji powłoki, np

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

Aby przypisać większą z dwóch do zmiennej, użyj tej zmodyfikowanej wersji:

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

lub użyj zdefiniowanej funkcji:

biggest=$( max $a $b )

Odmiana funkcji daje również możliwość dokładnego dodania sprawdzania błędów wejściowych.

Aby zwrócić maksymalnie dwie liczby dziesiętne / zmiennoprzecinkowe, możesz użyć awk

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

EDYCJA: Za pomocą tej techniki można utworzyć funkcję „limit”, która działa w drugą stronę, zgodnie z edycją / notatką. Ta funkcja zwróci dolną z dwóch, np .:

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

Lubię umieszczać funkcje narzędzia w osobnym pliku, wywoływać go myprogram.funcsi używać w skrypcie w następujący sposób:

#!/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 nadal robi to, co zrobiłeś, a twoja wersja, chociaż jest bardziej szczegółowa, jest równie wydajna.

Bardziej kompaktowa forma nie jest tak naprawdę lepsza, ale zapobiega bałaganowi w twoich skryptach. Jeśli masz wiele prostych konstrukcji if-then-else-fi, skrypt szybko się rozwija.

Jeśli chcesz ponownie użyć sprawdzania większych / mniejszych liczb wiele razy w jednym skrypcie, umieść je w funkcji. Format funkcji ułatwia debugowanie i ponowne użycie oraz pozwala łatwo zastąpić tę część skryptu, na przykład komendą awk, aby móc obsługiwać liczby dziesiętne niecałkowite.

Jeśli jest to pojedynczy przypadek użycia, po prostu koduj go w linii.


4

Możesz zdefiniować funkcję jako

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

Nazwij to jak maxnum 54 42i echo 54. Możesz dodać informacje o sprawdzeniu poprawności do funkcji (takie jak dwa argumenty lub liczby jako argumenty), jeśli chcesz.


Większość powłok nie wykonuje arytmetyki zmiennoprzecinkowej. Ale to działa dla liczb całkowitych.
orion,

1
Ta funkcja jest niepotrzebnie niezgodna z POSIX. Zmień function maxnum {na maxnum() {i będzie działać na znacznie więcej pocisków.
Charles Duffy,

2

Ze skryptu powłoki istnieje sposób na użycie dowolnej publicznej metody statycznej Java (i na przykład Math.min () ). Z bash na Linuksie:

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

Wymaga to Java Shell Bridge https://sourceforge.net/projects/jsbridge/

Bardzo szybko, ponieważ wywołania metod są przetwarzane wewnętrznie ; nie wymaga procesu.


0

Większość ludzi po prostu by to zrobiła sort -n input | head -n1(lub ogon), co jest wystarczające w większości sytuacji skryptowych. Jest to jednak nieco niezdarne, jeśli masz liczby w wierszu zamiast w kolumnie - musisz wydrukować go w odpowiednim formacie ( tr ' ' '\n'lub coś podobnego).

Powłoki nie są idealnie idealne do przetwarzania numerycznego, ale można łatwo po prostu podłączyć do innego programu, który jest w tym lepszy. W zależności od własnych preferencji możesz wywołać maksimum dc(trochę zaciemnione, ale jeśli wiesz, co robisz, jest w porządku - patrz odpowiedź mikeserv) lub awk 'NR==1{max=$1} {if($1>max){max=$1}} END { print max }'. Lub ewentualnie perllub pythonjeśli wolisz. Jednym rozwiązaniem (jeśli chcesz zainstalować i używać mniej znanego oprogramowania) byłoby ised(szczególnie jeśli twoje dane są w jednym wierszu: wystarczy to zrobić ised --l input.dat 'max$1').


Ponieważ prosisz o dwie liczby, to wszystko jest przesada. To powinno wystarczyć:

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

1
Może być lepiej, jeśli użyjesz sys.argv:python2 -c 'import sys; print (max(sys.argv))' "$@"
muru

1
Argumenty, które sort + headsą przesadne, ale pythonnie są obliczane.
mikeserv

Wszystkie metody powyżej linii są zaprojektowane do obsługi ogromnych zestawów liczb i wyraźnie sugerują tego rodzaju użycie (czytanie z potoku lub pliku). min / max dla 2 argumentów to pytanie, które wydaje się inne - wywołuje funkcję zamiast strumienia. Chodziło mi tylko o to, że podejście strumieniowe to przesada - narzędzie, którego używasz, jest arbitralne, po prostu użyłem, pythonponieważ jest schludne.
orion,

To sugerowane rozwiązanie nazwałbym starszym , ale może to być spowodowane tym, że jestem pythonbigotem (lub ponieważ nie wymaga on widelca i dodatkowego gigantycznego tłumacza) . A może jedno i drugie.
mikeserv

@ mikeserv użyłbym tego również, gdybym wiedział, że są to liczby całkowite. Wszystkie te rozwiązania I wymienione są przy założeniu, że liczby mogą być pływaki - bash nie zrobić zmiennoprzecinkowych i chyba zsh jest ojczystym shell, to będzie potrzebował widelca (i ewentualnie nóż).
orion,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.