Existe um comando unix que fornece o mínimo / máximo de dois números?


37

Eu estava procurando por um comando para limitar os números de leitura stdin.

Escrevi um pequeno script para esse fim (a crítica é bem-vinda), mas fiquei imaginando se não havia um comando padrão para esse caso de uso simples e (acho) comum.

Meu script que encontra o mínimo de dois números:

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

Respostas:


20

Você pode comparar apenas dois números com os seguintes dc:

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

... onde "$1"está seu valor máximo e "$2"é o número que você imprimiria se for menor que "$1". Isso também requer o GNU dc- mas você pode fazer o mesmo de maneira portável:

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

Em ambos os casos acima, você pode definir a precisão para algo diferente de 0 (o padrão) como ${desired_precision}k. Para ambos, também é imperativo que você verifique se os dois valores são definitivamente números, pois dcpodem fazer system()chamadas com o !operador.

Com o pequeno script a seguir (e o próximo), você deve verificar a entrada também - grep -v \!|dcou algo assim - para lidar com entrada arbitrária de maneira robusta. Você também deve saber que dcinterpreta números negativos com um _prefixo em vez de um -prefixo - porque o último é o operador de subtração.

Além disso, com esse script, dcvocê lerá o número sequencial de \nnúmeros de linha de ew que você gostaria de fornecê-lo e imprimirá para cada um o seu $maxvalor ou a entrada, dependendo de qual é o menor dos dois:

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

Então ... cada um desses [colchetes quadrados ]extensões é uma dc seqüência de objeto que é SAVED cada um para seu respectivo array - qualquer um dos T, ?ou M. Além de algumas outras coisas que dcpodem fazer com uma string , ela também pode ser xreproduzida como uma macro. Se você acertar, um pequeno dcscript totalmente funcional será montado com bastante simplicidade.

dctrabalha em uma pilha . Todos os objetos de entrada são empilhados, cada um sobre o último - cada novo objeto de entrada pressionando o último objeto superior e todos os objetos abaixo dele na pilha por um à medida que são adicionados. A maioria das referências a um objeto são para o valor superior da pilha, ea maioria das referências pop que topo da pilha (que puxa todos os objetos abaixo-lo por um) .

Além da pilha principal, também existem (pelo menos) 256 matrizes e cada elemento da matriz vem com uma pilha própria. Eu não uso muito disso aqui. Eu apenas guardo as strings conforme mencionado para poder lcarregá-las quando desejado e xcalculá-las condicionalmente, e srasguei $maxo valor na parte superior da mmatriz.

Enfim, esse pouco dcfaz, em grande parte, o que o seu shell-script faz. Ele usa a -eopção GNU-ism - como dcgeralmente tira seus parâmetros do padrão - mas você pode fazer o mesmo como:

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

... se $scriptparecia com a parte acima.

Funciona como:

  • lTx- Isso lapaga e faz xeco da macro armazenada no topo T (para teste, eu acho - eu geralmente escolho esses nomes arbitrariamente) .
  • z 0=?- Test testa a profundidade da pilha w / ze, se a pilha estiver vazia (leia-se: contém 0 objetos) , chama a ?macro.
  • ? z0!=T q- A ?macro é nomeada para o ? dccomando embutido que lê uma linha de entrada do stdin, mas também adicionei outro zteste de profundidade da pilha, para que ele possa qutilizar todo o pequeno programa se puxar uma linha em branco ou atingir o EOF. Mas se não !preencher e preencher com êxito a pilha, ela chamará Test novamente.
  • d lm<M- Test então dduplicará a parte superior da pilha e comparará com $max (conforme armazenado em m) . Se mfor o menor valor, dcchama a Mmacro.
  • s0 lm- Mapenas abre a parte superior da pilha e a despeja no escalar fictício 0- apenas uma maneira barata de estourar a pilha. Também se lrecupera mantes de retornar ao Test.
  • p- Isso significa que, se mfor menor que o topo da pilha atual, a msubstitui (a dcópia da mesma, de qualquer maneira) e fica aqui pdefinida, caso contrário, não existe e o que quer que a entrada tenha sido pdefinida.
  • s0- Depois (porque pnão abre a pilha) , despejamos o topo da pilha 0novamente e depois ...
  • lTx- recursivamente load Test mais uma vez, em seguida, e xecute-lo novamente.

Assim, você pode executar esse pequeno trecho e digitar números de forma interativa no seu terminal e dcimprimir de volta o número digitado ou o valor $maxse o número digitado for maior. Também aceitaria qualquer arquivo (como um pipe) como entrada padrão. Ele continuará o loop de leitura / comparação / impressão até encontrar uma linha em branco ou EOF.

