Apakah ada perintah unix yang memberikan minimum / maksimum dua angka?

37

Saya sedang mencari perintah untuk membatasi angka yang dibaca dari stdin.

Saya menulis sebuah skrip kecil untuk tujuan itu (kritik dipersilahkan), tetapi saya bertanya-tanya apakah tidak ada perintah standar untuk ini, kasus penggunaan sederhana dan (saya pikir) umum.

Skrip saya yang menemukan minimum dua angka:

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

Jawaban:

20

Anda dapat membandingkan hanya dua angka dengan dcseperti:

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

... di mana "$1"nilai maksimum Anda dan "$2"merupakan angka yang akan Anda cetak jika lebih rendah dari itu "$1". Itu juga membutuhkan GNU dc- tetapi Anda dapat melakukan hal yang sama dengan mudah seperti:

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

Dalam kedua kasus di atas Anda dapat mengatur presisi ke sesuatu selain 0 (default) seperti ${desired_precision}k. Untuk keduanya, Anda juga harus memastikan bahwa kedua nilai tersebut merupakan angka pasti karena dcdapat membuat system()panggilan dengan !operator.

Dengan skrip kecil berikut (dan selanjutnya) Anda harus memverifikasi input juga - suka grep -v \!|dcatau sesuatu untuk menangani input sewenang - wenang. Anda juga harus tahu bahwa dcmenafsirkan angka negatif dengan _awalan daripada -awalan - karena yang terakhir adalah operator pengurangan.

Selain itu, dengan skrip ini dcakan terbaca \nnomor ewline terpisah berurutan sebanyak yang Anda mau berikan, dan cetak untuk setiap $maxnilai atau input Anda, tergantung mana yang lebih rendah dari wo:

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

Jadi ... masing-masing [persegi kurung ]hamparan adalah dc string yang objek yang Saved masing-masing untuk array yang masing-masing - salah satu dari T, ?atau M. Selain beberapa hal lain yang dcmungkin dilakukan dengan sebuah string , ia juga dapat menjadikannya xsebagai makro. Jika Anda mengaturnya dengan benar, dcskrip kecil yang berfungsi penuh dirakit cukup sederhana.

dcbekerja pada tumpukan . Semua objek input ditumpuk masing-masing pada yang terakhir - setiap objek input baru mendorong objek teratas terakhir dan semua objek di bawahnya di atas tumpukan dengan satu saat ditambahkan. Sebagian besar referensi ke objek adalah ke nilai tumpukan teratas, dan sebagian besar referensi muncul di atas tumpukan (yang menarik semua objek di bawahnya satu per satu) .

Selain tumpukan utama, ada juga (setidaknya) 256 array dan masing-masing elemen array memiliki tumpukan sendiri. Saya tidak banyak menggunakan itu di sini. Saya hanya menyimpan string seperti yang disebutkan sehingga saya dapat menggunakannya lketika diinginkan dan membuat xmereka kondisional, dan saya smerobek $maxnilai di bagian atas marray.

Bagaimanapun, sedikit ini dctidak, sebagian besar, apa yang dilakukan oleh shell-script Anda. Itu memang menggunakan opsi GNU-isme -e- seperti dcumumnya mengambil parameternya dari standard-in - tetapi Anda bisa melakukan hal yang sama seperti:

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

... jika $scripttampak seperti bit di atas.

Ini berfungsi seperti:

  • lTx- Ini loads dan e xecutes makro disimpan di bagian atas T (untuk tes, saya kira - Saya biasanya mengambil nama-nama sewenang-wenang) .
  • z 0=?- Test kemudian menguji kedalaman tumpukan dengan zdan, jika tumpukan kosong (baca: menampung 0 objek) ia memanggil ?makro.
  • ? z0!=T q- ?Makro dinamai untuk ? dcperintah builtin yang membaca baris input dari stdin, tapi saya juga menambahkan ztes kedalaman tumpukan lain untuk itu, sehingga dapat cocok qdengan seluruh program kecil jika menarik garis kosong atau menekan EOF. Tetapi jika !tidak dan malah berhasil mengisi stack, ia memanggil Test lagi.
  • d lm<M- Test kemudian akan dmenggandakan bagian atas tumpukan dan membandingkannya dengan $max (seperti yang disimpan di m) . Jika mnilainya lebih rendah, dcpanggil Mmakro.
  • s0 lm- MHanya muncul bagian atas tumpukan dan membuangnya ke skalar dummy 0- hanya cara murah untuk muncul tumpukan. Hal ini juga loads mlagi sebelum kembali ke Test.
  • p- Ini berarti bahwa jika mkurang dari puncak tumpukan saat ini, maka mganti itu (bagian datasnya, bagaimanapun) dan di sini di- pbengkokkan, yang lain tidak dan apa pun input yang di- pbengkokkan sebagai gantinya.
  • s0- Setelah itu (karena ptidak memunculkan tumpukan) kami membuang bagian atas tumpukan 0lagi, dan kemudian ...
  • lTx- rekursif load Test sekali lagi maka e xecute lagi.

