Kiểm tra xem chuỗi có phải là số nguyên hợp lệ không


117

Tôi đang cố gắng làm điều gì đó đủ phổ biến: Phân tích cú pháp đầu vào của người dùng trong một tập lệnh shell. Nếu người dùng cung cấp một số nguyên hợp lệ, tập lệnh sẽ thực hiện một việc, và nếu không hợp lệ, nó thực hiện một việc khác. Rắc rối là, tôi chưa tìm ra một cách dễ dàng (và hợp lý) để làm điều này - tôi không muốn phải tách nó ra từng char.

Tôi biết điều này phải dễ dàng nhưng tôi không biết làm thế nào. Tôi có thể làm điều đó bằng hàng chục ngôn ngữ, nhưng không phải BASH!

Trong nghiên cứu của mình, tôi tìm thấy điều này:

Biểu thức chính quy để kiểm tra xem một chuỗi có chứa một số thực hợp lệ trong cơ số 10 hay không

Và có một câu trả lời trong đó nói về regex, nhưng theo tôi biết, đó là một hàm có sẵn trong C (trong số những hàm khác). Tuy nhiên, nó có một câu trả lời tuyệt vời nên tôi đã thử nó với grep, nhưng grep không biết phải làm gì với nó. Tôi đã thử -P mà trên hộp của tôi có nghĩa là coi nó như một PERL regexp - nada. Dash E (-E) cũng không hoạt động. Và cả -F cũng vậy.

Nói rõ hơn, tôi đang thử một thứ như thế này, tìm kiếm bất kỳ đầu ra nào - từ đó, tôi sẽ hack kịch bản để tận dụng bất cứ thứ gì tôi có được. (IOW, tôi đã mong đợi rằng đầu vào không phù hợp sẽ không trả lại gì trong khi một dòng hợp lệ được lặp lại.)

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

Ai đó vui lòng minh họa cách thực hiện điều này dễ dàng nhất?

Thành thật mà nói, đây là một thử nghiệm ngắn hạn, theo ý kiến ​​của tôi. Nó phải có một lá cờ như thế này

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