Algumas observações sobre isso - eu escrevi isso apenas para emular o comportamento da função shell, portanto, ele apenas lida com um número por linha. dcNo entanto, você pode lidar com tantos números separados por espaço por linha quanto gostaria de jogar nela. No entanto , devido à sua pilha, o último número de uma linha acaba sendo o primeiro em que opera, e, como está escrito, dcimprimiria sua saída ao contrário se você imprimisse / digitasse mais de um número por linha nela. lidar com isso é armazenar uma linha em uma matriz e depois trabalhá-la.

Como isso:

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"

Mas ... não sei se quero explicar isso com tanta profundidade. Basta dizer que, ao dcler cada valor da pilha, ele armazena seu valor ou $maxseu valor em uma matriz indexada e, uma vez que detecta a pilha novamente vazia, ele imprime cada objeto indexado antes de tentar ler outro linha de entrada.

E assim, enquanto o primeiro script faz ...

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

O segundo faz:

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

Você pode manipular carros alegóricos de precisão arbitrária se primeiro configurá-lo com o kcomando E você pode alterar os radios nput iou output independentemente - o que às vezes pode ser útil por razões que você não pode esperar. Por exemplo:

echo 100000o 10p|dc
 00010

... que define o primeiro dcraio de saída para 100000 e depois imprime 10.


3
+1 por não ter ideia do que aconteceu depois de ler duas vezes. Terei que tomar meu tempo para me aprofundar nisso.
Minix 25/02

@Minix - meh - não é necessário pesquisar a linguagem de programação Unix mais antiga, se você a achar confusa. Talvez apenas canalize alguns números de dcvez em quando para mantê-lo alerta.
mikeserv

11
@ mikeserv É tarde demais para mim. Espero que a geração futura tome o meu como um conto preventivo. Parênteses rectos e letras em todo o lado ...
Minix 27/02

@Minix - o que você quer dizer? Você foi por isso? Muito bom - dcé um animal instável, mas pode ser apenas o utilitário comum mais rápido e estranhamente capaz em qualquer sistema Unix. Quando emparelhado, sedele pode fazer algumas coisas extraordinárias. ddUltimamente, tenho brincado com ele para poder substituir a monstruosidade que é readline. Aqui está uma pequena amostra de algumas coisas que eu tenho feito. Fazer um revin dcé quase brincadeira de criança.
mikeserv

