Uji apakah string adalah bilangan bulat yang valid

117

Saya mencoba melakukan sesuatu yang cukup umum: Parse input pengguna dalam skrip shell. Jika pengguna memberikan integer yang valid, skrip melakukan satu hal, dan jika tidak valid, skrip melakukan hal lain. Masalahnya adalah, saya belum menemukan cara yang mudah (dan cukup elegan) untuk melakukan ini - saya tidak ingin memilah-milahnya satu per satu.

Saya tahu ini pasti mudah tetapi saya tidak tahu caranya. Saya bisa melakukannya dalam selusin bahasa, tetapi tidak BASH!

Dalam penelitian saya, saya menemukan ini:

Ekspresi reguler untuk menguji apakah sebuah string terdiri dari bilangan real yang valid di basis 10

Dan ada jawaban di dalamnya yang berbicara tentang regex, tetapi sejauh yang saya tahu, itu adalah fungsi yang tersedia di C (antara lain). Tetap saja, itu sepertinya jawaban yang bagus jadi saya mencobanya dengan grep, tetapi grep tidak tahu apa yang harus dilakukan dengannya. Saya mencoba -P yang di kotak saya berarti memperlakukannya sebagai PERL regexp - nada. Dasbor E (-E) juga tidak berfungsi. Dan begitu pula -F.

Hanya untuk memperjelas, saya mencoba sesuatu seperti ini, mencari output apa pun - dari sana, saya akan meretas skrip untuk memanfaatkan apa pun yang saya dapatkan. (IOW, saya mengharapkan bahwa input yang tidak sesuai tidak menghasilkan apa-apa saat baris yang valid diulang.)

snafu=$(echo "$2" | grep -E "/^[-+]?(?:\.[0-9]+|(?:0|[1-9][0-9]*)(?:\.[0-9]*)?)$/")
if [ -z "$snafu" ] ;
then
   echo "Not an integer - nothing back from the grep"
else
   echo "Integer."
fi

Bisakah seseorang mengilustrasikan bagaimana ini paling mudah dilakukan?

Terus terang, ini adalah kekurangan TEST, menurut saya. Ini harus memiliki bendera seperti ini

if [ -I "string" ] ;
then
   echo "String is a valid integer."
else
   echo "String is not a valid integer."
