Dizenin geçerli bir tam sayı olup olmadığını test edin


117

Yeterince yaygın bir şey yapmaya çalışıyorum: Kullanıcı girdisini bir kabuk betiğinde ayrıştırın. Kullanıcı geçerli bir tamsayı sağladıysa, komut dosyası bir şey yapar ve geçerli değilse başka bir şey yapar. Sorun şu ki, bunu yapmanın kolay (ve oldukça zarif) bir yolunu bulamadım - onu karakter karakter ayırmak istemiyorum.

Bunun kolay olduğunu biliyorum ama nasıl yapılacağını bilmiyorum. Bunu bir düzine dilde yapabilirdim ama BASH yapamazdım!

Araştırmamda şunu buldum:

Bir dizenin 10 tabanında geçerli bir gerçek sayı içerip içermediğini test etmek için normal ifade

Ve orada regex'ten bahseden bir cevap var, ama bildiğim kadarıyla bu, C'de (diğerlerinin yanı sıra) mevcut bir fonksiyon. Yine de harika bir cevap gibi görünen bir cevap vardı, bu yüzden grep ile denedim ama grep bununla ne yapacağını bilmiyordu. Kutumda onu bir PERL regexp - nada olarak ele almak anlamına gelen -P'yi denedim. Dash E (-E) de çalışmadı. Ve -F de yapmadı.

Açık olmak gerekirse, bunun gibi bir şey deniyorum, herhangi bir çıktı arıyorum - oradan, elde ettiğim her şeyden yararlanmak için senaryoyu hackleyeceğim. (IOW, uygun olmayan bir girişin geçerli bir satır tekrarlanırken hiçbir şey döndürmemesini bekliyordum.)

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

Lütfen birisi bunun en kolay nasıl yapıldığını gösterebilir mi?

Açıkçası, bence bu TEST'in kısa bir geleceği. Bunun gibi bir bayrağı olmalı

if [ -I "string" ] ;
then
   echo "String is a valid integer."
else
   echo "String is not a valid integer."
fi