11
@Minix - cuidado com os suportes, no entanto. Não há como colocar um colchete dentro de uma corda - o melhor que você pode fazer é [string]P91P93P[string]P. Portanto, tenho um pouco de sedvocê que pode achar útil: o sed 's/[][]/]P93]&[1P[/g;s/[]3][]][[][1[]//g'que sempre deve substituir os quadrados corretamente por um colchete de corda, depois a P, o valor ascii decimal do quadrado e outro P; depois, um [colchete aberto para continuar a sequência. Não sei se você mexeu com dcos recursos de conversão numérica / sequência de caracteres w / , mas - especialmente quando combinados com od-, pode ser bastante divertido.
mikeserv

87

Se você sabe que está lidando com dois números inteiros ae b, essas expansões aritméticas simples do shell usando o operador ternário são suficientes para fornecer o máximo numérico:

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

e min numérico:

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

Por exemplo

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

Aqui está um script de shell demonstrando isso:

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

Boa resposta. Por favor, um mod menor: isso também poderia ser usado para "> ="?
Sopalajo de Arrierez

@SopalajodeArrierez Não tenho muita certeza do que você quer dizer. Você também pode fazê-lo max=$(( a >= b ? a : b )), mas o resultado é totalmente o mesmo - se a e b são iguais, não importa realmente qual é o retorno. É isso que você está perguntando?
Digital Trauma

De fato, obrigado, Digital Trauma. Eu só queria saber se o operador booleano "> =" era possível aqui.
Sopalajo de Arrierez

@SopalajodeArrierez if (( a >= b )); then echo a is greater than or equal to b; fi- é isso que você está pedindo? (note o uso de (( ))aqui em vez de $(( )))
Digital Trauma

Ah sim, ok. Eu entendo agora. Eu não sei muito sobre expansão de shell, então geralmente fico confuso entre as condições. Obrigado novamente.
Sopalajo de Arrierez

24

sorte headpode fazer isso:

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

2
Observe que isso ocorre O(n log(n))enquanto uma implementação eficiente do max seria O(n). É nosso pouco significado n=2, no entanto, uma vez que a geração de dois processos é muito maior.
Lie Ryan

11
Embora seja verdade, @ glenn-jackman, não tenho certeza se isso é importante, dada a pergunta. Não houve solicitação da maneira mais eficiente de fazer isso. Eu acho que a pergunta era mais sobre conveniência.
David Hoelzer

11
@ DavidHoelzer - essa não é a maneira mais eficiente de fazer isso, mesmo entre as respostas oferecidas aqui. Se estiver trabalhando com conjuntos de números, há pelo menos uma outra resposta aqui que é mais eficiente que isso (por ordens de magnitude) , e se apenas trabalhando com dois números inteiros, há outra resposta aqui mais eficiente que isso (por ordens de magnitude) . Porém, é conveniente (mas eu provavelmente deixaria de fora a matriz do shell) .
mikeserv

11
Isso pode ser feito sem matrizes da seguinte maneira:numbers="1 4 3 5 7 1 10 21 8"; echo $numbers | tr ' ' "\n" | sort -rn | head -n 1
ngreen

11
Uma abordagem mais eficiente é provavelmente a seguinte:max=0; for x in $numbers ; do test $x -gt $max && max=$x ; done
ngreen

6

Você pode definir uma biblioteca de funções matemáticas predefinidas bce usá-las na linha de comando.

Por exemplo, inclua o seguinte em um arquivo de texto como ~/MyExtensions.bc:

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

Agora você pode ligar bcpor:

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

Para sua informação, existem funções gratuitas da biblioteca matemática , disponíveis on-line.

Usando esse arquivo, você pode calcular facilmente funções mais complicadas, como GCD:

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

Se não me engano, essas funções também podem ser compiladas com o executável, se necessário. Eu acho que a maioria dos bcs é apenas uma dcinterface para os dias de hoje, mesmo que o GNUbc não seja mais assim (mas o GNU dce o GNU bccompartilham uma quantidade prodigiosa de sua base de código) . De qualquer forma, esta pode ser a melhor resposta aqui.
mikeserv

Para chamar isso convenientemente no arquivo de um script de shell, você também pode canalizar a definição da função bc, imediatamente antes da chamada da função. No segundo arquivo necessário então :)
tanius

5

Tempo demais para um comentário:

Embora você possa fazer essas coisas, por exemplo, com os combos sort | headou sort | tail, parece um pouco abaixo do ideal em termos de manipulação de recursos e erros. No que diz respeito à execução, o combo significa gerar 2 processos apenas para verificar duas linhas. Isso parece ser um exagero.

O problema mais sério é que, na maioria dos casos, você precisa saber que a entrada é sensata, ou seja, contém apenas números. A solução da @ glennjackmann resolve isso de maneira inteligente, pois printf %ddeve vomitar não-inteiros. Também não funcionará com flutuadores (a menos que você altere o especificador de formato para %f, onde você terá problemas de arredondamento).

test $1 -gt $2 indicará se a comparação falhou ou não (o status de saída 2 significa que houve um erro durante o teste. Como esse geralmente é um shell embutido, não há processo adicional) - estamos falando da ordem de centenas execução vezes mais rápida, embora só funcione com números inteiros.

Se você precisar comparar alguns números de ponto flutuante, uma opção interessante pode ser bc:

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

seria o equivalente a test $1 -gt $2, e usando no shell:

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

ainda é quase 2,5 vezes mais rápido que printf | sort | head(para dois números).

Se você pode confiar nas extensões GNU bc, também pode usar a read()função para ler os números diretamente na bcassinatura.


Meus pensamentos exatamente - eu estava apenas resolvendo isso, mas você me venceu: dc -e "${max}sm[z0=?dlm<Mps0lTx]ST[?z0!=Tq]S?[s0lm]SMlTx"- oh, exceto que dcfaz a coisa toda (exceto o eco, embora pudesse) - ele lê stdin e imprime um $maxou o número de entrada, dependendo de qual é menor. De qualquer forma, eu realmente não me importo de explicar e sua resposta é melhor do que eu ia escrever. Então, tenha meu voto positivo, por favor.
mikeserv

@mikeserv, na verdade, ter um dcscript explicado seria muito bom, o RPN não é visto com frequência nos dias de hoje.
Peterph

Notação polonesa reversa (também conhecida como notação Postfix). Além disso, se você dcpuder fazer a E / S por si só, seria ainda mais elegante do que.
Peterph

4

Para obter o maior valor de $ a e $ b, use o seguinte:

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

