Чи існує команда unix, яка дає мінімум / максимум двох чисел?


37

Я шукав команду для обмеження читання, прочитаного в stdin.

Я написав для цього невеликий сценарій (критика вітається), але мені було цікаво, чи не існує стандартної команди для цього, простого і (я думаю) загального випадку використання.

Мій сценарій, який знаходить мінімум два числа:

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

Відповіді:


20

Можна порівняти лише два числа з dcтакими:

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

... де "$1"ваше максимальне значення та "$2"число, яке ви б надрукували, якщо воно менше "$1". Для цього також потрібен GNU dc- але ви можете робити те саме, що портативно, як:

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

В обох вищезгаданих випадках ви можете встановити точність, відмінне від 0 (за замовчуванням) , як ${desired_precision}k. Для обох також важливо переконатися, що обидва значення безумовно номери, тому що dcможуть system()телефонувати з !оператором.

За допомогою наступного маленького сценарію (і наступного) ви також повинні перевірити вхід, як-то grep -v \!|dcабо щось, щоб надійно обробити довільний ввід. Ви також повинні знати, що dcінтерпретує від'ємні числа з _префіксом, а не -префіксом - тому що останній є оператором віднімання.

Крім цього, за допомогою цього скрипту dcбуде прочитано стільки послідовних \nномерів, що відокремлюються електронною лінією, скільки ви б хотіли його надати, і надрукуйте для кожного $maxзначення або вхід, залежно від того, що менше:

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

Так ... кожен з цих [квадратних дужках ]пристроїв є dc рядок об'єкт, Saved кожного до відповідного масиву - будь-який один з T, ?або M. Окрім кількох інших речей, які dcможуть бути виконані зі струною , вона також може виконувати її xяк макрос. Якщо ви правильно це влаштуєте, повністю функціонуючий маленький dcсценарій збирається досить просто.

dcпрацює на стек . Усі вхідні об'єкти укладаються кожен на останній - кожен новий вхідний об'єкт виштовхує останній верхній об'єкт і всі об'єкти під ним вниз на стеку по одному, як він додається. Більшість посилань на об'єкт є до верхнього значення стека, і більшість посилань поп , що вершини стека (який тягне всі об'єкти під ним до одного) .

Крім основного стека, є також (щонайменше) 256 масивів, і кожен елемент масиву має стек все своє. Я не використовую багато цього тут. Я просто зберігаю рядки, як згадувалося, щоб я міг lїх завищувати, коли хотів, і виконувати xїх умовно, і я sзірвав $maxзначення у верхній частині mмасиву.

У будь-якому разі, ця дрібниця dcробить багато в чому те, що робить ваш оболонка-скрипт. Він використовує параметр GNU-ism -e- як dcправило, приймає його параметри від стандартного входу - але ви можете зробити те саме, що:

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

... якщо це $scriptбуло схоже на вищезазначений шматочок.

