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


117

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

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

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

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

そして、そこには正規表現についての答えがありますが、私の知る限り、それはCで利用可能な関数です(とりわけ)。それでも、すばらしい答えのように見えたので、grepで試してみましたが、grepはそれをどうするかわかりませんでした。私の箱では、それをPERL正規表現として扱うことを意味する-Pを試しました-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

4
参考:[旧互換testです。[[より多くの操作とさまざまな引用ルールを備えたBashの新しいものです。すでにBashを使用することに決めている場合は、それを実行してください[[(これは非常に優れています)。他のシェルへの移植性が必要な場合は、[[完全に避けてください。
ephemient

回答:


183
[[ $var =~ ^-?[0-9]+$ ]]
  • ^入力パターンの始まりを示し、
  • これ-は文字通りの「-」です
  • ?「前の0または1(-)」の意味
  • +手段「直前の1以上([0-9])」
  • $入力パターンの終了を示します

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

参照


3
イグナシオ、ありがとうございます。すぐに試してみます。少し学べるように説明していただけませんか?「文字列(^)の先頭では、マイナス記号(-)は省略可能(?)で、その後に0から9までの任意の数の文字が続きます」と読み、それから+ $意味ですか?ありがとう。
リチャードT

10
+手段「直前の1又はそれ以上」、及び$入力パターンの終了を示します。したがって、正規表現は、オプションの-後に1つ以上の10進数字が続くものと一致します。
Ignacio Vazquez-Abrams

不平の再:ABSリンク
チャールズ・ダフィー

これは正接ですが、文字範囲を指定すると奇妙な結果が得られることに注意してください。たとえば、[A-z]あなたを与えるだろうばかりA-Za-z\ []^_、と`
Doktor J

さらに、文字の照合に基づいて(この関連する質問/回答を参照)、d[g-i]{2}一致するだけでなく、その回答によって提案された照合(ダイグラフが単一の文字と見なされ、後に照合される)にdigも結果が出る可能性があります。dishshh
Doktor J

61

うわー...ここには良い解決策がたくさんあります!! 上記のすべての解決策の中で、-eq1つのライナーを使用するのが最もかっこいいと、@ 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]+$ ]]

いいね!ただし、これとかなり似ています。
devnull 2013年

はい。似ています。ただし、「if」ステートメントのワンライナーソリューションを探していました。このために関数を呼び出す必要は本当にないと思いました。また、関数内でstderrがstdoutにリダイレクトされることがわかります。試したところ、「整数式が必要です」というstderrメッセージが表示されました。
Peter Ho

ありがとうございました!これは簡単でエレガントだと思います。
Ezra Nugroho 2015年

2
あなたのソリューションと正規表現のものの間には顕著な違いがあります:整数のサイズはbashの制限に向かってチェックされます(私のコンピューターでは64ビットです)。この制限は、正規表現ソリューションには影響しません。したがって、64ビットコンピュータでは9223372036854775807よりも厳密に大きい数でソリューションが失敗します。
vaab 2015年

2
最近発見したように、いくつかの注意点があります。
カイルストランド

28

ここのパーティーに遅刻。最も単純で、最も速く、最もポータブルなソリューションについては、どの回答にも言及していません。case声明。

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

比較前の符号のトリミングは少しハックのように感じられますが、それにより、ケースステートメントの式が非常に単純になります。


4
だまされたため、この質問に戻るたびに、これに1度賛成したいと思います。シンプルでありながらPOSIXに準拠したソリューションが底に埋め込まれていることは、私の機材を傷つけます。
エイドリアンFrühwirth2014

3
空の文字列を処理する必要があるかもしれません:''|*[!0-9]*)
Niklas Peter

2
ところで、この構文は次のとおりです:tldp.org/LDP/abs/html/string-manipulation.html
Niklas Peter

ABSは特に容赦しません。これは明らかにBashのマニュアルにも記載されています。とにかく、あなたがリンクしたセクションはこの特定の構成を説明しているのではなく、例えば@Nortallyの答えです。
Tripleee、2015

@tripleeeリンクされたドキュメントは、ケース行で使用される変数から文字列の接頭辞を削除するための構成を説明しています。それはページの一番下にありますが、アンカーがないため、直接リンクできませんでした。「サブストリングの削除」セクションを参照してください
Niklas Peter

10

-eq基本的にワンライナーなので、テストを使用したソリューションが好きです。

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

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

4
これはさらに改善できます[ -z "${INPUT_STRING//[0-9]}" ]が、本当に素晴らしい解決策です!
ShellFish 2015

否定的な兆候はどうですか?
scottysseus

-eq解決策はいくつかの問題があります。こちらをご覧ください:stackoverflow.com/a/808740/1858225
カイルストランド

空のINPUT_STRINGは数値と見なされるため、私の場合は失敗します
Manwe

9

=~テストが導入されたとき)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 : REGEXSTRINGの先頭にアンカーされた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])) ]]

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


どうぞよろしくお願いします。これまで==構文を見たことがありませんでしたが、それが何を意味するのかまだわかりません-ほぼ同じですか?...私はBASHでプログラミングすることに興奮したことがありませんが、それときどき必要です!
リチャードT

ではawk~「正規表現一致」演算子でした。Perlでは(Cからコピーしたとおり)、~「ビット補完」にすでに使用されていたため、を使用しました=~。この後の表記は他のいくつかの言語にコピーされました。(5.10とPerl 6のような~~より多くの、それがここには影響しません。)私は...あなたはおおよその平等のいくつかの並べ替えとしてそれを見ることができると仮定
ephemient

素晴らしい投稿と編集!それが何を意味するのか説明してくれて本当にありがとう 私はあなたとイグナシオの両方の投稿を正しい答えとしてマークできるといいのですが。-しかめっ面-君たちは二人とも素晴らしい。しかし、あなたが彼の2倍の評判を持っているので、私はそれをイグナシオに与えます-ご理解いただければ幸いです!-smile-
リチャードT

4

次に、もう1つ別の例を示します(組み込みのテストコマンドとその戻りコードのみを使用)。

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
で使用$()する必要はありませんif。これは機能しますif is_int "$input"。また、$[]フォームは非推奨です。$(())代わりに使用してください。どちらの場合も、ドル記号は省略できecho "Integer: $((input))"ます。スクリプトでは、中括弧は必要ありません。
追って通知があるまで一時停止。

私はこれがBashの基本表記の数値を有効な整数として処理することも期待していましたが(当然、定義によってはそうですが、実際の整数とは一致しない場合があります)、testこれをサポートしていないようです。 [[しかし、そうです。 [[ 16#aa -eq 16#aa ]] && echo integer「整数」を出力します。
Tripleee 2017年

[[このメソッドの誤検知を返すことに注意してください。例えば[[ f -eq f ]]成功します。したがって、testまたはを使用する必要があります[
スピンアップ

3

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

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整数ではない

こんにちはデニス、上記のmatch =の右側の構文を紹介していただきありがとうございます。これまでにその型構文に気づいたことはありません。私はtrの構文の一部を認識しています(私は完全に習得していないユーティリティですが、時々自分のやり方を調べます)。そのような構文はどこで読むことができますか?(つまり、このタイプのものは何と呼ばれますか?)ありがとう。
リチャードT

詳細については${var//string}、「パラメータ拡張」と呼ばれるセクションのBashマニュアルページと、${var#string}[^ [:digit:]] `の「パターンマッチング」と呼ばれるセクションを参照してください(これについてもで説明していますman 7 regex)。
追って通知があるまで一時停止。

1
match=${match#0*}先行ゼロを取り除きませ。最大で1つのゼロを取り除きます。拡張を使用すると、これはextglobvia を使用してのみ実現できmatch=${match##+(0)}ます。
エイドリアンFrühwirth2014

9または09は整数ではありませんか?
マイクQ

@MikeQ:09整数に先行ゼロがないと考える場合、整数ではありません。テストは、入力(09)がサニタイズされたバージョン(9-整数)と等しいかどうかであり、等しくないかどうかです。
追って通知があるまで一時停止。

2

私にとって、最も簡単な解決策は、(())式の中で変数を使用することでした。

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

もちろん、このソリューションは、ゼロの値がアプリケーションにとって意味をなさない場合にのみ有効です。それはたまたま私の場合は真実であり、これは他のソリューションよりもはるかに簡単です。

コメントで指摘されているように、これはコード実行攻撃の対象となる可能性があります。bash(1)のマニュアルページのセクションで説明されているように、(( ))演算子はを評価します。したがって、コンテンツのソースが不明な場合は、この手法を使用しないでください(もちろん、他の形式の変数展開を使用しないでください)。VARArithmetic EvaluationVAR


さらに簡単にできますif (( var )); then echo "$var is an int."; fi
アーロンR.

2
しかし、それはOPが探していたものではなく、負の整数@aaronrに対してもtrueを返します。
Trebor Rude 14

2
これは危険です。n= 1を参照してください。var = "n"; if((var)); 次に、「$ var is int。」をエコーし​​ます。fi
jarno、2015年

2
これは非常に悪い考えであり、任意のコードが実行される可能性がありますVAR='a[$(ls)]'; if ((VAR > 0)); then echo "$VAR is a positive integer"; fi。自分で試してください。この時点で、の代わりに邪悪なコマンドを入力しなかったことをうれしく思いますls。OPはユーザー入力について言及しているので、本番用コードでこれをユーザー入力とともに使用しないでください。
gniourf_gniourf 2015年

:文字列のようないくつかの数字が含まれている場合これは仕事をしませんagent007
brablc

1

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

バッシュと他のいくつかの「ボーンプラス」シェルでは、コマンド置換と外部コマンドを回避できますtest -z "${string//[0-9]/}" && echo "integer" || echo "no integer"。ただし、基本的にはデニスウィリアムソンの回答と
同じです

ありがとう!ここで実際に動作する唯一の答え!
ユーザー

サイレント代替:if [[ -n "$(printf "%s" "${2}" | sed s/[0-9]//g)" ]]; then
ユーザー

0

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-9]...の代わりに、正の数と負の数に2つの異なる正規表現を使用する理由はありますか?
tripleee 2017

0

笑うために、これを行うための関数のセット(is_string、is_int、is_float、is alpha stringなど)を大まかにすばやく作成しましたが、これを行うにはより効率的な(コードが少ない)方法があります。

#!/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で削除する)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.