Jadi, Anda dapat menjalankan potongan kecil ini dan mengetik nomor secara interaktif di terminal Anda dan dcakan mencetak kembali pada Anda nomor yang Anda masukkan atau nilai $maxjika nomor yang Anda ketikkan lebih besar. Itu juga akan menerima file apa pun (seperti pipa) sebagai input standar. Ini akan melanjutkan loop baca / bandingkan / cetak sampai bertemu dengan garis kosong atau EOF.

Beberapa catatan tentang ini - saya menulis ini hanya untuk meniru perilaku dalam fungsi shell Anda, sehingga hanya menangani satu angka per baris dengan kuat. dcnamun, dapat menangani sebanyak mungkin angka yang dipisahkan spasi per baris seperti yang Anda inginkan. Namun , karena tumpukannya, angka terakhir pada sebuah garis akhirnya menjadi yang pertama beroperasi, dan, seperti yang tertulis, dcakan mencetak hasilnya secara terbalik jika Anda mencetak / mengetik lebih dari satu angka per baris di dalamnya. Cara yang tepat untuk mengatasinya adalah dengan menyimpan sebuah baris dalam sebuah array, kemudian mengerjakannya.

Seperti ini:

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"

Tapi ... Saya tidak tahu apakah saya ingin menjelaskannya secara mendalam. Cukuplah untuk mengatakan bahwa ketika dcdibaca di setiap nilai pada stack ia menyimpan nilai atau $maxnilainya dalam array yang diindeks, dan, begitu ia mendeteksi stack sekali lagi kosong, ia kemudian mencetak setiap objek yang diindeks sebelum mencoba membaca yang lain jalur input.

Jadi, sementara skrip pertama tidak ...

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

Yang kedua tidak:

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

Anda dapat menangani float dengan presisi sewenang-wenang jika Anda pertama kali mengaturnya dengan kperintah. Dan Anda dapat mengubah input atau radisi output secara independen - yang kadang-kadang bisa berguna karena alasan yang mungkin tidak Anda harapkan. Sebagai contoh:

echo 100000o 10p|dc
 00010

... yang pertama mengatur dcoutput radix ke 100000 kemudian mencetak 10.