4
FYI: [tương thích cũ test; [[là điều mới của Bash, với nhiều thao tác hơn và các quy tắc trích dẫn khác nhau. Nếu bạn đã quyết định gắn bó với Bash, hãy tiếp tục [[(nó thực sự đẹp hơn nhiều); nếu bạn cần khả năng chuyển sang các shell khác, hãy tránh [[hoàn toàn.
ephemient

Câu trả lời:


183
[[ $var =~ ^-?[0-9]+$ ]]
  • Các ^ chỉ sự bắt đầu của mẫu đầu vào
  • Các - một chữ "-"
  • ?nghĩa là "0 hoặc 1 trong số trước đó (- )"
  • +nghĩa là "1 hoặc nhiều từ trước ([0-9] )"
  • Dấu $chỉ sự kết thúc của mẫu đầu vào

Vì vậy, regex khớp với một tùy chọn - (đối với trường hợp số âm), theo sau là một hoặc nhiều chữ số thập phân.

Tài liệu tham khảo :


3
Cảm ơn Ignacio, tôi sẽ thử trong giây lát. Bạn có vui lòng giải thích nó để tôi có thể tìm hiểu một chút không? Tôi tập hợp nó đọc, "Ở đầu chuỗi (^), dấu trừ (-) là tùy chọn (?), Theo sau là bất kỳ số ký tự nào từ 0 đến 9, bao gồm" ... và sau đó dấu + $ nghĩa là gì? Cảm ơn.
Richard T

10
+nghĩa là "1 hoặc nhiều từ trước", và $dấu chỉ phần cuối của mẫu nhập. Vì vậy, regex khớp với một tùy chọn -theo sau bởi một hoặc nhiều chữ số thập phân.
Ignacio Vazquez-Abrams

càu nhàu lại: liên kết ABS
Charles Duffy

Đó là một tiếp tuyến, nhưng lưu ý rằng khi chỉ định phạm vi ký tự, bạn có thể nhận được kết quả kỳ lạ; ví dụ, [A-z]sẽ không chỉ cung cấp cho bạn A-Za-zmà còn \ , [, ], ^, _, và `.
Doktor J,

Ngoài ra, dựa trên đối chiếu ký tự ( xem câu hỏi / câu trả lời liên quan này ) một cái gì đó giống như d[g-i]{2}có thể không chỉ khớp digmà còn dishở đối chiếu được đề xuất bởi câu trả lời đó (trong đó biểu shđồ được coi là một ký tự duy nhất, được đối chiếu sau h).
Doktor J

61

Wow ... có rất nhiều giải pháp tốt ở đây !! Trong tất cả các giải pháp ở trên, tôi đồng ý với @nortally rằng sử dụng -eqmột lớp lót là tuyệt vời nhất.

Tôi đang chạy GNU bash, phiên bản 4.1.5 (Debian). Tôi cũng đã kiểm tra điều này trên ksh (SunSO 5.10).

Đây là phiên bản của tôi để kiểm tra xem $1có phải là số nguyên hay không:

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

Cách tiếp cận này cũng tính đến số âm, mà một số giải pháp khác sẽ có kết quả âm bị lỗi và nó sẽ cho phép tiền tố "+" (ví dụ +30) rõ ràng là một số nguyên.

Các kết quả:

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

Giải pháp được cung cấp bởi Ignacio Vazquez-Abrams cũng rất gọn gàng (nếu bạn thích regex) sau khi nó được giải thích. Tuy nhiên, nó không xử lý các số dương với+ tiền tố, nhưng nó có thể dễ dàng được sửa như sau:

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

Đẹp! Tuy nhiên, khá giống với điều này .
devnull

Đúng. Nó tương tự. Tuy nhiên, tôi đang tìm kiếm một giải pháp lót cho câu lệnh "if". Tôi nghĩ rằng tôi không thực sự cần gọi một hàm cho việc này. Ngoài ra, tôi có thể thấy rằng sự chuyển hướng của stderr thành stdout trong hàm. Khi tôi thử, thông báo stderr "mong đợi biểu thức số nguyên" được hiển thị mà tôi không mong muốn.
Peter Ho

Cảm ơn bạn! Tôi sẽ gọi điều này là dễ dàng và thanh lịch.
Ezra Nugroho

2
Có một sự khác biệt đáng chú ý giữa giải pháp của bạn và giải pháp regex: kích thước của số nguyên được kiểm tra theo giới hạn bash (trên máy tính của tôi là 64bits). Giới hạn này không đạt đến giải pháp regexp. Vì vậy, giải pháp của bạn sẽ không thành công với số lớn hơn 9223372036854775807 trên máy tính 64bits.
vaab

2
Như tôi đã khám phá gần đây, có một số lưu ý .
Kyle Strand,

28

Người đến sau bữa tiệc ở đây. Tôi cực kỳ ngạc nhiên khi không có câu trả lời nào đề cập đến giải pháp đơn giản nhất, nhanh nhất, di động nhất; các casetuyên bố.

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

Việc cắt bớt bất kỳ dấu hiệu nào trước khi so sánh có vẻ hơi phức tạp, nhưng điều đó làm cho biểu thức cho câu lệnh trường hợp trở nên đơn giản hơn rất nhiều.


4
Tôi ước gì tôi có thể ủng hộ điều này một lần mỗi khi tôi quay lại câu hỏi này vì lỗi lừa đảo. Nó nghiền nát các bánh răng của tôi rằng một giải pháp đơn giản nhưng tuân thủ POSIX được chôn sâu dưới đáy.
Adrian Frühwirth

3
Có lẽ bạn nên chăm sóc chuỗi rỗng:''|*[!0-9]*)
Niklas Peter

2
BTW: Đây là cú pháp này được ghi lại: tldp.org/LDP/abs/html/string-manipulation.html
Niklas Peter

Tôi không đặc biệt dung túng ABS; điều này rõ ràng cũng được ghi lại trong sách hướng dẫn Bash. Dù sao, phần bạn liên kết đến không mô tả cấu trúc cụ thể này, mà là câu trả lời của @ Nortally.
tripleee

@tripleee Tài liệu được liên kết mô tả cấu trúc để xóa tiền tố chuỗi khỏi một biến được sử dụng trong dòng chữ hoa chữ thường. Nó nằm ở cuối trang, nhưng không có neo, vì vậy tôi không thể liên kết trực tiếp đến nó, hãy xem phần "Loại bỏ chuỗi con"
Niklas Peter

10

Tôi thích giải pháp bằng cách sử dụng -eqthử nghiệm, vì về cơ bản nó là một lớp lót.

Giải pháp của riêng tôi là sử dụng mở rộng tham số để loại bỏ tất cả các chữ số và xem liệu còn lại gì không. (Tôi vẫn đang sử dụng 3.0, chưa từng sử dụng [[hoặc exprtrước đây, nhưng rất vui khi gặp chúng.)

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

4
Điều này có thể được cải thiện hơn nữa bằng cách sử dụng [ -z "${INPUT_STRING//[0-9]}" ]nhưng giải pháp thực sự tốt đẹp!
ShellFish

còn dấu hiệu tiêu cực thì sao?
scottysseus

Các -eqgiải pháp có một số vấn đề; xem tại đây: stackoverflow.com/a/808740/1858225
Kyle Strand

INPUT_STRING trống được coi là số, vì vậy không thành công đối với trường hợp của tôi
Manwe

9

Để có thể chuyển sang pre-Bash 3.1 (khi =~thử nghiệm được giới thiệu), hãy sử dụng 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 : REGEXtìm kiếm REGEX được cố định ở đầu STRING, lặp lại nhóm đầu tiên (hoặc độ dài của đối sánh, nếu không có) và trả về thành công / thất bại. Đây là cú pháp regex cũ, do đó thừa \. -\?có nghĩa là "có thể -", [0-9]\+có nghĩa là "một hoặc nhiều chữ số", và $có nghĩa là "cuối chuỗi".

Bash cũng hỗ trợ các địa cầu mở rộng, mặc dù tôi không nhớ từ phiên bản nào trở đi.

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

@(-|)có nghĩa là " -hoặc không có gì", [0-9]có nghĩa là "chữ số", và *([0-9])có nghĩa là "không hoặc nhiều chữ số".


Cảm ơn bạn phù du, rất có nghĩa vụ. Tôi chưa bao giờ thấy cú pháp = ~ trước đây - và vẫn không biết nó có nghĩa là gì - xấp xỉ bằng nhau ?! ... Tôi chưa bao giờ hào hứng với việc lập trình bằng BASH nhưng đôi khi điều đó cần thiết!
Richard T

Trong awk, ~là toán tử "kết hợp regex". Trong Perl (như được sao chép từ C), ~đã được sử dụng cho "phần bổ sung bit", vì vậy họ đã sử dụng =~. Ký hiệu sau này đã được sao chép sang một số ngôn ngữ khác. (Perl 5,10 và Perl 6 giống như ~~nhiều hơn, nhưng điều đó không có tác động ở đây.) Tôi cho rằng bạn có thể xem nó như một loại bình đẳng gần đúng nào đó ...
ephemient

Bài xuất sắc VÀ chỉnh sửa! Tôi thực sự đánh giá cao việc giải thích ý nghĩa của nó. Tôi ước tôi có thể đánh dấu cả bài viết của bạn và của Ignacio là câu trả lời đúng. -frown- Hai người đều tuyệt vời. Nhưng khi bạn có gấp đôi danh tiếng của anh ấy, tôi sẽ trao nó cho Ignacio - hy vọng bạn hiểu! -smile-
Richard T

4

Đây là một cách khác về nó (chỉ sử dụng lệnh nội trang thử nghiệm và mã trả lại của nó):

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
Nó không cần thiết để sử dụng $()với if. Đây hoạt động: if is_int "$input". Ngoài ra, $[]biểu mẫu không được dùng nữa. Sử dụng $(())thay thế. Bên trong, ký hiệu đô la có thể được bỏ qua: Dấu echo "Integer: $((input))"ngoặc nhọn không cần thiết ở bất kỳ đâu trong tập lệnh của bạn.
Tạm dừng cho đến khi có thông báo mới.

Tôi đã mong đợi điều này cũng xử lý các số trong ký hiệu cơ sở của Bash dưới dạng số nguyên hợp lệ (tất nhiên theo một số định nghĩa chúng là; nhưng nó có thể không đồng ý với của bạn) nhưng testdường như không hỗ trợ điều này. [[mặc dù vậy. [[ 16#aa -eq 16#aa ]] && echo integerin ra "số nguyên".
tripleee

Lưu ý rằng [[trả về kết quả dương tính sai cho phương thức này; ví dụ: [[ f -eq f ]]thành công. Vì vậy, nó phải sử dụng testhoặc [.
spinup

3

Bạn có thể tách các chữ số không và thực hiện so sánh. Đây là một kịch bản 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

Đây là kết quả thử nghiệm trông như thế nào:

44 44 Số nguyên
-44 44 Số nguyên
44- 44 Không phải số nguyên
4-4 44 Không phải số nguyên
a4 4 Không phải số nguyên
4a 4 Không phải số nguyên
.4 4 Không phải số nguyên
4.4 44 Không phải số nguyên
-4,4 44 Không phải số nguyên
09 9 Không phải số nguyên

Chào Dennis, Cảm ơn bạn đã giới thiệu cho tôi cú pháp ở bên phải của match = ở trên. Tôi chưa bao giờ nhận thấy cú pháp kiểu đó trước đây. Tôi nhận ra một số cú pháp từ tr (một tiện ích mà tôi chưa hoàn toàn thành thạo, nhưng thỉnh thoảng vẫn mò mẫm tìm hiểu); Tôi có thể đọc cú pháp như vậy ở đâu? (tức là, loại thứ này được gọi là gì?) Cảm ơn.
Richard T

Bạn có thể xem trong trang Bash man trong phần có tên "Mở rộng tham số" để biết thông tin về ${var//string}${var#string}và trong phần có tên "So khớp mẫu" cho [^ [: digit:]] `(cũng được đề cập trong man 7 regex).
Tạm dừng cho đến khi có thông báo mới.

1
match=${match#0*}không không dải zero hàng đầu, nó tước đoạt nhiều nhất là một số không. Sử dụng mở rộng điều này chỉ có thể đạt được bằng cách sử dụng extglobvia match=${match##+(0)}.
Adrian Frühwirth

Không phải 9 hoặc 09 là một số nguyên?
Mike Q

@MikeQ: 09không phải là số nguyên nếu bạn coi một số nguyên không có số 0 ở đầu. Kiểm tra là liệu đầu vào ( 09) có bằng một phiên bản được làm sạch ( 9- một số nguyên) hay không.
Tạm dừng cho đến khi có thông báo mới.

2

Đối với tôi, giải pháp đơn giản nhất là sử dụng biến bên trong một (())biểu thức, như vậy:

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

Tất nhiên, giải pháp này chỉ hợp lệ nếu giá trị bằng 0 không phù hợp với ứng dụng của bạn. Điều đó đã xảy ra đúng trong trường hợp của tôi, và điều này đơn giản hơn nhiều so với các giải pháp khác.

Như đã chỉ ra trong các nhận xét, điều này có thể khiến bạn bị tấn công thực thi mã: Người (( ))điều hành đánh giá VAR, như đã nêu trong Arithmetic Evaluationphần của trang người đàn ông bash (1) . Do đó, bạn không nên sử dụng kỹ thuật này khi nguồn của nội dung VARkhông chắc chắn (tất nhiên bạn cũng không nên sử dụng BẤT KỲ dạng mở rộng biến nào khác).


Bạn thậm chí có thể đi đơn giản hơn vớiif (( var )); then echo "$var is an int."; fi
Aaron R.

2
Nhưng điều đó cũng sẽ trả về đúng cho số nguyên âm, @aaronr, không phải những gì OP đang tìm kiếm.
Trebor Rude

2
Điều này là nguy hiểm, xem: n = 1; var = "n"; if ((var)); thì echo "$ var là một int."; fi
jarno

2
Đây là một ý tưởng rất xấu và chịu sự thực thi mã tùy ý: thử nó cho mình: VAR='a[$(ls)]'; if ((VAR > 0)); then echo "$VAR is a positive integer"; fi. Tại thời điểm này, bạn rất vui vì tôi đã không nhập một số lệnh xấu thay vì ls. Bởi vì OP đề cập đến đầu vào của người dùng , tôi thực sự hy vọng bạn không sử dụng điều này với đầu vào của người dùng trong mã sản xuất!
gniourf_gniourf

Điều này không làm việc nếu chuỗi có chứa một số chữ số như:agent007
brablc

1

hoặc với 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

Trong Bash và một số khác "Bourne cộng với" vỏ bạn có thể tránh sự thay thế lệnh và lệnh bên ngoài với test -z "${string//[0-9]/}" && echo "integer" || echo "no integer"... dù rằng về cơ bản trùng lặp Dennis Williamson của câu trả lời
tripleee

Cảm ơn! Câu trả lời duy nhất thực sự hoạt động ở đây!
người dùng

Thay thế im lặng:if [[ -n "$(printf "%s" "${2}" | sed s/[0-9]//g)" ]]; then
người dùng

0

Thêm vào câu trả lời từ Ignacio Vazquez-Abrams. Điều này sẽ cho phép dấu + đứng trước số nguyên và nó sẽ cho phép bất kỳ số không nào dưới dạng dấu thập phân. Ví dụ: điều này sẽ cho phép +45.00000000 được coi là một số nguyên.
Tuy nhiên, $ 1 phải được định dạng để chứa dấu thập phân. 45 không được coi là một số nguyên ở đây, nhưng 45.0 là.

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

Có lý do gì bạn sử dụng hai biểu thức chính quy khác nhau cho số dương và số âm, thay vì ^[-+]?[0-9]...?
tripleee

0

Để cười, tôi đại khái chỉ nhanh chóng tìm ra một tập hợp các hàm để làm điều này (is_string, is_int, is_float, là chuỗi alpha, hoặc các cách khác) nhưng có nhiều cách hiệu quả hơn (ít mã hơn) để thực hiện điều này:

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

Chạy qua một số thử nghiệm ở đây, tôi đã xác định rằng -44 là một int nhưng 44- không phải là vv ..:

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

Đầu ra:

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 = 

LƯU Ý: Các số 0 đứng đầu có thể suy ra điều gì đó khác khi thêm các số chẳng hạn như bát phân, vì vậy tốt hơn là loại bỏ chúng nếu bạn có ý định coi '09' là số nguyên (mà tôi đang làm) (ví dụ: expr 09 + 0hoặc dải bằng sed)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.