web-dev-qa-db-ja.com

文字列が有効な整数かどうかをテストします

十分に一般的なことをしようとしています:シェルスクリプトでユーザー入力を解析します。ユーザーが有効な整数を指定した場合、スクリプトは1つのことを行い、無効な場合は別のことを行います。問題は、これを行うための簡単な(そしてかなりエレガントな)方法を見つけていないことです。文字ごとに区別する必要はありません。

これは簡単なはずですが、方法はわかりません。私は何十もの言語でそれをすることができましたが、BASHではできませんでした!

私の研究でこれを見つけました:

文字列が基数10の有効な実数で構成されているかどうかをテストする正規表現

そしてそこには正規表現について話す答えがありますが、私が知る限り、それはCで利用可能な関数です(とりわけ)。それでも、それは素晴らしい答えのように見えたので、grepで試しましたが、grepはそれをどうするかわかりませんでした。私は-Pを試しましたが、これは私のボックスではPerl正規表現として扱うことを意味しています-nada。ダッシュE(-E)も機能しませんでした。また、-Fもしませんでした。

明確にするために、私はこのようなことを試みて、出力を探しています-そこから、私が得たものを利用するためにスクリプトをハックします。 (IOW、有効な行が繰り返される間、不適合の入力は何も返さないと予想していました。)

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

誰かがこれが最も簡単に行われる方法を説明していただけますか?

率直に言って、これはTESTの欠点です。このようなフラグが必要です

if [ -I "string" ] ;
then
   echo "String is a valid integer."
else
   echo "String is not a valid integer."
fi
105
Richard T
[[ $var =~ ^-?[0-9]+$ ]]
  • ^は、入力パターンの始まりを示します
  • -はリテラル「-」です
  • ?は、「前の0または1(-)」を意味します
  • +は、「1つ以上の先行する([0-9])」を意味します
  • $は、入力パターンの終わりを示します

したがって、正規表現は、オプションの-(負の数の場合)に一致し、その後に1つ以上の10進数が続きます。

参照

167

うわー...ここにはとても良い解決策がたくさんあります!!上記のすべてのソリューションの中で、-eqライナーを1つ使用することが最もクールであることに@nortallyに同意します。

GNU bash、バージョン4.1.5(Debian)を実行しています。これもksh(SunSO 5.10)で確認しました。

$1が整数かどうかをチェックする私のバージョンは次のとおりです。

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

このアプローチは負の数も考慮しますが、他のソリューションのいくつかは誤った負の結果になり、明らかに整数である「+」(例:+30)のプレフィックスを許可します。

結果:

$ 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が提供するソリューションは、説明された後も非常にきれいでした(正規表現が好きな場合)。ただし、+プレフィックスを持つ正の数は処理しませんが、以下のように簡単に修正できます。

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

ここでパーティーの後発。最も簡単で、最速で、最も移植性の高いソリューションについて言及している回答がないことに、私は非常に驚いています。 caseステートメント。

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

比較前の記号のトリミングは、ちょっとしたハックのように感じますが、それにより、caseステートメントの式が非常に簡単になります。

25
tripleee

Bash 3.1以前への移植性(=~テストが導入されたとき)には、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 : REGEXは、STRINGの先頭にアンカーされたREGEXを検索し、最初のグループ(または一致しない場合は一致の長さ)をエコーし​​、成功/失敗を返します。これは古い正規表現構文であるため、余分な\です。 -\?は「多分-」を意味し、[0-9]\+は「1桁以上」を意味し、$は「文字列の終わり」を意味します。

Bashは拡張グロブもサポートしていますが、どのバージョンから先のことは思い出せません。

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

@(-|)は「- or nothing」、[0-9]は「数字」、*([0-9])は「ゼロ以上の数字」を意味します。

9
ephemient

基本的には1行なので、-eqテストを使用したソリューションが気に入っています。

私自身の解決策は、パラメーター展開を使用してすべての数字を破棄し、何か残っているかどうかを確認することでした。 (私はまだ3.0を使用しており、以前に[[またはexprを使用したことはありませんが、それらに会えてうれしいです。)

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

さらにもう1つ考えてみましょう(組み込みコマンドtestとその戻りコードのみを使用):

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
3
hans

数字以外を削除して比較することができます。デモスクリプトは次のとおりです。

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

テスト出力は次のようになります。

 44 44整数
-44 44整数
 44- 44整数ではない
 4-4 44整数ではない
 a4 4整数ではない
 4a 4非整数
。4 4非整数
 4.4 44非整数
-4.4 44非整数
 09 9非整数
2

または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
1
knipwim

笑いのために、私はこれを行うための一連の関数(is_string、is_int、is_float、is alpha文字列、またはその他)を大まかにすばやく計算しましたが、これを行うためのより効率的な(コードが少ない)方法があります:

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

ここでいくつかのテストを実行し、-44はintですが、44-はそうではないことを定義しました。

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

出力:

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 = 

注:0の先頭は8進数などの数値を追加するときに他の何かを推測する可能性があるため、'09 'をint(私がやっていること)として扱う場合は削除する方が良いでしょう(例:expr 09 + 0またはsed)

0
Mike Q

Ignacio Vazquez-Abramsからの回答に追加します。これにより、整数の前に+記号を付けることができ、任意の数のゼロを小数点として使用できます。たとえば、これにより+45.00000000を整数と見なすことができます。
ただし、$ 1は小数点を含むようにフォーマットする必要があります。ここでは45は整数と見なされませんが、45.0は整数と見なされます。

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
0
JustinMT