mikeserv
sumber
3
+1 karena tidak tahu apa yang baru saja terjadi setelah membacanya dua kali. Harus mengambil waktu saya untuk mempelajari ini.
Minix
@Minix - meh - tidak perlu mempelajari bahasa pemrograman Unix tertua jika Anda merasa membingungkan. Mungkin hanya menyalurkan beberapa angka dcsetiap waktu untuk mempertahankannya.
mikeserv
1
@ mikeserv Sudah terlambat bagi saya. Saya berharap generasi mendatang menganggap saya sebagai peringatan. Kurung kotak dan surat di mana-mana ...
Minix
@Minix - apa maksudmu Kamu pergi untuk itu? Sangat bagus - dcadalah binatang yang berubah-ubah, tetapi mungkin saja merupakan utilitas umum yang tercepat dan paling aneh pada setiap sistem Unix. Ketika dipasangkan dg seditu bisa melakukan beberapa hal luar biasa. Saya sudah bermain dengan itu dan ddakhir - akhir ini sehingga saya bisa mengganti keburukan itu readline. Ini contoh kecil dari beberapa hal yang telah saya lakukan. Melakukan revin dchampir merupakan permainan anak-anak.
mikeserv
1
@Minix - meskipun dengan tanda kurung. Tidak ada cara untuk menempatkan braket persegi dalam string - yang terbaik yang dapat Anda lakukan adalah [string]P91P93P[string]P. Jadi saya punya sedikit dari sedAnda mungkin menemukan berguna: sed 's/[][]/]P93]&[1P[/g;s/[]3][]][[][1[]//g'yang harus selalu mengganti kotak dengan benar dengan braket tutup string, kemudian a P, maka nilai ascii desimal kuadrat dan yang lainnya P; kemudian [braket kotak terbuka untuk melanjutkan string. Tidak tahu apakah Anda telah mengacaukan dckemampuan konversi string / numerik, tetapi - terutama bila dikombinasikan dengan / od- itu bisa sangat menyenangkan.
mikeserv
87

Jika Anda tahu Anda berurusan dengan dua bilangan bulat adan b, maka ekspansi aritmatika shell sederhana ini menggunakan operator ternary cukup untuk memberikan jumlah numerik:

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

dan angka min:

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

Misalnya

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

Berikut ini adalah skrip shell yang menunjukkan ini:

#!/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 ))
Trauma Digital
sumber
Jawaban bagus. Tolong, mod kecil: bisakah ini digunakan juga untuk "> ="?
Sopalajo de Arrierez
@SopalajodeArrierez Saya tidak sepenuhnya yakin apa yang Anda maksud. Anda juga dapat melakukannya max=$(( a >= b ? a : b )), tetapi hasilnya sepenuhnya sama - jika a dan b sama, maka tidak masalah mana yang dikembalikan. Itukah yang kamu tanyakan?
Digital Trauma
Memang, terima kasih, Trauma Digital. Saya hanya ingin tahu apakah operator boolean "> =" dimungkinkan di sini.
Sopalajo de Arrierez
@SopalajodeArrierez if (( a >= b )); then echo a is greater than or equal to b; fi- apakah itu yang Anda minta? (perhatikan penggunaan di (( ))sini alih-alih $(( )))
Digital Trauma
Ah, ya, baiklah. Saya mengerti sekarang. Saya tidak tahu banyak tentang ekspansi shell, jadi saya biasanya bingung antara kondisi. Lagi terima kasih
Sopalajo de Arrierez
24

sortdan headdapat melakukan ini:

numbers=(1 4 3 5 7 1 10 21 8)
printf "%d\n" "${numbers[@]}" | sort -rn | head -1       # => 21
glenn jackman
sumber
2
Perhatikan bahwa ini adalah O(n log(n))implementasi maks yang efisien O(n). Ini adalah signifikansi kecil kita n=2, karena pemijahan dua proses jauh lebih besar.
Lie Ryan
1
Meskipun benar, @ glenn-jackman, saya tidak yakin apakah itu penting mengingat pertanyaan itu. Tidak ada permintaan untuk cara paling efisien untuk melakukannya. Saya pikir pertanyaannya lebih tentang kenyamanan.
David Hoelzer
1
@ Davidvidozer - ini bukan cara paling efisien untuk melakukan ini bahkan di antara jawaban yang ditawarkan di sini. Jika bekerja dengan set angka, setidaknya ada satu jawaban lain di sini yang lebih efisien daripada ini (berdasarkan urutan besarnya) , dan jika hanya bekerja dengan dua bilangan bulat, ada jawaban lain di sini yang lebih efisien daripada itu (berdasarkan urutan besarnya) . Lebih mudah meskipun (tapi saya mungkin akan meninggalkan array shell, secara pribadi) .
mikeserv
1
Ini dapat dilakukan tanpa array sebagai berikut:numbers="1 4 3 5 7 1 10 21 8"; echo $numbers | tr ' ' "\n" | sort -rn | head -n 1
ngreen
1
Pendekatan yang lebih efisien mungkin adalah ini:max=0; for x in $numbers ; do test $x -gt $max && max=$x ; done
ngreen
6

Anda bisa mendefinisikan perpustakaan fungsi matematika yang telah ditentukan untuk bcdan kemudian menggunakannya di baris perintah.

Misalnya, sertakan yang berikut ini dalam file teks seperti ~/MyExtensions.bc:

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

Sekarang Anda dapat menelepon bcdengan:

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

FYI, ada fungsi perpustakaan matematika gratis seperti ini tersedia online.

Dengan menggunakan file itu, Anda dapat dengan mudah menghitung fungsi yang lebih rumit seperti GCD:

> echo 'gcd (60,54)' | bc ~/extensions.bc -l
6
Ari
sumber
Jika saya tidak salah, fungsi tersebut juga dapat dikompilasi dengan executable jika perlu. Saya pikir sebagian besar bchanya dcfrontend sampai hari ini, meskipun, bahkan jika GNUbc tidak lagi seperti itu (tetapi GNU dcdan GNU bcberbagi jumlah basis kode yang luar biasa) . Bagaimanapun, ini mungkin jawaban terbaik di sini.
mikeserv
Untuk memanggil ini dengan mudah dalam file skrip shell, Anda juga dapat memasukkan definisi fungsi bc, tepat sebelum panggilan fungsi. Tidak diperlukan lagi file kedua :)
tanius
5

