Есть ли команда 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
Minix
источник

Ответы:

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значение, либо входные данные, в зависимости от того, что меньше из wo:

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 массивов, и каждый элемент массива имеет свой собственный стек. Я не использую большую часть этого здесь. Я просто храню строки, как уже упоминалось, так что я могу load их при желании и еx вывести их условно, и я sпорвал $maxзначение в верхней части mмассива.

В любом случае, эта небольшая часть dcделает, в основном, то, что делает ваш shell-скрипт. Он использует GNU-изм-e опцию - как dcправило, берет свои параметры из стандарта - но вы можете сделать то же самое, например:

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

... если бы $scriptвыглядело, как указано выше.

Это работает как:

  • lTx- Это lвыдает и xизвлекает макрос, хранящийся в верхней части T (для теста, я думаю, я обычно выбираю эти имена произвольно) .
  • z 0=?- TЭст затем проверяет глубину стека ш / zи, если стек пуст (читай: содержит 0 объектов) он вызывает ?макрос.
  • ? z0!=T q- ?Макрос назван по имени ? dcвстроенной команды, которая читает строку ввода из stdin, но я также добавил еще один zтест глубины стека, чтобы он мог qиспользовать всю маленькую программу, если он вытянет пустую строку или нажмет EOF. Но если это !не так и вместо этого успешно заполняет стек, он вызывает Test снова.
  • d lm<M- TЗатем est dобновит верхнюю часть стека и сравнит его с $max (как хранится в m) . Если mэто меньшее значение, dcвызывает Mмакрос.
  • s0 lm- Mпросто выбрасывает верх стека и сбрасывает его в фиктивный скаляр 0- просто дешевый способ вытолкнуть стек. Это также loads mснова, прежде чем вернуться кT EST.
  • p- Это означает, что если mон меньше текущей вершины стека, то mзаменяет его dлюбом случае его дубликат) и находится здесьp печатается, в противном случае это не так, и независимо от того, какой ввод был pвведен, вместо этого печатается.
  • s0- После этого (потому pчто стек не выталкивается) мы сбрасываем верхнюю часть стека в0 снова , а затем ...
  • lTx- рекурсивно lполучается Tеще разx ecute его снова.

Таким образом, вы можете запустить этот небольшой отрывок и в интерактивном режиме набрать цифры на своем терминале и 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.

mikeserv
источник
3
+1 за то, что понятия не имел, что случилось после прочтения его дважды. Придется не торопиться, чтобы вникать в это.
Minix
@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; затем открытая [квадратная скобка, чтобы продолжить строку. Не знаю, если вы перепутали w / dcs возможности преобразования строк / чисел, но - особенно в сочетании с w od- это может быть довольно забавно.
mikeserv
88

Если вы знаете, что имеете дело с двумя целыми числами 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. Мне просто интересно, был ли здесь возможен логический оператор "> =".
Сопалахо де Арриерес
@SopalajodeArrierez if (( a >= b )); then echo a is greater than or equal to b; fi- это то, что вы просите? (обратите внимание на использование (( ))здесь вместо $(( )))
Цифровая травма
Ах да, хорошо. Теперь я понимаю. Я не знаю много о расширении оболочки, поэтому я обычно путаюсь между условиями. Еще раз спасибо.
Сопалайо де Арриерес
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))пока эффективная реализация max 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
ngreen
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

К вашему сведению, в интернете есть бесплатные математические функции, такие как эта .

Используя этот файл, вы можете легко вычислить более сложные функции, такие как GCD:

> echo 'gcd (60,54)' | bc ~/extensions.bc -l
6
Ari
источник
Если я не ошибаюсь, такие функции также могут быть скомпилированы с исполняемым файлом при необходимости. Я думаю, что большинство bcs до сих пор являются только dcвнешними интерфейсами , даже если GNUbc больше не является таковым (но GNU dcи GNU bcимеют колоссальное количество своей кодовой базы) . В любом случае, это может быть лучшим ответом здесь.
mikeserv
Чтобы удобно вызывать это в файле сценария оболочки, вы также можете передать определение функции bcпрямо перед вызовом функции. Тогда второй файл не нужен :)
tanius
5

Слишком долго для комментария:

Хотя вы можете делать эти вещи, например, с помощью sort | headили sort | tailкомбинаций, это кажется довольно неоптимальным с точки зрения как ресурсов, так и обработки ошибок. Что касается выполнения, то комбо означает порождение двух процессов только для проверки двух строк. Это кажется излишним.

Более серьезная проблема заключается в том, что в большинстве случаев вам нужно знать, что ввод вменяется, то есть содержит только цифры. Решение @ 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скрипт.

peterph
источник
Мои мысли точно - я просто сглаживал это, но вы меня обыгрывали: 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

Но вам нужно что-то вокруг этого, вы, вероятно, не хотите выполнять число, поэтому для отображения большего значения из двух используйте «echo»

[ "$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, чтобы иметь возможность обрабатывать нецелые десятичные числа.

Если это единственный вариант использования, просто закодируйте его в строке.

Johan
источник
4

Вы можете определить функцию как

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

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

unxnut
источник
Большинство оболочек не делают арифметику с плавающей точкой. Но это работает для целых чисел.
Орион
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"

Для этого требуется Java Shell Bridge https://sourceforge.net/projects/jsbridge/

Очень быстро, потому что вызовы методов внутренне переданы ; процесс не требуется.

Fil
источник
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 Я бы тоже использовал это, если бы знал, что они целые числа. Все эти решения я назвать в предположении , что числа могут быть поплавки - баш не делать с плавающей точкой , и если ЗШ не ваша родная оболочка, вы будете нуждаться в вилке (и , возможно , ноже).
Орион