Це працює так:

  • lTx- Це нерозумно lта електронно xвиконує макрос, що зберігається у верхній частині T (для тесту, я думаю, я зазвичай вибираю ці назви довільно) .
  • z 0=?- Test тоді тестує глибину стека w /, zі якщо стек порожній (read: вміщує 0 об'єктів), він викликає ?макрос.
  • ? z0!=T q- ?Макрос названий ? dcвбудованою командою, яка зчитує рядок введення з stdin, але я також додав ще один zтест на глибину стека, щоб він міг qкористуватися цілою маленькою програмою, якщо вона потягне в порожній рядок або натисне EOF. Але якщо цього не відбувається !і натомість успішно заповнює стек, він Tзнову викликає est.
  • d lm<M- Test потім dускладнить верхню частину стека та порівняє її $max (як це зберігається m) . Якщо mце менше значення, dcвикликає Mмакрос.
  • s0 lm- Mпросто спливає верхівку стека і скидає його на манекен скаляра 0- просто дешевий спосіб вискочити стек. Крім того, він знову lопускається, mперш ніж повернутися в Test.
  • p- Це означає, що якщо mменше, ніж поточна вершина стека, то mзамінює його ( dкопія його, все одно), і тут pпоширюється, інакше - ні, і все, що було введено pзамість цього.
  • s0- Потім (оскільки pне з'являється стек) ми знову скидаємо верхню частину стека 0, а потім ...
  • lTx- рекурсивно lоад Tще раз, а потім виконувати xце знову.

Таким чином, ви можете запустити цей маленький фрагмент та інтерактивно вводити номери на своєму терміналі та dcнадрукувати вам або число, яке ви ввели, або значення, $maxякщо число, яке ви ввели, було більшим. Він також приймає будь-який файл (наприклад, трубу) як стандартний вхід. Він продовжуватиме цикл читання / порівняння / друку, поки не зустріне порожній рядок або EOF.

Деякі зауваження з цього приводу - я написав це просто для імітації поведінки у вашій функції оболонки, тому вона лише надійно обробляє одне число на рядок. dcоднак можна обробити стільки пробільних цифр на рядок, скільки ви б хотіли кинути на нього. Однак через його стек останній номер у рядку накручується як перший, над яким він працює, і так, як написано, dcдрукував би його результат у зворотному порядку, якщо ви надрукували / набрали більше одного числа на рядок у ньому. Правильний спосіб обробляти, тобто зберігати рядок у масиві, а потім працювати.

Подобається це:

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"

Але ... я не знаю, чи хочу це пояснити настільки ж глибоко. Досить сказати, що, як dcчитається в кожному значенні на стеку, він зберігає або його значення, або $maxзначення в індексованому масиві, і, коли він виявляє стек знову порожній, він друкує кожен індексований об'єкт, перш ніж намагатися прочитати інший рядок введення.

І так, поки перший сценарій робить ...

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

Другий:

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

Ви можете обробляти поплавці довільної точності, якщо спочатку встановите його kкомандою. І ви можете змінювати iрадіуси nput або output самостійно - що іноді може бути корисним з причин, яких ви можете не очікувати. Наприклад:

echo 100000o 10p|dc
 00010

... який спочатку встановлює dcвихідний радіус до 100000, потім друкує 10.


3
+1 за те, що не має уявлення про те, що щойно сталося, прочитавши його двічі. Доведеться зайняти свій час, щоб заглибитись у це.
Мінікс

@Minix - meh - не потрібно заглиблюватися в найдавнішу мову програмування Unix, якщо ви вважаєте це заплутаним. Можливо, просто перекажіть кілька номерів dcкожен раз за деякий час, щоб тримати його на ногах.
mikeserv

1
@mikeserv Мені вже пізно. Я сподіваюся, що майбутні покоління сприймуть мене як застереження. Квадратні дужки та букви скрізь ...
Minix

@Minix - що ти маєш на увазі? Ви пішли на це? Дуже добре - dcце вигадливий звір, але це може бути просто найшвидша і дивно здатна загальна програма у будь-якій системі Unix. Якщо він працює в парі, sedвін може робити надзвичайні речі. Я граю з цим і ddостаннім часом, щоб я міг замінити жахливість, яка є readline. Ось крихітний зразок деяких речей, які я робив. Виконання revін dcмайже дитяча гра.
mikeserv

1
@Minix - обачливі ж / дужки, хоча. Немає можливості вставити квадратну дужку в рядок - найкраще, що ти можеш зробити [string]P91P93P[string]P. Отож, у мене ця sedкористь може бути корисною: sed 's/[][]/]P93]&[1P[/g;s/[]3][]][[][1[]//g'яка завжди повинна правильно замінити квадрати на дужку закриття рядка, потім a P, потім десяткове значення квадрата ascii та інше P; потім відкриту [квадратну дужку для продовження рядка. Не знаю, якщо ви заплуталися навколо dcможливостей перетворення рядків / числових перетворень, але - особливо в поєднанні з w / od- це може бути дуже цікаво.
mikeserv

87

Якщо ви знаєте , що ви маєте справу з двома цілими числами aі b, то ці простих оболонки арифметичних розкладання з використанням троичного оператора достатньо , щоб дати чисельний макс:

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

і числовий хв:

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

Напр

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

Ось сценарій оболонки, що демонструє це:

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

Гарна відповідь. Будь ласка, мінорний мод: чи можна це використовувати і для "> ="?
Сопалахо де Аррієрес

@SopalajodeArrierez Я не зовсім впевнений, що ти маєш на увазі. Ви також можете зробити max=$(( a >= b ? a : b )), але результат абсолютно однаковий - якщо a і b рівні, то насправді не має значення, який з них повертається. Це ти просиш?
Цифрова травма

Дійсно, дякую, DIgital Trauma. Мені просто було цікаво, чи можливий булевий оператор "> =" тут.
Sopalajo de Arrierez

@SopalajodeArrierez if (( a >= b )); then echo a is greater than or equal to b; fi- це те, про що ти просиш? (зверніть увагу на використання (( ))тут замість $(( )))
Digital Trauma

Ага, так, гаразд. Тепер я розумію. Я мало знаю про розширення оболонки, тому зазвичай я плутаюсь між умовами. Ще раз дякую
Sopalajo de Arrierez

24

sortі headможе це зробити:

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

2
Зауважте, що це O(n log(n))хоча ефективна реалізація макс O(n). Нам це мало значення n=2, оскільки нерест двох процесів набагато більший.
Лежати Райан

1
Хоча правда, @ glenn-jackman, я не впевнений, що це має значення при цьому. Не було запиту про найефективніший спосіб це зробити. Я думаю, питання стосувалося зручності.
Девід Хольцер

1
@DavidHoelzer - це не найефективніший спосіб зробити це навіть серед запропонованих тут відповідей. Якщо працювати з наборами чисел, то тут принаймні одна відповідь, яка є більш ефективною, ніж ця (на порядки величини) , а якщо працювати лише з двома цілими числами, тут є ще одна відповідь, більш ефективна, ніж ця (на порядки величини) . Це хоч і зручно (але я, мабуть, особисто залишив би оболонку оболонки) .
mikeserv

1
Це можна зробити без масивів так:numbers="1 4 3 5 7 1 10 21 8"; echo $numbers | tr ' ' "\n" | sort -rn | head -n 1
ngreen

1
Більш ефективний підхід, мабуть, такий:max=0; for x in $numbers ; do test $x -gt $max && max=$x ; done
чорнозема

6

Ви можете визначити бібліотеку заздалегідь заданих математичних функцій для, bcа потім використовувати їх у командному рядку.

Наприклад, включіть у текстовий файл таке ~/MyExtensions.bc:

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

Тепер ви можете зателефонувати bc:

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

FYI, в Інтернеті є безкоштовні математичні функції, такі як ця .

Використовуючи цей файл, ви можете легко обчислити складніші функції, такі як GCD:

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

Якщо я не помиляюся, такі функції при необхідності можна також скласти разом із виконуваним файлом. Я думаю, що більшість bcs до цього дня є лише dcпередумови , навіть якщо GNUbc вже не є такою (але GNU dcта GNU bcділять величезну кількість своєї кодової бази) . У будь-якому випадку, це може бути найкращою відповіддю тут.
mikeserv

Щоб зручно викликати це у файлі сценарію оболонки, ви можете вказати визначення функції bcтакож, безпосередньо перед викликом функції. Тоді не потрібен другий файл :)
tanius

5

Занадто довго для коментаря:

Хоча ви можете робити це, наприклад, за допомогою комбо sort | headабо sort | tailкомбо, це здається досить неоптимальним як для ресурсів, так і для помилок. Що стосується виконання, комбо означає нерестування 2 процесів просто для перевірки двох рядків. Це здається трохи надмірним.

Більш серйозна проблема полягає в тому, що в більшості випадків вам потрібно знати, що вхід є здоровим, тобто містить лише числа. Рішення @ glennjackmann вміло вирішує це, оскільки printf %dповинно нести цілі числа. Він також не працюватиме з поплавками (якщо ви не зміните специфікатор формату на те %f, де у вас виникнуть проблеми із округленням).

test $1 -gt $2 дасть вам вказівку на те, що порівняння не вдалося чи ні (стан виходу 2 означає, що під час тестування сталася помилка. Оскільки це зазвичай вбудована оболонка, додаткового процесу не породили - ми говоримо про порядок сотень Хоча в рази швидше, проте працює лише з цілими числами.

Якщо вам потрібно порівняти пару чисел з плаваючою комою, цікавим варіантом може бути bc:

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

буде еквівалент test $1 -gt $2і використання в оболонці:

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

все ще майже в 2,5 рази швидше, ніж printf | sort | head(для двох чисел).

Якщо ви можете покластися на розширення GNU в bc, ви також можете скористатися read()функцією для читання чисел безпосередньо в bcкрипті.


Мої думки саме - я це саме прасував, але ти мене до цього побив: dc -e "${max}sm[z0=?dlm<Mps0lTx]ST[?z0!=Tq]S?[s0lm]SMlTx"- О, хіба що dcце все робить (крім відлуння, хоч і могло б) - воно читає stdin і друкує або $maxабо номер вводу залежно від того, який менший. У всякому разі, мені зовсім не хочеться це пояснювати, і ваша відповідь краще, ніж я писав. Отож, будь ласка, підсумуйте, будь ласка.
mikeserv

@mikeserv насправді пояснення dcсценарію було б дуже приємно, RPN не часто зустрічається в ці дні.
петерф

Зворотне польське позначення (також нотація Postfix). Плюс якщо ви dcможете зробити введення / виведення самостійно, це було б навіть елегантніше, ніж.
петерф

4

Для отримання більшого значення $ a і $ b використовуйте це:

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

Але вам потрібно щось навколо, напевно, ви не маєте на увазі виконувати число, тому для відображення більшого значення обох використовуйте "ехо"

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

Вищезазначене добре вписується у функцію оболонки, наприклад

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

Щоб призначити більшій з двох змінній, використовуйте цю змінену версію:

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

або скористайтеся визначеною функцією:

biggest=$( max $a $b )

Варіант функції також дає можливість акуратно додати перевірку помилок на вході.

Щоб повернути максимум два числа з десятковим / плаваючою комою, ви можете використовувати awk

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

РЕДАКТУВАННЯ: Використовуючи цю техніку, ви можете створити функцію "обмеження", яка працює навпаки відповідно до редагування / примітки. Ця функція поверне нижню з двох, наприклад:

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

Мені подобається розміщувати утилітні функції в окремому файлі, називати його myprogram.funcsта використовувати його в сценарії так:

#!/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 це все-таки робить те, що ви зробили, і ваша версія, хоча вона є більш багатослівною, так само ефективна.

Більш компактна форма насправді не краща, але заважає захаращувати ваші сценарії. Якщо у вас є багато простих конструкцій if-then-else-fi, сценарій швидко розширюється.

Якщо ви хочете повторно використовувати чек на більші / менші числа кілька разів в одному сценарії, поставте його у функцію. Формат функції полегшує налагодження та повторне використання та дозволяє легко замінити цю частину сценарію, наприклад, командою awk, щоб мати можливість обробляти не цілі десяткові числа.

Якщо це один випадок використання, просто зашифруйте його в рядку.


4

Ви можете визначити функцію як

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

Назвіть це як maxnum 54 42і це відлунює 54. Ви можете додати інформацію про перевірку всередині функції (наприклад, два аргументи або числа як аргументи), якщо вам це подобається.


Більшість оболонок не мають арифметики з плаваючою комою. Але це працює для цілих чисел.
Оріон

1
Ця функція непотрібна POSIX-несумісна. Перейдіть function maxnum {на, maxnum() {і він працюватиме набагато більше снарядів.
Чарльз Даффі

2

У скрипті оболонки є спосіб використовувати будь-який відкритий статичний метод Java (і, наприклад, Math.min () ). З bash на Linux:

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

Для цього потрібен міст Shell Bridge https://sourceforge.net/projects/jsbridge/

Дуже швидко, тому що виклики методи внутрішньо конвеєр ; не потрібно ніякого процесу.


0

Більшість людей просто зробили б це sort -n input | head -n1 (або хвіст), це досить добре для більшості сценаріїв. Однак це трохи незграбно, якщо ви маєте цифри в рядку замість стовпця - ви повинні роздрукувати їх у відповідному форматі ( tr ' ' '\n'або щось подібне).

Оболонки не зовсім ідеальні для чисельної обробки, але ви легко можете просто потрапити в якусь іншу програму, яка краще в ній. Залежно від ваших власних уподобань, ви максимально дзвоните dc(трохи заплутані, але якщо ви знаєте, що ви робите, це добре - див. Відповідь mikeserv), або awk 'NR==1{max=$1} {if($1>max){max=$1}} END { print max }'. Або, можливо, perlабо pythonякщо ви віддаєте перевагу. Одне рішення (якщо ви готові встановити та використовувати менш відоме програмне забезпечення) було б ised(особливо якщо ваші дані знаходяться в одному рядку: вам просто потрібно це зробити ised --l input.dat 'max$1').


Оскільки ви просите два числа, це все зайве. Цього має бути достатньо:

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

1
Можливо, буде краще, якщо ви використовували sys.argv:python2 -c 'import sys; print (max(sys.argv))' "$@"
muru

1
Аргументи, які sort + headє надмірними, але pythonне є обчисленнями.
mikeserv

Усі способи над рядком розроблені для обробки величезних наборів чисел і явно пропонують такий спосіб використання (читання з труби чи файлу). min / max для 2 аргументів - це питання, яке відчувається інакше - воно вимагає функції замість потоку. Я просто мав на увазі, що потоковий підхід є надмірним - інструмент, який ви використовуєте, є довільним, я просто використовував, pythonтому що він акуратний.
Оріон

Я б назвав це пропоноване рішення більш охайним , але це може бути тому, що я pythonфанатик (або тому, що для нього не потрібна вилка та додатковий гігантський перекладач) . А може, і те й інше.
mikeserv

@mikeserv Я також би використовував це, якби я знав, що це цілі числа. Всі ці рішення я назвати в припущенні , що числа можуть бути поплавці - баш не робити з плаваючою точкою , і якщо ЗШ не ваша рідна оболонка, ви будете потребувати в вилці (і , можливо , ножі).
Оріон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.