Terlalu panjang untuk dikomentari:

Meskipun Anda dapat melakukan hal-hal ini misalnya dengan sort | headatau sort | tailkombo, tampaknya agak kurang optimal, baik sumber daya maupun penanganan kesalahan. Sejauh menyangkut eksekusi, kombo berarti menelurkan 2 proses hanya untuk memeriksa dua baris. Itu tampaknya sedikit berlebihan.

Masalah yang lebih serius adalah, bahwa dalam kebanyakan kasus Anda perlu tahu, bahwa inputnya waras, yang hanya berisi angka. solusi @ glennjackmann dengan cerdik menyelesaikan ini, karena printf %dharus muntah pada non-integer. Ini tidak akan bekerja dengan float (kecuali jika Anda mengubah penentu format ke %f, di mana Anda akan mengalami masalah pembulatan).

test $1 -gt $2 akan memberi Anda indikasi apakah perbandingan gagal atau tidak (status keluar dari 2 berarti ada kesalahan selama pengujian. Karena ini biasanya merupakan shell bawaan, tidak ada proses tambahan yang muncul - kita berbicara tentang urutan ratusan kali eksekusi lebih cepat. Hanya bekerja dengan bilangan bulat.

Jika Anda perlu membandingkan beberapa angka floating point, opsi menarik mungkin bc:

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

akan menjadi setara dengan test $1 -gt $2, dan menggunakan di dalam shell:

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

masih hampir 2,5 kali lebih cepat dari printf | sort | head(untuk dua angka).

Jika Anda dapat mengandalkan ekstensi GNU di bc, maka Anda juga dapat menggunakan read()fungsi untuk membaca angka langsung ke bcsript.

peterph
sumber
Pikiran saya persis - saya baru saja menyetrika ini, tetapi Anda mengalahkan saya untuk itu: dc -e "${max}sm[z0=?dlm<Mps0lTx]ST[?z0!=Tq]S?[s0lm]SMlTx"- oh, kecuali yang dcmelakukan semuanya (kecuali gema, meskipun bisa) - membaca stdin dan mencetak salah satu $maxatau nomor input tergantung pada yang lebih kecil. Bagaimanapun, saya tidak terlalu peduli untuk menjelaskannya dan jawaban Anda lebih baik daripada yang akan saya tulis. Tolong, tolong, tolong, tolong.
mikeserv
@mikeserv benar-benar memiliki dcskrip yang dijelaskan akan sangat bagus, RPN tidak terlihat yang sering hari ini.
peterph
Membalikkan notasi Polandia (alias notasi Postfix). Plus jika dcdapat melakukan I / O sendiri itu akan lebih elegan daripada.
peterph
4

Untuk mendapatkan nilai lebih besar dari $ a dan $ b gunakan ini:

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

Tetapi Anda membutuhkan sesuatu di sekitar itu, Anda mungkin tidak bermaksud mengeksekusi angka, jadi untuk menampilkan nilai yang lebih besar dari keduanya gunakan "echo"

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

Di atas cocok dengan baik ke fungsi shell, misalnya

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

Untuk menetapkan yang lebih besar dari keduanya ke variabel, gunakan versi modifikasi ini:

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

atau gunakan fungsi yang didefinisikan:

biggest=$( max $a $b )

Variasi fungsi juga memberi Anda kesempatan untuk menambahkan pemeriksaan kesalahan input dengan rapi.

Untuk mengembalikan maksimal dua angka desimal / titik mengambang yang dapat Anda gunakan awk

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

EDIT: Menggunakan teknik ini Anda dapat membuat fungsi "batas" yang beroperasi sebaliknya sesuai dengan edit / catatan Anda. Fungsi ini akan mengembalikan yang lebih rendah dari keduanya, misalnya:

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

Saya suka meletakkan fungsi utilitas ke dalam file terpisah, memanggilnya myprogram.funcsdan menggunakannya dalam skrip sebagai berikut:

#!/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 ini masih melakukan apa yang Anda lakukan, dan versi Anda meskipun lebih verbose, sama efisiennya.

Bentuk yang lebih ringkas tidak benar-benar lebih baik, tetapi mencegah kekacauan dalam skrip Anda. Jika Anda memiliki banyak konstruksi if-then-else-fi yang sederhana, skrip akan cepat mengembang.

Jika Anda ingin menggunakan kembali pemeriksaan untuk jumlah yang lebih besar / lebih kecil beberapa kali dalam satu skrip, masukkan fungsi. Format fungsi membuatnya lebih mudah untuk di-debug dan digunakan kembali dan memungkinkan Anda untuk dengan mudah mengganti bagian skrip itu, misalnya dengan perintah awk untuk dapat menangani angka desimal yang bukan bilangan bulat.

Jika ini adalah kasus penggunaan tunggal, cukup kode itu in-line.

Johan
sumber
4

Anda dapat mendefinisikan suatu fungsi sebagai

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

Sebut saja maxnum 54 42dan gema 54. Anda dapat menambahkan info validasi di dalam fungsi (seperti dua argumen atau angka sebagai argumen) jika Anda mau.

unxnut
sumber
Sebagian besar cangkang tidak melakukan aritmatika titik apung. Tapi itu berfungsi untuk bilangan bulat.
orion
1
Fungsi ini tentu saja tidak kompatibel dengan POSIX. Ubah function maxnum {ke maxnum() {dan itu akan bekerja untuk lebih banyak kerang.
Charles Duffy
2

Dari skrip shell, ada cara untuk menggunakan metode statis publik Java apa pun (dan misalnya Math.min () ). Dari bash di Linux:

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

Ini membutuhkan Java Shell Bridge https://sourceforge.net/projects/jsbridge/

Sangat cepat, karena pemanggilan metode secara internal disalurkan ; tidak diperlukan proses.

Fil
sumber
0

Kebanyakan orang hanya akan melakukan sort -n input | head -n1(atau membuntuti), itu cukup baik untuk sebagian besar situasi scripting. Namun, ini agak canggung jika Anda memiliki angka dalam satu baris dan bukan kolom - Anda harus mencetaknya dalam format yang tepat (tr ' ' '\n' atau yang serupa).

Kerang tidak sepenuhnya ideal untuk pemrosesan numerik, tetapi Anda dapat dengan mudah menyalurkan ke beberapa program lain yang lebih baik di dalamnya. Bergantung pada preferensi Anda sendiri, Anda dapat panggilan maksimal dc(agak dikaburkan, tetapi jika Anda tahu apa yang Anda lakukan, tidak masalah - lihat jawaban mikeserv), atau awk 'NR==1{max=$1} {if($1>max){max=$1}} END { print max }'. Atau mungkin perlatau pythonjika Anda suka. Satu solusi (jika Anda bersedia menginstal dan menggunakan perangkat lunak yang kurang dikenal) adalah ised(terutama jika data Anda berada dalam satu baris: Anda hanya perlu melakukannya ised --l input.dat 'max$1').


Karena Anda meminta dua nomor, ini semua berlebihan. Ini seharusnya cukup:

python -c "print(max($j,$k))"
orion
sumber
1
Mungkin lebih baik jika Anda menggunakan sys.argv:python2 -c 'import sys; print (max(sys.argv))' "$@"
muru
1
Argumen yang sort + headberlebihan tetapi pythontidak menghitung.
mikeserv
Semua metode di atas garis dirancang untuk menangani set angka yang sangat besar dan secara eksplisit menyarankan penggunaan semacam ini (membaca dari pipa atau file). min / maks untuk 2 argumen adalah pertanyaan yang terasa berbeda - ia meminta fungsi alih-alih streaming. Saya hanya bermaksud bahwa pendekatan aliran berlebihan - alat yang Anda gunakan sewenang-wenang, saya hanya menggunakan pythonkarena rapi.
orion
Saya akan menyebut solusi yang disarankan ini lebih rapi , tetapi itu mungkin karena saya pythonfanatik (atau karena tidak memerlukan garpu dan penerjemah raksasa tambahan) . Atau mungkin keduanya.
mikeserv
@ mikeserv Saya akan menggunakannya juga jika saya tahu itu bilangan bulat. Semua solusi yang saya sebutkan di bawah asumsi bahwa jumlahnya mungkin mengambang - bash tidak melakukan floating point dan kecuali zsh adalah cangkang asli Anda, Anda akan memerlukan garpu (dan mungkin pisau).
orion