Mas você precisa de algo em torno disso, provavelmente não pretende executar o número; portanto, para exibir o maior valor dos dois, use "eco"

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

O item acima se encaixa perfeitamente em uma função shell, por exemplo

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

Para atribuir o maior dos dois à variável, use esta versão modificada:

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

ou use a função definida:

biggest=$( max $a $b )

A variação da função também oferece a oportunidade de adicionar uma verificação de erro de entrada ordenadamente.

Para retornar o máximo de dois números decimais / ponto flutuante, você pode usar awk

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

EDIT: Usando esta técnica, você pode criar uma função de "limite" que opera de maneira inversa, conforme sua edição / nota. Esta função retornará o menor dos dois, por exemplo:

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

Eu gosto de colocar funções utilitárias em um arquivo separado, chamá-lo myprogram.funcse usá-lo em um script da seguinte maneira:

#!/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, isso ainda faz o que você fez, e sua versão, embora seja mais detalhada, é igualmente eficiente.

A forma mais compacta não é realmente melhor, mas evita a confusão de seus scripts. Se você tem muitas construções simples de se-então-outro-fi, o script se expande rapidamente.

Se você deseja reutilizar a verificação de números maiores / menores várias vezes em um único script, coloque-a em uma função. O formato da função facilita a depuração e a reutilização e permite substituir facilmente essa parte do script, por exemplo, por um comando awk para poder manipular números decimais não inteiros.

Se for um caso de uso único, basta codificá-lo em linha.


4

Você pode definir uma função como

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

Chame como maxnum 54 42e ecoa 54. Você pode adicionar informações de validação dentro da função (como dois argumentos ou números como argumentos), se desejar.


A maioria das conchas não faz aritmética de ponto flutuante. Mas funciona para números inteiros.
orion 25/02

11
Esta função é desnecessariamente incompatível com POSIX. Mude function maxnum {para maxnum() {e funcionará por muito mais conchas.
Charles Duffy

2

Em um script de shell, existe uma maneira de usar qualquer método estático público Java (e, por exemplo, Math.min () ). Do bash no Linux:

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

Isso requer o Java Shell Bridge https://sourceforge.net/projects/jsbridge/

Muito rápido, porque as chamadas de método são canalizadas internamente ; nenhum processo é necessário.


0

A maioria das pessoas simplesmente faz sort -n input | head -n1(ou segue), é bom o suficiente para a maioria das situações de script. No entanto, isso é um pouco desajeitado se você tiver números em uma linha em vez de em uma coluna - precisará imprimi-lo em um formato adequado ( tr ' ' '\n'ou algo semelhante).

Os shells não são exatamente ideais para processamento numérico, mas você pode facilmente inserir outros programas que são melhores nele. Dependendo da sua preferência, você liga no máximo dc(um pouco ofuscado, mas se você sabe o que está fazendo, tudo bem - veja a resposta do mikeserv), ou awk 'NR==1{max=$1} {if($1>max){max=$1}} END { print max }'. Ou possivelmente perlou pythonse você preferir. Uma solução (se você estiver disposto a instalar e usar software menos conhecido) seria ised(especialmente se seus dados estiverem em uma única linha: você só precisa fazê-lo ised --l input.dat 'max$1').


Como você está pedindo dois números, tudo isso é um exagero. Isso deve ser suficiente:

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

11
Pode ser melhor se você usou sys.argv:python2 -c 'import sys; print (max(sys.argv))' "$@"
muru

11
Os argumentos que sort + headsão um exagero, mas pythonnão são computados.
mikeserv

Todos os métodos acima da linha são projetados para lidar com grandes conjuntos de números e sugerir explicitamente esse tipo de uso (leitura de um canal ou arquivo). min / max para 2 argumentos é uma pergunta que parece diferente - exige uma função em vez de um fluxo. Eu apenas quis dizer que a abordagem de fluxo é um exagero - a ferramenta que você usa é arbitrária, eu apenas usei pythonporque é legal.
orion 25/02

Eu chamaria isso de solução sugerida , mas isso pode ser porque sou pythonfanático (ou porque não requer um garfo e um intérprete gigante adicional) . Ou talvez ambos.
mikeserv

@ MikeServ Eu também usaria isso se soubesse que eram números inteiros. Todas estas soluções que eu mencionei estão sob suposição de que os números podem ser carros alegóricos - o bash não faz ponto flutuante ea menos que zsh é o seu shell nativa, você vai precisar de um garfo (e possivelmente uma faca).
orion 25/02
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.