4
Bilginize: [eski uyumludur test; [[Bash'in daha fazla işlem ve farklı alıntı kuralları içeren yeni özelliği. Zaten Bash'e bağlı kalmaya karar verdiyseniz, gidin [[(gerçekten çok daha güzel); diğer mermilere taşınabilirliğe ihtiyacınız varsa, [[tamamen kaçının .
ephemient

Yanıtlar:


183
[[ $var =~ ^-?[0-9]+$ ]]
  • ^Giriş deseni başladığını belirten
  • -Bir hazır olduğunu "-"
  • ?Araçlar "0 ya da önceki 1 ( -)"
  • +Araçlar "önceki 1 ya da daha fazla ( [0-9])"
  • $Giriş modeli sonunu gösterir

Dolayısıyla normal ifade, isteğe bağlı -(negatif sayılar için) ve ardından bir veya daha fazla ondalık basamakla eşleşir .

Referanslar :


3
Teşekkürler Ignacio, birazdan deneyeceğim. Biraz öğrenebilmem için açıklamayı düşünür müsün? Anladığım kadarıyla, "Dizenin başında (^), bir eksi işareti (-) isteğe bağlıdır (?), Ardından sıfır ile 9 arasında herhangi bir sayıda karakter gelir" ... ve bu durumda + $ demek? Teşekkürler.
Richard T

10
" +Öncekilerden 1 veya daha fazlası" anlamına gelir $ve giriş modelinin sonunu belirtir. Dolayısıyla normal ifade, isteğe bağlı -bir veya daha fazla ondalık basamakla eşleşir .
Ignacio Vazquez-Abrams

homurdanıyor: ABS bağlantısı
Charles Duffy

Bu bir teğet, ancak karakter aralıklarını belirlerken garip sonuçlar alabileceğinizi unutmayın; Örneğin, [A-z]size sadece vermezdim A-Zve a-zaynı zamanda \ , [, ], ^, _, ve `.
Doktor J

Ek olarak, karakter harmanlamasına dayalı olarak ( bu ilgili soruya / cevaba bakın ) buna benzer bir şey d[g-i]{2}sadece eşleşmeyebilir, digaynı zamanda dishbu cevabın önerdiği harmanlamada da sonuçlanabilir (burada shdigraph tek bir karakter olarak kabul edilir, sonra harmanlanır h).
Doktor J

61

Vay be ... burada çok iyi çözüm var !! Yukarıdaki tüm çözümlerden @nortally'ye katılıyorum, -eqtek astarı kullanmanın en havalı olduğu.

GNU bash sürümünü 4.1.5(Debian) çalıştırıyorum. Bunu ksh'de de kontrol ettim (SunSO 5.10).

İşte $1tam sayı olup olmadığını kontrol etme versiyonum :

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

Bu yaklaşım aynı zamanda, diğer çözümlerden bazılarının hatalı bir negatif sonuca sahip olacağı negatif sayıları da hesaba katar ve açıkça bir tam sayı olan "+" önekine (örneğin +30) izin verir.

Sonuçlar:

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

Ignacio Vazquez-Abrams tarafından sağlanan çözüm de açıklandıktan sonra çok temizdi (regex'i seviyorsanız). Bununla birlikte, +ön ekli pozitif sayıları işlemez , ancak aşağıdaki gibi kolayca düzeltilebilir:

[[ $var =~ ^[-+]?[0-9]+$ ]]

Güzel! Oldukça benzer bu , gerçi.
devnull

Evet. Benzer. Ancak, "eğer" ifadesi için tek satırlık bir çözüm arıyordum. Bunun için gerçekten bir fonksiyon çağırmama gerek olmadığını düşündüm. Ayrıca, stderr'in işlevde stdout'a yeniden yönlendirildiğini görebiliyorum. Denediğimde, benim için arzu edilmeyen "tamsayı ifadesi bekleniyor" stderr mesajı görüntülendi.
Peter Ho

Teşekkür ederim! Ben buna kolay ve zarif derim.
Ezra Nugroho

2
Çözümünüz ile normal ifade arasında dikkate değer bir fark vardır: tamsayının boyutu bash sınırlarına göre kontrol edilir (bilgisayarımda 64 bittir). Bu sınır, regexp çözümüne ulaşmaz. Dolayısıyla, çözümünüz 64 bit bilgisayarlarda 9223372036854775807'den büyük sayılarda başarısız olacaktır.
vaab

2
Yakın zamanda keşfettiğim gibi, bazı uyarılar var .
Kyle Strand

28

Partiye geç geliyorum. Yanıtların hiçbirinin en basit, en hızlı, en taşınabilir çözümden bahsetmediğine çok şaşırdım; casebeyanı.

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

Karşılaştırmadan önce herhangi bir işaretin kırpılması biraz hile gibi geliyor, ancak bu durum ifadesinin ifadesini çok daha basit hale getiriyor.


4
Keşke dupes yüzünden bu soruya her geldiğimde bunu bir kez yükseltebilseydim. Basit ama POSIX uyumlu bir çözümün altta gömülü olması dişlilerimi öğütüyor.
Adrian Frühwirth

3
Belki boş dizelerle ilgilenmelisin:''|*[!0-9]*)
Niklas Peter


ABS'yi özellikle hoş görmüyorum; bu açıkça Bash kılavuzunda da belgelenmiştir. Her neyse, bağlandığınız bölüm bu belirli yapıyı değil, örneğin @ Nortally'nin cevabını açıklıyor.
üçlü

@tripleee Bağlı belge, vaka satırında kullanılan bir değişkenden bir dizge önekini kaldırmak için yapıyı açıklar. Sayfanın hemen altında, ancak bağlayıcı yok, bu yüzden doğrudan ona bağlantı kuramadım, "Alt Dize Kaldırma" bölümüne bakın
Niklas Peter

10

Çözümü kullanarak seviyorum -eqTesti , çünkü temelde tek satırlık bir çözüm.

Benim çözümüm, tüm sayıları atmak ve kalan bir şey olup olmadığını görmek için parametre genişletmeyi kullanmaktı. (Hala 3.0 kullanıyorum, kullanmadım [[veya daha exprönce, ancak onlarla tanıştığıma memnun oldum.)

if [ "${INPUT_STRING//[0-9]}" = "" ]; then
  # yes, natural number
else
  # no, has non-numeral chars
fi

4
Bu, [ -z "${INPUT_STRING//[0-9]}" ]ancak gerçekten güzel bir çözüm kullanılarak daha da geliştirilebilir !
ShellFish

peki ya olumsuz işaretler?
scottysseus

-eqSolüsyon bazı sorunlar vardır; buraya bakın: stackoverflow.com/a/808740/1858225
Kyle Strand

Boş INPUT_STRING sayı olarak kabul edilir, bu yüzden benim durumum için başarısız
Manwe

9

Bash 3.1'den önce taşınabilirlik için ( =~test sunulduğunda) kullanın 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 : REGEXSTRING'in başlangıcında sabitlenmiş REGEX'i arar, ilk grubu (veya yoksa eşleşmenin uzunluğunu) yineler ve başarı / başarısızlık döndürür. Bu eski normal sözdizimidir, dolayısıyla fazlalıktır \. -\?"belki -" [0-9]\+anlamına gelir, "bir veya daha fazla rakam" anlamına gelir ve$ anlamına gelir "dizenin sonu" anlamına gelir.

Bash ayrıca genişletilmiş globları da destekliyor, ancak hangi sürümden itibaren olduğunu hatırlamıyorum.

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])) ]]

@(-|)" -veya hiçbiri" [0-9]anlamına gelir, "rakam" *([0-9])anlamına gelir ve "sıfır veya daha fazla rakam" anlamına gelir.


Kısa sürede teşekkür ederim, minnettarım. = ~ Sözdizimini daha önce hiç görmemiştim - ve hala ne anlama geldiği hakkında hiçbir fikrim yok - yaklaşık olarak eşit mi ?! ... Ben BASH programa heyecan olmamıştım ama olan bazı zamanlar gerekli!
Richard T

İçinde awk, ~"normal ifade eşleşmesi" operatörü idi. Perl'de (C'den kopyalandığı gibi), ~zaten "bit tamamlama" için kullanıldı, bu yüzden kullandılar =~. Bu sonraki gösterim birkaç başka dile kopyalandı. (Perl 5.10 ve Perl 6 ~~daha çok sever , ancak bunun burada bir etkisi yoktur.) Sanırım buna bir tür yaklaşık eşitlik olarak bakabilirsiniz ...
ephemient

Mükemmel gönderi VE düzenleme! Bunun ne anlama geldiğini açıkladığım için gerçekten minnettarım. Keşke hem sizin hem de Ignacio'nun gönderilerini doğru cevap olarak işaretleyebilseydim. -frown- İkiniz de harikasınız. Ama onun sahip olduğu itibarı iki katına çıkardığına göre, bunu Ignacio'ya veriyorum - umarım anlarsın! -smile-
Richard T

4

İşte buna başka bir yaklaşım (yalnızca test yerleşik komutunu ve dönüş kodunu kullanarak):

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

1
Kullanımı gerekli değil $()ile if. Bu çalışır: if is_int "$input". Ayrıca $[]form kullanımdan kaldırılmıştır. $(())Bunun yerine kullanın . Her ikisinin içinde de dolar işareti ihmal edilebilir: echo "Integer: $((input))"Senaryonuzun hiçbir yerinde küme parantezleri gerekli değildir.
sonraki duyuruya kadar duraklatıldı.

Bunun ayrıca Bash'in temel gösterimindeki sayıları geçerli tamsayılar olarak ele almasını beklerdim (ki bunlar tabii ki bazı tanımlara göre; ancak sizinki ile uyuşmayabilir) ancak testbunu desteklemiyor gibi görünüyor. [[olsa da. [[ 16#aa -eq 16#aa ]] && echo integer"tamsayı" yazar.
üçlü

[[Bu yöntem için yanlış pozitif döndürdüğünü unutmayın ; örneğin [[ f -eq f ]]başarılı olur. Yani testveya kullanması gerekir [.
spinup

3

Rakam olmayanları çıkarabilir ve bir karşılaştırma yapabilirsiniz. İşte bir demo komut dosyası:

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

Test çıktısı şuna benzer:

44 44 Tamsayı
-44 44 Tam Sayı
44- 44 Tamsayı değil
4-4 44 Tamsayı değil
a4 4 Tamsayı değil
4a 4 Tamsayı değil
.4 4 Tamsayı değil
4.4 44 Tamsayı değil
-4.4 44 Tamsayı değil
09 9 Tamsayı değil

Merhaba Dennis, Match = yukarıdaki sözdizimini bana tanıttığınız için teşekkür ederiz. Bu tür sözdizimini daha önce hiç fark etmemiştim. Tr'deki sözdiziminin bir kısmını tanıyorum (tam olarak ustalaşmadığım ama bazen yolumu karıştırdığım bir yardımcı program); böyle bir sözdizimi hakkında nereden okuyabilirim? (yani, bu tür şeylere ne denir?) Teşekkürler.
Richard T

Sen hakkında bilgi için "Parametre Genişleme" olarak adlandırılan bölümünde Bash kılavuz sayfasında bakabilirsiniz ${var//string}ve ${var#string}`(ayrıca kaplı olan ve [: haneli] ^ [] için "Modeli Eşleştirme" olarak adlandırılan bölümünde man 7 regex).
sonraki duyuruya kadar duraklatıldı.

1
match=${match#0*}etmez olmayan baştaki sıfırları, en az bir sıfır şeritler. Genişletme kullanılarak bu yalnızca extglobaracılığıyla sağlanabilir match=${match##+(0)}.
Adrian Frühwirth

9 veya 09 bir tam sayı değil mi?
Mike Q

@MikeQ: 09bir tamsayının başında sıfır olmadığını düşünürseniz bir tamsayı değildir. Test, input ( 09) 'un sterilize edilmiş bir sürüme ( 9- bir tamsayı) eşit olup olmadığı ve eşit olup olmadığıdır.
sonraki duyuruya kadar duraklatıldı.

2

Benim için en basit çözüm, değişkeni bir (())ifadenin içinde kullanmaktı , şöyle ki:

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

Elbette bu çözüm, uygulamanız için sıfır değeri bir anlam ifade etmiyorsa geçerlidir. Bu benim durumumda doğru oldu ve bu diğer çözümlerden çok daha basit.

Yorumlarda belirtildiği gibi, bu sizi bir kod yürütme saldırısına maruz bırakabilir: (( ))Operatör , bash (1) man sayfasının bölümünde VARbelirtildiği gibi değerlendirir . Bu nedenle, içeriğinin kaynağı belirsiz olduğunda bu tekniği kullanmamalısınız (tabii ki başka HERHANGİ bir değişken genişletme biçimi kullanmamalısınız).Arithmetic EvaluationVAR


Hatta daha basit gidebilirif (( var )); then echo "$var is an int."; fi
Aaron R.

2
Ancak bu, OP'nin aradığı şey değil, @aaronr gibi negatif tamsayılar için de doğru olacaktır.
Trebor Rude

2
Bu tehlikelidir, bakınız: n = 1; var = "n"; eğer ((var)); then echo "$ var bir int."; fi
jarno

2
Bu çok kötü bir fikir ve keyfi kod çalıştırma tabidir: it kendinizi deneyin: VAR='a[$(ls)]'; if ((VAR > 0)); then echo "$VAR is a positive integer"; fi. Bu noktada, yerine kötü bir komuta girmediğim için memnunsunuz ls. OP kullanıcı girdisinden bahsettiği için , bunu üretim kodunda kullanıcı girdisi ile kullanmadığınızı umuyorum!
gniourf_gniourf

Dize aşağıdaki gibi bazı rakamlar içeriyorsa bu işe yaramaz:agent007
brablc

1

veya sed ile:

   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

Bash ve diğer bazı "Bourne plus" mermilerinde, komut ikamesinden ve harici komuttan kaçınabilirsiniz test -z "${string//[0-9]/}" && echo "integer" || echo "no integer"... Dennis Williamson'ın cevabını
yineler

Teşekkürler! Aslında burada işe yarayan tek cevap!
kullanıcı

Sessiz alternatif:if [[ -n "$(printf "%s" "${2}" | sed s/[0-9]//g)" ]]; then
kullanıcı

0

Ignacio Vazquez-Abrams'ın cevabına ekleniyor. Bu, + işaretinin tamsayıdan önce gelmesine izin verecek ve ondalık nokta olarak herhangi bir sayıda sıfıra izin verecektir. Örneğin bu, +45.00000000'ün bir tamsayı olarak kabul edilmesini sağlayacaktır.
Ancak $ 1, ondalık nokta içerecek şekilde biçimlendirilmelidir. 45 burada bir tam sayı olarak kabul edilmez, ancak 45.0 eşittir.

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

Pozitif ve negatif sayılar için ^[-+]?[0-9]... yerine iki farklı normal ifade kullanmanızın bir nedeni var mı ?
2017

0

Gülmek için kabaca bunu yapmak için bir dizi işlevi kabaca çabucak çözdüm (is_string, is_int, is_float, is alpha string, or other) ama bunu yapmanın daha verimli (daha az kod) yolları var:

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

Burada bazı testler yapın, tanımladım ki -44 bir int ama 44- değil vs ..:

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

Çıktı:

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 = 

NOT: Baştaki 0'lar, sekizlik gibi sayılar eklerken başka bir şey çıkarabilir, bu nedenle '09'u bir int olarak değerlendirmeyi düşünüyorsanız (ki bunu yapıyorum) (örneğin expr 09 + 0sed ile soyun) bunları çıkarmak daha iyi olacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.