fi
Richard T
sumber
4
FYI: [kompatibel lama test; [[adalah hal baru Bash, dengan lebih banyak operasi dan aturan kutipan yang berbeda. Jika Anda sudah memutuskan untuk tetap menggunakan Bash, lakukan [[(ini benar-benar jauh lebih bagus); jika Anda membutuhkan portabilitas ke shell lain, hindari [[sepenuhnya.
efemient

Jawaban:

183
[[ $var =~ ^-?[0-9]+$ ]]
  • The ^menunjukkan awal dari pola input
  • Ini -adalah "-" literal
  • The ?berarti "0 atau 1 dari sebelumnya ( -)"
  • The +berarti "1 atau lebih dari sebelumnya ( [0-9])"
  • The $menunjukkan akhir dari pola input

Jadi ekspresi reguler cocok dengan opsional -(untuk kasus angka negatif), diikuti dengan satu atau beberapa digit desimal.

Referensi :

Ignacio Vazquez-Abrams
sumber
3
Terima kasih Ignacio, saya akan mencobanya sebentar lagi. Maukah Anda menjelaskannya agar saya bisa belajar sedikit? Saya mengumpulkannya berbunyi, "Pada awal string (^), tanda minus (-) adalah opsional (?), Diikuti dengan sejumlah karakter antara nol dan 9, inklusif" ... dan apa yang mungkin + $ mean? Terima kasih.
Richard T
10
The +berarti "1 atau lebih dari sebelumnya", dan $menunjukkan akhir dari pola input. Jadi ekspresi reguler cocok dengan opsional -diikuti dengan satu atau beberapa digit desimal.
Ignacio Vazquez-Abrams
grumbles re: tautan ABS
Charles Duffy
Ini bersinggungan, tetapi perhatikan bahwa saat menentukan rentang karakter Anda bisa mendapatkan hasil yang aneh; misalnya, [A-z]tidak akan hanya memberikan A-Zdan a-ztetapi juga \ , [, ], ^, _, dan `.
Doktor J
Selain itu, berdasarkan susunan karakter ( lihat pertanyaan / jawaban terkait ini ), sesuatu seperti d[g-i]{2}bisa berakhir tidak hanya cocok digtetapi juga dishdalam susunan yang disarankan oleh jawaban itu (di mana shdigraf dianggap sebagai karakter tunggal, disusun setelahnya h).
Doktor J
61

Wow ... ada banyak solusi bagus di sini !! Dari semua solusi di atas, saya setuju dengan @nortally bahwa menggunakan -eqsatu liner adalah yang paling keren.

Saya menjalankan GNU bash, versi 4.1.5(Debian). Saya juga telah memeriksa ini di ksh (SunSO 5.10).

Ini adalah versi saya untuk memeriksa apakah $1itu bilangan bulat atau bukan:

if [ "$1" -eq "$1" ] 2>/dev/null
then
    echo "$1 is an integer !!"
else
    echo "ERROR: first parameter must be an integer."
    echo $USAGE
    exit 1
fi

Pendekatan ini juga memperhitungkan bilangan negatif, yang beberapa solusi lain akan memiliki hasil negatif yang salah, dan itu akan memungkinkan awalan "+" (misalnya +30) yang jelas merupakan bilangan bulat.

Hasil:

$ int_check.sh 123
123 is an integer !!

$ int_check.sh 123+
ERROR: first parameter must be an integer.

$ int_check.sh -123
-123 is an integer !!

$ int_check.sh +30
+30 is an integer !!

$ int_check.sh -123c
ERROR: first parameter must be an integer.

$ int_check.sh 123c
ERROR: first parameter must be an integer.

$ int_check.sh c123
ERROR: first parameter must be an integer.

Solusi yang diberikan oleh Ignacio Vazquez-Abrams juga sangat rapi (jika Anda suka regex) setelah dijelaskan. Namun, ini tidak menangani bilangan positif dengan +awalan, tetapi dapat dengan mudah diperbaiki seperti di bawah ini:

[[ $var =~ ^[-+]?[0-9]+$ ]]
Peter Ho
sumber
Bagus! Sangat mirip dengan ini .
devnull
Iya. Itu serupa. Namun, saya sedang mencari solusi satu baris untuk pernyataan "jika". Saya pikir saya tidak benar-benar perlu memanggil fungsi untuk ini. Juga, saya dapat melihat bahwa pengalihan stderr ke stdout dalam fungsinya. Ketika saya mencoba, pesan stderr "ekspresi integer diharapkan" ditampilkan yang tidak diinginkan untuk saya.
Peter Ho
Terima kasih! Saya akan menyebut yang ini mudah dan elegan.
Ezra Nugroho
2
Ada perbedaan penting antara solusi Anda dan regex: ukuran bilangan bulat diperiksa terhadap batas bash (di komputer saya 64 bit). Batas ini tidak mencapai solusi regexp. Jadi solusi Anda akan gagal pada nomor yang lebih besar dari 9223372036854775807 pada komputer 64bits.
vaab
2
Seperti yang baru-baru ini saya temukan, ada beberapa peringatan .
Kyle Strand
28

Terlambat ke pesta di sini. Saya sangat terkejut tidak ada jawaban yang menyebutkan solusi yang paling sederhana, tercepat, dan paling portabel; yang casepernyataan.

case ${variable#[-+]} in
  *[!0-9]* | '') echo Not a number ;;
  * ) echo Valid number ;;
esac

Pemangkasan tanda apa pun sebelum perbandingan terasa seperti sedikit retakan, tetapi itu membuat ekspresi untuk pernyataan kasus jauh lebih sederhana.

tripleee
sumber
4
Saya berharap saya dapat memberikan suara positif ini sekali setiap kali saya kembali ke pertanyaan ini karena penipuan. Saya yakin bahwa solusi sederhana namun sesuai dengan POSIX terkubur di bagian bawah.
Adrian Frühwirth
3
Mungkin Anda harus menjaga string kosong:''|*[!0-9]*)
Niklas Peter
2
BTW: Ini sintaks yang didokumentasikan: tldp.org/LDP/abs/html/string-manipulation.html
Niklas Peter
Saya tidak terlalu memaafkan ABS; ini jelas juga didokumentasikan dalam manual Bash. Bagaimanapun, bagian yang Anda tautkan tidak menjelaskan konstruksi khusus ini, melainkan misalnya jawaban @ Nortally.
tripleee
@tripleee Dokumen yang ditautkan menjelaskan konstruksi untuk menghapus awalan string dari variabel yang digunakan dalam baris kasus. Itu hanya di bagian bawah halaman, tetapi tidak ada jangkar, jadi saya tidak bisa langsung menautkannya, lihat bagian "Penghapusan Substring"
Niklas Peter
10

Saya suka solusi yang menggunakan -eqpengujian, karena pada dasarnya ini adalah satu baris.

Solusi saya sendiri adalah menggunakan perluasan parameter untuk membuang semua angka dan melihat apakah masih ada yang tersisa. (Saya masih menggunakan 3.0, belum pernah menggunakan [[atau exprsebelumnya, tapi senang bertemu dengan mereka.)

if [ "${INPUT_STRING//[0-9]}" = "" ]; then
  # yes, natural number
else
  # no, has non-numeral chars
fi
ke utara
sumber
4
Ini dapat lebih ditingkatkan dengan menggunakan [ -z "${INPUT_STRING//[0-9]}" ]solusi yang sangat bagus!
ShellFish
bagaimana dengan tanda negatif?
scottysseus
The -eqsolusi memiliki beberapa masalah; lihat di sini: stackoverflow.com/a/808740/1858225
Kyle Strand
INPUT_STRING kosong dianggap sebagai angka, jadi gagal untuk kasus saya
Manwe
9

Untuk portabilitas ke pra-Bash 3.1 (saat =~tes diperkenalkan), gunakan expr.

if expr "$string" : '-\?[0-9]\+$' >/dev/null
then
  echo "String is a valid integer."
else
  echo "String is not a valid integer."
fi

expr STRING : REGEXmencari REGEX berlabuh di awal STRING, menggemakan grup pertama (atau panjang kecocokan, jika tidak ada) dan mengembalikan sukses / gagal. Ini adalah sintaks regex lama, oleh karena itu kelebihannya \. -\?berarti "mungkin -", [0-9]\+berarti "satu atau lebih digit", dan $berarti "akhir string".

Bash juga mendukung gumpalan yang diperpanjang, meskipun saya tidak ingat dari versi mana dan seterusnya.

shopt -s extglob
case "$string" of
    @(-|)[0-9]*([0-9]))
        echo "String is a valid integer." ;;
    *)
        echo "String is not a valid integer." ;;
esac

# equivalently, [[ $string = @(-|)[0-9]*([0-9])) ]]

@(-|)berarti " -atau tidak ada", [0-9]berarti "digit", dan *([0-9])berarti "nol atau lebih digit".

singkat
sumber
Terima kasih efemient, terima kasih banyak. Saya belum pernah melihat = ~ sintaks sebelumnya - dan masih tidak tahu apa artinya - kira-kira sama ?! ... Saya tidak pernah bersemangat untuk program BASH tetapi adalah diperlukan beberapa kali!
Richard T
Dalam awk, ~adalah operator "pencocokan ekspresi reguler". Di Perl (seperti yang disalin dari C), ~telah digunakan untuk "pelengkap bit", jadi mereka menggunakannya =~. Notasi ini kemudian disalin ke beberapa bahasa lain. (Perl 5.10 dan Perl 6 menyukai ~~lebih, tapi itu tidak berdampak di sini.) Saya kira Anda dapat melihatnya sebagai semacam perkiraan kesetaraan ...
ephemient
Posting yang bagus DAN edit! Saya sangat menghargai menjelaskan apa artinya. Saya berharap saya dapat menandai postingan Anda dan Ignacio sebagai jawaban yang benar. -mengernyit- Kalian berdua hebat. Tetapi karena Anda memiliki reputasi ganda yang dia lakukan, saya memberikannya kepada Ignacio - harap Anda mengerti! -senyum-
Richard T
4

Berikut ini adalah pandangan lain (hanya menggunakan perintah test builtin dan kode kembaliannya):

function is_int() { return $(test "$@" -eq "$@" > /dev/null 2>&1); } 

input="-123"

if $(is_int "${input}");
then
   echo "Input: ${input}"
   echo "Integer: $[${input}]"
else
   echo "Not an integer: ${input}"
fi
hans
sumber
1
Ini tidak perlu digunakan $()dengan if. Ini bekerja: if is_int "$input". Selain itu, $[]formulir sudah tidak digunakan lagi. Gunakan $(())sebagai gantinya. Di dalam keduanya, tanda dolar dapat dihilangkan: Tanda echo "Integer: $((input))"kurung kurawal tidak diperlukan di mana pun dalam skrip Anda.
Dijeda sampai pemberitahuan lebih lanjut.
Saya berharap ini juga menangani angka dalam notasi dasar Bash sebagai bilangan bulat yang valid (yang tentu saja menurut beberapa definisi mereka; tetapi mungkin tidak sesuai dengan milik Anda) tetapi testtampaknya tidak mendukung ini. [[tidak. [[ 16#aa -eq 16#aa ]] && echo integermencetak "integer".
tripleee
Perhatikan bahwa [[mengembalikan positif palsu untuk metode ini; misalnya [[ f -eq f ]]berhasil. Jadi harus menggunakan testatau [.
spinup
3

Anda dapat menghapus non-digit dan melakukan perbandingan. Berikut skrip demo:

for num in "44" "-44" "44-" "4-4" "a4" "4a" ".4" "4.4" "-4.4" "09"
do
    match=${num//[^[:digit:]]}    # strip non-digits
    match=${match#0*}             # strip leading zeros
    echo -en "$num\t$match\t"
    case $num in
        $match|-$match)    echo "Integer";;
                     *)    echo "Not integer";;
    esac
done

Seperti inilah keluaran pengujiannya:

44 44 Bilangan bulat
-44 44 Integer
44- 44 Bukan integer
4-4 44 Bukan bilangan bulat
a4 4 Bukan integer
4a 4 Bukan bilangan bulat
.4 4 Bukan bilangan bulat
4.4 44 Bukan bilangan bulat
-4.4 44 Bukan integer
09 9 Bukan bilangan bulat
Dijeda sampai pemberitahuan lebih lanjut.
sumber
Hai Dennis, Terima kasih telah memperkenalkan saya pada sintaks di sebelah kanan pencocokan = di atas. Saya belum pernah memperhatikan sintaks tipe itu sebelumnya. Saya mengenali beberapa sintaksis dari tr (sebuah utilitas yang belum cukup saya kuasai, tetapi kadang-kadang gagal); di mana saya bisa membaca tentang sintaks seperti itu? (yaitu, apa yang disebut hal semacam ini?) Terima kasih.
Richard T
Anda dapat melihat di halaman manual Bash di bagian bernama "Perluasan Parameter" untuk informasi tentang ${var//string}dan ${var#string}dan di bagian bernama "Pencocokan Pola" untuk [^ [: digit:]] `(yang juga dibahas dalam man 7 regex).
Dijeda sampai pemberitahuan lebih lanjut.
1
match=${match#0*}tidak tidak menghapus nol terkemuka, itu strip paling banyak satu nol. Menggunakan ekspansi ini hanya dapat dicapai dengan menggunakan extglobvia match=${match##+(0)}.
Adrian Frühwirth
Bukankah 9 atau 09 adalah integer?
Mike Q
@ MikeQ: 09bukan bilangan bulat jika Anda menganggap bilangan bulat tidak memiliki nol di depannya. Tesnya adalah apakah input ( 09) sama dengan versi yang sudah dibersihkan ( 9- integer) dan tidak.
Dijeda sampai pemberitahuan lebih lanjut.
2

Bagi saya, solusi paling sederhana adalah menggunakan variabel di dalam (())ekspresi, sebagai berikut:

if ((VAR > 0))
then
  echo "$VAR is a positive integer."
fi

Tentu saja, solusi ini hanya valid jika nilai nol tidak masuk akal untuk aplikasi Anda. Itu benar dalam kasus saya, dan ini jauh lebih sederhana daripada solusi lainnya.

Seperti yang ditunjukkan di komentar, ini bisa membuat Anda terkena serangan eksekusi kode: (( ))Operator mengevaluasi VAR, seperti yang dinyatakan di bagian Arithmetic Evaluationhalaman manual bash (1) . Oleh karena itu, Anda tidak boleh menggunakan teknik ini ketika sumber konten VARtidak pasti (Anda juga tidak boleh menggunakan bentuk ekspansi variabel APAPUN, tentunya).

Trebor Rude
sumber
Anda bahkan bisa lebih sederhana denganif (( var )); then echo "$var is an int."; fi
Aaron R.
2
Tapi itu juga akan mengembalikan nilai true untuk bilangan bulat negatif, @aaronr, bukan yang dicari OP.
Trebor Rude
2
Ini berbahaya, lihat: n = 1; var = "n"; if ((var)); lalu echo "$ var is an int."; fi
jarno
2
Ini adalah ide yang sangat buruk dan tunduk pada eksekusi kode sewenang-wenang: mencobanya sendiri: VAR='a[$(ls)]'; if ((VAR > 0)); then echo "$VAR is a positive integer"; fi. Pada titik ini Anda senang saya tidak memasukkan perintah jahat sebagai gantinya ls. Karena OP menyebutkan input pengguna , saya sangat berharap Anda tidak menggunakan ini dengan input pengguna dalam kode produksi!
gniourf_gniourf
Ini tidak berfungsi jika string berisi beberapa digit seperti:agent007
brablc
1

atau dengan sed:

   test -z $(echo "2000" | sed s/[0-9]//g) && echo "integer" || echo "no integer"
   # integer

   test -z $(echo "ab12" | sed s/[0-9]//g) && echo "integer" || echo "no integer"
   # no integer
knipwim
sumber
Di Bash dan beberapa shell "Bourne plus" lainnya, Anda dapat menghindari penggantian perintah dan perintah eksternal dengan test -z "${string//[0-9]/}" && echo "integer" || echo "no integer"... meskipun itu pada dasarnya menggandakan jawaban Dennis Williamson
tripleee
Terima kasih! Satu-satunya jawaban yang benar-benar berfungsi di sini!
pengguna
Alternatif senyap:if [[ -n "$(printf "%s" "${2}" | sed s/[0-9]//g)" ]]; then
pengguna
0

Menambah jawaban dari Ignacio Vazquez-Abrams. Ini akan memungkinkan tanda + untuk mendahului bilangan bulat, dan itu akan memungkinkan sejumlah nol sebagai titik desimal. Misalnya, ini akan memungkinkan +45.00000000 dianggap sebagai bilangan bulat.
Namun, $ 1 harus diformat agar mengandung koma desimal. 45 tidak dianggap sebagai bilangan bulat di sini, tetapi 45.0 dianggap.

if [[ $1 =~ ^-?[0-9]+.?[0]+$ ]]; then
    echo "yes, this is an integer"
elif [[ $1 =~ ^\+?[0-9]+.?[0]+$ ]]; then
    echo "yes, this is an integer"
else
    echo "no, this is not an integer"
fi
JustinMT
sumber
Adakah alasan Anda menggunakan dua ekspresi reguler yang berbeda untuk bilangan positif dan negatif, daripada ^[-+]?[0-9]...?
tripleee
0

Untuk tertawa, saya kira-kira dengan cepat mengerjakan satu set fungsi untuk melakukan ini (is_string, is_int, is_float, adalah alpha string, atau lainnya) tetapi ada cara yang lebih efisien (lebih sedikit kode) untuk melakukan ini:

#!/bin/bash

function strindex() {
    x="${1%%$2*}"
    if [[ "$x" = "$1" ]] ;then
        true
    else
        if [ "${#x}" -gt 0 ] ;then
            false
        else
            true
        fi
    fi
}

function is_int() {
    if is_empty "${1}" ;then
        false
        return
    fi
    tmp=$(echo "${1}" | sed 's/[^0-9]*//g')
    if [[ $tmp == "${1}" ]] || [[ "-${tmp}" == "${1}" ]] ; then
        #echo "INT (${1}) tmp=$tmp"
        true
    else
        #echo "NOT INT (${1}) tmp=$tmp"
        false
    fi
}

function is_float() {
    if is_empty "${1}" ;then
        false
        return
    fi
    if ! strindex "${1}" "-" ; then
        false
        return
    fi
    tmp=$(echo "${1}" | sed 's/[^a-z. ]*//g')
    if [[ $tmp =~ "." ]] ; then
        #echo "FLOAT  (${1}) tmp=$tmp"
        true
    else
        #echo "NOT FLOAT  (${1}) tmp=$tmp"
        false
    fi
}

function is_strict_string() {
    if is_empty "${1}" ;then
        false
        return
    fi
    if [[ "${1}" =~ ^[A-Za-z]+$ ]]; then
        #echo "STRICT STRING (${1})"
        true
    else
        #echo "NOT STRICT STRING (${1})"
        false
    fi
}

function is_string() {
    if is_empty "${1}" || is_int "${1}" || is_float "${1}" || is_strict_string "${1}" ;then
        false
        return
    fi
    if [ ! -z "${1}" ] ;then
        true
        return
    fi
    false
}
function is_empty() {
    if [ -z "${1// }" ] ;then
        true
    else
        false
    fi
}

Jalankan melalui beberapa tes di sini, saya mendefinisikan bahwa -44 adalah int tetapi 44- bukan dll ..:

for num in "44" "-44" "44-" "4-4" "a4" "4a" ".4" "4.4" "-4.4" "09" "hello" "h3llo!" "!!" " " "" ; do
    if is_int "$num" ;then
        echo "INT = $num"

    elif is_float "$num" ;then
        echo "FLOAT = $num"

    elif is_string "$num" ; then
        echo "STRING = $num"

    elif is_strict_string "$num" ; then
        echo "STRICT STRING = $num"
    else
        echo "OTHER = $num"
    fi
done

Keluaran:

INT = 44
INT = -44
STRING = 44-
STRING = 4-4
STRING = a4
STRING = 4a
FLOAT = .4
FLOAT = 4.4
FLOAT = -4.4
INT = 09
STRICT STRING = hello
STRING = h3llo!
STRING = !!
OTHER =  
OTHER = 

CATATAN: Memimpin 0 dapat menyimpulkan sesuatu yang lain saat menambahkan angka seperti oktal sehingga akan lebih baik untuk menghapusnya jika Anda bermaksud memperlakukan '09' sebagai int (yang saya lakukan) (misalnya expr 09 + 0atau strip dengan sed)

Mike Q
sumber