ฉันจะทดสอบว่าตัวแปรเป็นตัวเลขใน Bash ได้อย่างไร


599

ฉันไม่สามารถทราบได้ว่าฉันจะแน่ใจได้อย่างไรว่าข้อโต้แย้งที่ส่งผ่านไปยังสคริปต์ของฉันนั้นเป็นตัวเลขหรือไม่

สิ่งที่ฉันอยากทำคือสิ่งนี้:

test *isnumber* $1 && VAR=$1 || echo "need a number"

ความช่วยเหลือใด ๆ


17
เช่นกัน - มีtest && echo "foo" && exit 0 || echo "bar" && exit 1วิธีการที่คุณใช้อาจมีผลข้างเคียงบางอย่างที่ไม่ได้ตั้งใจ - ถ้าสะท้อนล้มเหลว (อาจจะส่งออกคือการปิด FD) ที่จะถูกข้ามไปและรหัสแล้วจะพยายามที่จะexit 0 echo "bar"ถ้ามันล้มเหลวเช่นกัน&&สภาพก็จะล้มเหลวและมันก็จะไม่ทำงานexit 1! การใช้ifข้อความจริงมากกว่า&&/ ||มีแนวโน้มน้อยที่จะเกิดผลข้างเคียงที่ไม่คาดคิด
ชาร์ลส์ดัฟฟี่

@CharlesDuffy นั่นเป็นความคิดที่ฉลาดจริง ๆ ที่คนส่วนใหญ่เข้ามาเมื่อพวกเขาต้องติดตามบั๊กขนดก ... ฉันไม่เคยคิดว่าเสียงสะท้อนจะกลับมาล้มเหลว
Camilo Martin

6
สายไปงานเลี้ยง แต่ฉันรู้เกี่ยวกับอันตรายที่ชาร์ลส์เขียนเกี่ยวกับเพราะฉันต้องผ่านพวกเขาค่อนข้างบางเวลาที่ผ่านมาเช่นกัน ดังนั้นนี่คือบรรทัด 100% (และอ่านได้ง่าย) สำหรับคุณ: [[ $1 =~ "^[0-9]+$" ]] && { echo "number"; exit 0; } || { echo "not a number"; exit 1; }วงเล็บปีกการะบุว่าสิ่งที่ไม่ควรดำเนินการใน subshell (ซึ่งแน่นอนว่าจะเป็นวิธีที่()ใช้วงเล็บแทน) ข้อแม้: ไม่พลาดเซมิโคลอน มิฉะนั้นคุณอาจทำให้bashพิมพ์ข้อความแสดงข้อผิดพลาดที่น่าเกลียดที่สุด (และไม่มีจุดหมายมากที่สุด) ...
syntaxerror

5
มันไม่ทำงานใน Ubuntu เว้นแต่คุณจะไม่ลบคำพูดออก ดังนั้นมันควรจะเป็น[[ 12345 =~ ^[0-9]+$ ]] && echo OKKK || echo NOOO
Treviño

4
คุณจะต้องเฉพาะเจาะจงมากขึ้นเกี่ยวกับสิ่งที่คุณหมายถึง"จำนวน" จำนวนเต็มหรือไม่ หมายเลขจุดคงที่หรือไม่ เครื่องหมายทางวิทยาศาสตร์ ("e")? มีช่วงที่ต้องการ (เช่นค่าที่ไม่ได้ลงชื่อ 64 บิต) หรือคุณอนุญาตให้มีหมายเลขที่สามารถเขียนได้หรือไม่?
Toby Speight

คำตอบ:


803

วิธีหนึ่งคือการใช้นิพจน์ทั่วไปเช่น:

re='^[0-9]+$'
if ! [[ $yournumber =~ $re ]] ; then
   echo "error: Not a number" >&2; exit 1
fi

หากค่าไม่จำเป็นต้องเป็นจำนวนเต็มให้พิจารณาแก้ไข regex อย่างเหมาะสม เช่น:

^[0-9]+([.][0-9]+)?$

... หรือเพื่อจัดการตัวเลขที่มีสัญลักษณ์:

^[+-]?[0-9]+([.][0-9]+)?$

7
+1 สำหรับวิธีการนี้ แต่ระวังทศนิยมทำแบบทดสอบนี้ด้วยตัวอย่างเช่นข้อผิดพลาด "1.0" หรือ "1,0" พิมพ์ ": ไม่ใช่ตัวเลข"
sourcerebels

15
ฉันค้นหา '' exec> & 2; echo ... '' ค่อนข้างงี่เง่า เพียง '' echo ... > & 2 ''
lhunath

5
@Ben คุณต้องการจัดการมากกว่าหนึ่งเครื่องหมายลบหรือไม่ ฉันจะทำมันให้^-?ดีกว่า^-*ว่าคุณจะทำงานให้จัดการกับผู้รุกรานหลายคนได้อย่างถูกต้อง
Charles Duffy

4
@SandraSchlichting ทำให้ผลลัพธ์ในอนาคตทั้งหมดไปที่ stderr ไม่ใช่จุดที่นี่จริง ๆ ซึ่งมีเพียงเสียงสะท้อนเดียว แต่เป็นนิสัยที่ฉันมักจะได้รับในกรณีที่ข้อความแสดงข้อผิดพลาดครอบคลุมหลายบรรทัด
Charles Duffy

29
ฉันไม่แน่ใจว่าทำไมการแสดงออกปกติจะต้องถูกบันทึกไว้ในตัวแปร แต่ถ้าเพื่อความเข้ากันได้ฉันไม่คิดว่ามันจำเป็น [[ $yournumber =~ ^[0-9]+$ ]]คุณก็สามารถใช้การแสดงออกโดยตรง:
konsolebox

284

ไม่มี bashisms (ทำงานได้แม้ใน System V sh)

case $string in
    ''|*[!0-9]*) echo bad ;;
    *) echo good ;;
esac

สิ่งนี้จะปฏิเสธสตริงว่างและสตริงที่ไม่มีตัวเลขซึ่งยอมรับทุกอย่าง

ตัวเลขติดลบหรือเลขทศนิยมต้องมีการทำงานเพิ่มเติม แนวคิดหนึ่งคือการแยก-/ .ในรูปแบบ "ไม่ดี" แรกและเพิ่มรูปแบบ "ไม่ดี" เพิ่มเติมซึ่งมีการใช้งานที่ไม่เหมาะสม ( ?*-*/*.*.* )


20
+1 - นี่เป็นวิธีที่พกพากลับไปยังเชลล์เป้าหมายเดิมได้และมีการรองรับสัญลักษณ์ตัวแทนแบบกลมในตัว ถ้าคุณมาจากภาษาการเขียนโปรแกรมอื่นมันดูน่าขนลุก แต่มันก็ดูดีกว่าการรับมือกับความเปราะบางของปัญหาการพูดและย้อนหลัง / ปัญหาความเข้ากันไม่สิ้นสุดกับด้านข้างif test ...
Tripleee

6
คุณสามารถเปลี่ยนบรรทัดแรกเป็น${string#-}(ซึ่งไม่ทำงานในเชลล์ Bourne โบราณ แต่ทำงานในเชลล์ POSIX ใด ๆ ) เพื่อยอมรับจำนวนเต็มลบ
Gilles 'หยุดความชั่วร้าย'

4
นอกจากนี้ยังขยายได้ง่ายถึงการลอยเพียงเพิ่ม'.' | *.*.*ในรูปแบบที่ไม่อนุญาตและเพิ่มจุดลงในอักขระที่อนุญาต ในทำนองเดียวกันคุณสามารถอนุญาตให้ลงชื่อเข้าใช้เสริมก่อนหน้านี้ แต่ฉันก็อยากcase ${string#[-+]}จะเพิกเฉยต่อสัญญาณนั้น
tripleee

ดูสิ่งนี้สำหรับการจัดการจำนวนเต็มที่ลงนามแล้ว: stackoverflow.com/a/18620446/2013911
Niklas Peter

2
@Dor ไม่จำเป็นต้องใส่เครื่องหมายคำพูดเนื่องจากคำสั่ง case จะไม่ทำการแยกคำและสร้างชื่อพา ธ ในคำนั้น (อย่างไรก็ตามการขยายในรูปแบบตัวพิมพ์อาจจำเป็นต้องมีการอ้างอิงเนื่องจากเป็นตัวกำหนดว่าอักขระที่ตรงกับรูปแบบนั้นเป็นตัวอักษรหรือพิเศษ)
jilles

193

โซลูชันต่อไปนี้สามารถใช้ในเชลล์พื้นฐานเช่น Bourne โดยไม่จำเป็นต้องใช้นิพจน์ทั่วไป โดยทั่วไปการดำเนินการประเมินค่าตัวเลขโดยใช้ที่ไม่ใช่ตัวเลขจะส่งผลให้เกิดข้อผิดพลาดซึ่งจะถือว่าเป็นเท็จในเชลล์โดยปริยาย:

"$var" -eq "$var"

ในขณะที่:

#!/bin/bash

var=a

if [ -n "$var" ] && [ "$var" -eq "$var" ] 2>/dev/null; then
  echo number
else
  echo not a number
fi

คุณสามารถทดสอบราคา $ ได้ไหม โค้ดส่งคืนของการดำเนินการที่ชัดเจนยิ่งขึ้น:

[ -n "$var" ] && [ "$var" -eq "$var" ] 2>/dev/null
if [ $? -ne 0 ]; then
   echo $var is not number
fi

การเปลี่ยนเส้นทางของข้อผิดพลาดมาตรฐานคือมีการซ่อนข้อความ "นิพจน์จำนวนเต็มคาดว่า" ที่ทุบตีพิมพ์ออกมาในกรณีที่เราไม่มีตัวเลข

ถ้ำ (ขอบคุณความคิดเห็นด้านล่าง):

  • ตัวเลขที่มีจุดทศนิยมจะไม่ถูกระบุว่าเป็น "ตัวเลข" ที่ถูกต้อง
  • การใช้[[ ]]แทนที่จะ[ ]ประเมินผลเสมอtrue
  • เชลล์ที่ไม่ใช่ Bash ส่วนใหญ่จะประเมินนิพจน์นี้เป็นเสมอ true
  • พฤติกรรมใน Bash นั้นไม่มีเอกสารและอาจเปลี่ยนแปลงได้โดยไม่มีการเตือนล่วงหน้า
  • หากค่ามีช่องว่างหลังจากตัวเลข (เช่น "1 a") จะสร้างข้อผิดพลาดเช่น bash: [[: 1 a: syntax error in expression (error token is "a")
  • หากค่านั้นเหมือนกับ var-name (เช่น i = "i") จะสร้างข้อผิดพลาดเช่น bash: [[: i: expression recursion level exceeded (error token is "i")

8
ฉันยังคงแนะนำสิ่งนี้ (แต่ด้วยตัวแปรที่ยกมาเพื่ออนุญาตให้ใช้สตริงว่าง) เนื่องจากผลลัพธ์ถูกรับประกันว่าจะสามารถใช้งานเป็นตัวเลขใน Bash ได้ไม่ว่าจะเกิดอะไรขึ้นก็ตาม
l0b0

21
ระมัดระวังในการใช้วงเล็บเดียว [[ a -eq a ]]ประเมินเป็นจริง (ทั้งสองอาร์กิวเมนต์รับการแปลงเป็นศูนย์)
Tgr

3
ดีมาก! โปรดทราบว่านี่ใช้งานได้เฉพาะกับจำนวนเต็มไม่ใช่ตัวเลขใด ๆ ผมจำเป็นต้องมีการตรวจสอบอาร์กิวเมนต์เดียวซึ่งจะต้องเป็นจำนวนเต็มดังนั้นนี้ทำงานได้ดี:if ! [ $# -eq 1 -o "$1" -eq "$1" ] 2>/dev/null; then
haridsv

6
ฉันจะแนะนำอย่างยิ่งต่อวิธีนี้เพราะจำนวนเชลล์ที่ไม่สำคัญซึ่ง[บิวอินจะประเมินข้อโต้แย้งเป็นเลขคณิต นั่นเป็นจริงทั้งใน ksh93 และ mksh เพิ่มเติมเนื่องจากทั้งสองอาร์เรย์สนับสนุนมีโอกาสง่ายสำหรับการฉีดรหัส ใช้รูปแบบการจับคู่แทน
ormaaj

3
@AlbertoZaccagni ในรุ่นปัจจุบันของทุบตีค่าเหล่านี้จะถูกตีความกฎระเบียบที่เป็นตัวเลขบริบทเฉพาะแต่ไม่ได้สำหรับ[[ ]] [ ]ที่กล่าวว่าพฤติกรรมนี้ไม่ได้ระบุทั้ง POSIX มาตรฐานสำหรับtestและในเอกสารของทุบตี; เวอร์ชันในอนาคตของ bash สามารถปรับเปลี่ยนพฤติกรรมเพื่อให้ตรงกับ ksh โดยไม่ทำลายสัญญาพฤติกรรมใด ๆ ที่เป็นเอกสารดังนั้นการพึ่งพาพฤติกรรมปัจจุบันที่ยังคงมีอยู่จึงไม่รับประกันว่าจะปลอดภัย
Charles Duffy

43

วิธีนี้จะทดสอบว่าตัวเลขเป็นจำนวนเต็มลบหรือไม่และเป็นทั้งเชลล์แยกต่างหาก (เช่นไม่มี bashisms) และใช้เฉพาะบิวด์อินของเชลล์:

ที่ไม่ถูกต้อง

เนื่องจากคำตอบแรก (ด้านล่าง) อนุญาตให้ใช้จำนวนเต็มที่มีอักขระในนั้นตราบเท่าที่คำแรกไม่ได้อยู่ในตัวแปร

[ -z "${num##[0-9]*}" ] && echo "is a number" || echo "is not a number";

ที่ถูกต้อง

ในฐานะที่เป็นjillesความเห็นและแนะนำในคำตอบของเขานี่เป็นวิธีที่ถูกต้องในการใช้รูปแบบเปลือกหอย

[ ! -z "${num##*[!0-9]*}" ] && echo "is a number" || echo "is not a number";

5
สิ่งนี้ทำงานไม่ถูกต้องมันยอมรับสตริงใด ๆ ที่ขึ้นต้นด้วยตัวเลข โปรดทราบว่า WORD ใน $ {VAR ## WORD} และที่คล้ายกันเป็นรูปแบบของเชลล์ไม่ใช่นิพจน์ทั่วไป
jilles

2
คุณช่วยแปลสำนวนนั้นเป็นภาษาอังกฤษได้มั้ย ฉันต้องการใช้มันจริง ๆ แต่ฉันไม่เข้าใจเพียงพอที่จะเชื่อใจมันแม้หลังจากอ่านหน้า bash man แล้ว
CivFan

2
*[!0-9]*เป็นรูปแบบที่ตรงกับสตริงทั้งหมดที่มีอักขระที่ไม่ใช่ตัวเลขอย่างน้อย 1 ตัว ${num##*[!0-9]*}เป็น "การขยายพารามิเตอร์" ซึ่งเรานำเนื้อหาของnumตัวแปรและลบสตริงที่ยาวที่สุดที่ตรงกับรูปแบบ หากผลลัพธ์ของการขยายพารามิเตอร์ไม่ว่างเปล่า ( ! [ -z ${...} ]) แสดงว่าเป็นตัวเลขเนื่องจากไม่มีอักขระที่ไม่ใช่ตัวเลข
mrucci

น่าเสียดายที่สิ่งนี้ล้มเหลวหากมีตัวเลขใด ๆ ในอาร์กิวเมนต์แม้ว่าจะไม่ใช่ตัวเลขที่ถูกต้องก็ตาม ตัวอย่างเช่น "exam1ple" หรือ "a2b"
studgeek

ทั้งสองล้มเหลวด้วย122s :-(
Hastur

40

ไม่มีใครแนะนำการจับคู่รูปแบบเพิ่มเติมของ bash :

[[ $1 == ?(-)+([0-9]) ]] && echo "$1 is an integer"

5
Glenn ฉันลบออกshopt -s extglobจากโพสต์ของคุณ (ที่ฉัน upvoted เป็นหนึ่งในคำตอบโปรดของฉันที่นี่) เนื่องจากในโครงสร้างแบบมีเงื่อนไขคุณสามารถอ่านได้: เมื่อใช้ตัวดำเนินการ==และ!=ตัวดำเนินการสตริงทางด้านขวาของตัวดำเนินการจะถือเป็นรูปแบบและจับคู่ ตามกฎที่อธิบายไว้ด้านล่างในการจับคู่รูปแบบราวกับว่าextglobเปิดใช้งานตัวเลือกเชลล์ ฉันหวังว่าคุณจะไม่รังเกียจ!
gniourf_gniourf

ในบริบทดังกล่าวคุณไม่จำเป็นต้องshopt extglob... นั่นเป็นเรื่องดีที่จะรู้!
gniourf_gniourf

ทำงานได้ดีสำหรับจำนวนเต็มอย่างง่าย

2
@Jdamian: ถูกต้องถูกเพิ่มเข้ามาใน Bash 4.1 (ซึ่งเปิดตัวเมื่อปลายปี 2009 … Bash 3.2 เปิดตัวในปี 2549 …ตอนนี้เป็นซอฟต์แวร์โบราณขออภัยสำหรับผู้ที่ติดอยู่ในอดีต) นอกจากนี้คุณสามารถยืนยันว่า extglobs ที่เปิดตัวในรุ่น 2.02 (เปิดตัวในปี 1998) และไม่ทำงานใน <2.02 เวอร์ชั่น ... ตอนนี้ความคิดเห็นของคุณที่นี่จะทำหน้าที่เป็นข้อแม้เกี่ยวกับรุ่นเก่ากว่า
gniourf_gniourf

1
ตัวแปรภายใน[[...]]ไม่ขึ้นอยู่กับการแยกคำหรือการขยายแบบกลม
เกล็

26

ฉันประหลาดใจที่คำตอบของการแยกวิเคราะห์รูปแบบตัวเลขในเชลล์โดยตรง เชลล์ไม่เหมาะกับสิ่งนี้เพราะเป็น DSL สำหรับการควบคุมไฟล์และกระบวนการ มีตัวแยกจำนวนมากมายที่ต่ำลงเล็กน้อยเช่น:

isdecimal() {
  # filter octal/hex/ord()
  num=$(printf '%s' "$1" | sed "s/^0*\([1-9]\)/\1/; s/'/^/")

  test "$num" && printf '%f' "$num" >/dev/null 2>&1
}

เปลี่ยน '% f' เป็นรูปแบบเฉพาะที่คุณต้องการ


4
isnumber(){ printf '%f' "$1" &>/dev/null && echo "this is a number" || echo "not a number"; }
Gilles Quenot

4
@sputnick เวอร์ชันของคุณจะแบ่งความหมายของการส่งคืนค่า (และมีประโยชน์) โดยธรรมชาติของฟังก์ชันดั้งเดิม ดังนั้นให้ออกจากฟังก์ชั่นตามที่เป็นอยู่และใช้มันแทน:isnumber 23 && echo "this is a number" || echo "not a number"
ไมเคิล

4
สิ่งนี้ไม่ควรมีเช่น2>/dev/nullกันดังนั้นจึงisnumber "foo"ไม่ก่อให้เกิดมลพิษ stderr?
gioele

4
ในการเรียกเชลล์ที่ทันสมัยเช่นทุบตี "DSL สำหรับควบคุมไฟล์และกระบวนการ" นั้นไม่สนใจว่ามันถูกใช้มากกว่านั้น - distros บางตัวได้สร้างตัวจัดการแพคเกจทั้งหมดและเว็บอินเตอร์เฟสบนมัน (น่าเกลียดที่สุด) ไฟล์แบตช์พอดีกับคำอธิบายของคุณแม้ว่าจะเป็นการตั้งค่าตัวแปรก็ยาก
Camilo Martin

7
เป็นเรื่องตลกที่คุณพยายามฉลาดโดยคัดลอกสำนวนจากภาษาอื่น น่าเสียดายที่นี่ใช้ไม่ได้กับหอย Shells มีความพิเศษเป็นอย่างมากและหากไม่มีความรู้เกี่ยวกับมันอย่างชัดเจนคุณก็น่าจะเขียนโค้ดที่เสียหาย รหัสของคุณเสีย: isnumber "'a"จะคืนค่าจริง นี่เป็นเอกสารในข้อมูลจำเพาะ POSIXที่คุณจะอ่าน: หากอักขระนำหน้าเป็นเครื่องหมายคำพูดเดี่ยวหรือเครื่องหมายคำพูดคู่ค่าจะเป็นค่าตัวเลขในชุดโค้ดพื้นฐานของอักขระตามเครื่องหมายคำพูดเดี่ยวหรือเครื่องหมายคำพูดคู่ .
gniourf_gniourf

16

ฉันกำลังดูคำตอบและ ... รู้ว่าไม่มีใครคิดเกี่ยวกับหมายเลขลอย (มีจุด)!

การใช้ grep ก็เยี่ยมเช่นกัน
-E หมายถึงการขยาย regexp
-q หมายถึงเงียบ (ไม่สะท้อน)
-qE เป็นการรวมกันของทั้งสอง

หากต้องการทดสอบโดยตรงในบรรทัดคำสั่ง:

$ echo "32" | grep -E ^\-?[0-9]?\.?[0-9]+$  
# answer is: 32

$ echo "3a2" | grep -E ^\-?[0-9]?\.?[0-9]+$  
# answer is empty (false)

$ echo ".5" | grep -E ^\-?[0-9]?\.?[0-9]+$  
# answer .5

$ echo "3.2" | grep -E ^\-?[0-9]?\.?[0-9]+$  
# answer is 3.2

ใช้ในสคริปต์ทุบตี:

check=`echo "$1" | grep -E ^\-?[0-9]*\.?[0-9]+$`

if [ "$check" != '' ]; then    
  # it IS numeric
  echo "Yeap!"
else
  # it is NOT numeric.
  echo "nooop"
fi

เพื่อให้ตรงกับจำนวนเต็ม JUST ใช้สิ่งนี้:

# change check line to:
check=`echo "$1" | grep -E ^\-?[0-9]+$`

โซลูชันที่ใช้ awk โดย triple_r และ tripleee ทำงานกับแพลทฟอร์ม
Ken Jackson

ขอบคุณสำหรับสิ่งนี้และเป็นจุดที่ดีมาก! สาเหตุของคำถามคือวิธีตรวจสอบว่าเป็นตัวเลขไม่ใช่เฉพาะจำนวนเต็มหรือไม่
Tanasis

ฉันขอบคุณ Tanasis ด้วย! มาช่วยเหลือกันเสมอ
Sergio Abreu

12

เพียงแค่ติดตาม @mary แต่เนื่องจากฉันมีตัวแทนไม่เพียงพอจึงไม่สามารถโพสต์สิ่งนี้เป็นความคิดเห็นในโพสต์นั้น อย่างไรก็ตามนี่คือสิ่งที่ฉันใช้:

isnum() { awk -v a="$1" 'BEGIN {print (a == a + 0)}'; }

ฟังก์ชั่นจะคืนค่า "1" ถ้าอาร์กิวเมนต์เป็นตัวเลขมิฉะนั้นจะส่งคืน "0" มันใช้งานได้สำหรับจำนวนเต็มเช่นเดียวกับลอย การใช้งานเป็นสิ่งที่ชอบ:

n=-2.05e+07
res=`isnum "$n"`
if [ "$res" == "1" ]; then
     echo "$n is a number"
else
     echo "$n is not a number"
fi

4
การพิมพ์ตัวเลขมีประโยชน์น้อยกว่าการตั้งรหัสออก 'BEGIN { exit(1-(a==a+0)) }'เป็นเรื่องยากเล็กน้อยเพื่อ grok แต่สามารถนำมาใช้ในการทำงานซึ่งผลตอบแทนจริงหรือเท็จเหมือน[, grep -qฯลฯ
tripleee

9
test -z "${i//[0-9]}" && echo digits || echo no no no

${i//[0-9]}แทนที่หลักใด ๆ ในค่าของกับสตริงที่ว่างเปล่าให้ดู$i ตรวจสอบว่าสตริงผลลัพธ์มีความยาวเป็นศูนย์หรือไม่man -P 'less +/parameter\/' bash-z

หากคุณต้องการแยกกรณีเมื่อ$iว่างเปล่าคุณสามารถใช้หนึ่งในสิ่งปลูกสร้างเหล่านี้:

test -n "$i" && test -z "${i//[0-9]}" && echo digits || echo not a number
[[ -n "$i" && -z "${i//[0-9]}" ]] && echo digits || echo not a number

ยกนิ้วให้โดยเฉพาะสำหรับชิ้นman -P 'less +/parameter\/' bashส่วน เรียนรู้สิ่งใหม่ทุกวัน :)
เดวิด

@sjas คุณสามารถเพิ่ม\-นิพจน์ปกติเพื่อแก้ไขปัญหาได้อย่างง่ายดาย ใช้[0-9\-\.\+]เพื่อบัญชีลอยและหมายเลขที่ลงนาม
user2683246

@sjas ตกลงความผิดของฉัน
user2683246

@sjasecho $i | python -c $'import sys\ntry:\n float(sys.stdin.read().rstrip())\nexcept:\n sys.exit(1)' && echo yes || echo no
user2683246

9

สำหรับปัญหาของฉันฉันเพียงต้องการให้แน่ใจว่าผู้ใช้ไม่ได้ป้อนข้อความโดยไม่ได้ตั้งใจดังนั้นฉันจึงพยายามทำให้มันง่ายและอ่านได้

isNumber() {
    (( $1 )) 2>/dev/null
}

ตามหน้า man นี้มันสวยมากในสิ่งที่ฉันต้องการ

หากค่าของนิพจน์ไม่ใช่ศูนย์สถานะการส่งคืนจะเป็น 0

เพื่อป้องกันข้อความแสดงข้อผิดพลาดที่น่ารังเกียจสำหรับสตริงที่ "อาจเป็นตัวเลข" ฉันไม่สนใจเอาต์พุตข้อผิดพลาด

$ (( 2s ))
bash: ((: 2s: value too great for base (error token is "2s")

นี่เป็นเหมือน isInteger แต่ขอบคุณมาก!
Naheel

8

คำถามเก่า แต่ฉันแค่ต้องการที่จะแก้ปัญหาของฉัน อันนี้ไม่ต้องใช้ลูกเล่นแปลก ๆ ของเปลือกหอยหรือพึ่งพาสิ่งที่ไม่เคยมีมาก่อน

if [ -n "$(printf '%s\n' "$var" | sed 's/[0-9]//g')" ]; then
    echo 'is not numeric'
else
    echo 'is numeric'
fi

โดยพื้นฐานแล้วมันจะลบตัวเลขทั้งหมดออกจากอินพุตและหากคุณมีสตริงที่ไม่ใช่ความยาวเป็นศูนย์นั่นก็ไม่ใช่ตัวเลข


varนี้ล้มเหลวสำหรับที่ว่างเปล่า
gniourf_gniourf

หรือตัวแปรที่มีการขึ้นบรรทัดใหม่หรืออะไรทำนอง$'0\n\n\n1\n\n\n2\n\n\n3\n'นั้น
gniourf_gniourf

7

ยังไม่สามารถแสดงความคิดเห็นได้ดังนั้นฉันจะเพิ่มคำตอบของฉันเองซึ่งเป็นส่วนขยายของคำตอบของ glenn jackman โดยใช้การจับคู่รูปแบบ bash

ความต้องการดั้งเดิมของฉันคือการระบุตัวเลขและแยกแยะจำนวนเต็มและจำนวนลอย คำจำกัดความของฟังก์ชันหักออกเป็น:

function isInteger() {
    [[ ${1} == ?(-)+([0-9]) ]]
}

function isFloat() {
    [[ ${1} == ?(-)@(+([0-9]).*([0-9])|*([0-9]).+([0-9]))?(E?(-|+)+([0-9])) ]]
}

ฉันใช้การทดสอบหน่วย (ด้วย shUnit2) เพื่อตรวจสอบรูปแบบการทำงานตามที่ต้องการ:

oneTimeSetUp() {
    int_values="0 123 -0 -123"
    float_values="0.0 0. .0 -0.0 -0. -.0 \
        123.456 123. .456 -123.456 -123. -.456
        123.456E08 123.E08 .456E08 -123.456E08 -123.E08 -.456E08 \
        123.456E+08 123.E+08 .456E+08 -123.456E+08 -123.E+08 -.456E+08 \
        123.456E-08 123.E-08 .456E-08 -123.456E-08 -123.E-08 -.456E-08"
}

testIsIntegerIsFloat() {
    local value
    for value in ${int_values}
    do
        assertTrue "${value} should be tested as integer" "isInteger ${value}"
        assertFalse "${value} should not be tested as float" "isFloat ${value}"
    done

    for value in ${float_values}
    do
        assertTrue "${value} should be tested as float" "isFloat ${value}"
        assertFalse "${value} should not be tested as integer" "isInteger ${value}"
    done

}

หมายเหตุ: รูปแบบ isFloat สามารถปรับเปลี่ยนให้มีความทนทานมากขึ้นเกี่ยวกับจุดทศนิยม ( @(.,)) และสัญลักษณ์ E ( @(Ee)) หน่วยของฉันทดสอบทดสอบเฉพาะค่าที่เป็นจำนวนเต็มหรือลอย แต่ไม่มีอินพุตที่ไม่ถูกต้อง


ฉันขอโทษไม่เข้าใจว่าจะใช้ฟังก์ชันแก้ไขอย่างไร
3ronco

6

ฉันจะลองสิ่งนี้:

printf "%g" "$var" &> /dev/null
if [[ $? == 0 ]] ; then
    echo "$var is a number."
else
    echo "$var is not a number."
fi

หมายเหตุ: สิ่งนี้จะจดจำน่านและ inf เป็นตัวเลข


2
อย่างใดอย่างหนึ่งที่ซ้ำกันของหรือบางทีอาจจะเหมาะดีเป็นความคิดเห็นที่คำตอบของ pixelbeat (ใช้%fอาจจะดีกว่าอยู่แล้ว)
ไมเคิล

3
แทนที่จะตรวจสอบรหัสสถานะก่อนหน้าทำไมไม่ลองใส่ในifตัวมันเองล่ะ? นั่นคือสิ่งที่if...if printf "%g" "$var" &> /dev/null; then ...
Camilo Martin

3
นี่มีข้อแม้อื่น ๆ 'aมันจะตรวจสอบสตริงว่างและสตริงชอบ
gniourf_gniourf

6

คำตอบที่ชัดเจนได้รับจาก @charles Dufy และคนอื่น ๆ แล้ว วิธีทุบตีบริสุทธิ์จะใช้สิ่งต่อไปนี้:

string="-12,345"
if [[ "$string" =~ ^-?[0-9]+[.,]?[0-9]*$ ]]
then
    echo $string is a number
else
    echo $string is not a number
fi

แม้ว่าสำหรับจำนวนจริงมันไม่จำเป็นต้องมีตัวเลขก่อนจุดฐานจุดฐาน

เพื่อให้การสนับสนุนอย่างละเอียดยิ่งขึ้นของตัวเลขลอยตัวและสัญกรณ์ทางวิทยาศาสตร์ (หลายโปรแกรมใน C / Fortran หรืออื่น ๆ จะส่งออกลอยตัวด้วยวิธีนี้) การเพิ่มประโยชน์ให้กับบรรทัดนี้จะเป็นดังต่อไปนี้:

string="1.2345E-67"
if [[ "$string" =~ ^-?[0-9]*[.,]?[0-9]*[eE]?-?[0-9]+$ ]]
then
    echo $string is a number
else
    echo $string is not a number
fi

จึงนำไปสู่การแยกประเภทของตัวเลขหากคุณกำลังมองหาประเภทเฉพาะ:

string="-12,345"
if [[ "$string" =~ ^-?[0-9]+$ ]]
then
    echo $string is an integer
elif [[ "$string" =~ ^-?[0-9]*[.,]?[0-9]*$ ]]
then
    echo $string is a float
elif [[ "$string" =~ ^-?[0-9]*[.,]?[0-9]*[eE]-?[0-9]+$ ]]
then
    echo $string is a scientific number
else
    echo $string is not a number
fi

หมายเหตุ: เราสามารถแสดงรายการข้อกำหนดเกี่ยวกับการสร้างประโยคสำหรับสัญกรณ์ทศนิยมและวิทยาศาสตร์หนึ่งรายการเพื่ออนุญาตให้ใช้เครื่องหมายจุลภาคเป็นจุดฐานเช่นเดียวกับ "." จากนั้นเราจะยืนยันว่าจะต้องมีจุด Radix เพียงจุดเดียวเท่านั้น สามารถมีเครื่องหมาย +/- สองอันในทุ่น [Ee] ฉันได้เรียนรู้กฎเพิ่มเติมจากการทำงานของ Aulu และทดสอบกับสตริงที่ไม่ดีเช่น '' '-' '-E-1' '0-0' นี่คือเครื่องมือ regex / substring / expr ของฉันที่ดูเหมือนจะค้าง:

parse_num() {
 local r=`expr "$1" : '.*\([.,]\)' 2>/dev/null | tr -d '\n'` 
 nat='^[+-]?[0-9]+[.,]?$' \
 dot="${1%[.,]*}${r}${1##*[.,]}" \
 float='^[\+\-]?([.,0-9]+[Ee]?[-+]?|)[0-9]+$'
 [[ "$1" == $dot ]] && [[ "$1" =~ $float ]] || [[ "$1" =~ $nat ]]
} # usage: parse_num -123.456

6
[[ $1 =~ ^-?[0-9]+$ ]] && echo "number"

อย่าลืม-ใส่จำนวนลบ!


เวอร์ชั่นต่ำสุดของ bash คืออะไร? ฉันเพิ่งได้รับ bash: ผู้ประกอบการไบนารีตามเงื่อนไขที่คาดหวัง bash: ข้อผิดพลาดทางไวยากรณ์ใกล้กับโทเค็นที่ไม่คาดคิด `= ~ '
Paul Hargreaves

1
@PaulHargreaves =~อย่างน้อยก็ไกลถึง bash 3.0
Gilles 'หยุดความชั่วร้าย'

@PaulHargreaves คุณอาจมีปัญหากับตัวถูกดำเนินการครั้งแรกของคุณเช่นเครื่องหมายคำพูดมากเกินไปหรือคล้ายกัน
Joshua Clayton

@JoshuaClayton ฉันถามเกี่ยวกับเวอร์ชั่นนี้เพราะมันทุบตีมากในกล่อง Solaris 7 ซึ่งเรายังมีอยู่และมันไม่รองรับ = ~
Paul Hargreaves

6

สิ่งนี้สามารถทำได้โดยใช้grepเพื่อดูว่าตัวแปรในคำถามตรงกับการแสดงออกปกติเพิ่มเติม

ทดสอบจำนวนเต็ม1120:

yournumber=1120
if echo "$yournumber" | grep -qE '^[0-9]+$'; then
    echo "Valid number."
else
    echo "Error: not a number."
fi

เอาท์พุท: Valid number.

ทดสอบไม่ใช่จำนวนเต็ม1120a:

yournumber=1120a
if echo "$yournumber" | grep -qE '^[0-9]+$'; then
    echo "Valid number."
else
    echo "Error: not a number."
fi

เอาท์พุท: Error: not a number.


คำอธิบาย

  • grepที่-Eสวิทช์ช่วยให้เราสามารถใช้การแสดงออกปกติขยาย'^[0-9]+$'สวิทช์ช่วยให้เราสามารถใช้การแสดงออกปกติขยายนิพจน์ทั่วไปนี้หมายความว่าตัวแปรควร[]มีตัวเลข0-9เป็นศูนย์ถึงเก้าจาก^จุดเริ่มต้นไปจนถึง$จุดสิ้นสุดของตัวแปรและควรมี+อักขระอย่างน้อยหนึ่งตัว
  • grepที่-qเปลี่ยนสวิทช์ปิดเงียบผลใด ๆ หรือไม่ก็พบอะไร
  • ifgrepตรวจสอบสถานะออกจาก สถานะการออก0หมายถึงความสำเร็จ grepคำสั่งมีสถานะทางออกของ0ถ้าพบการแข่งขันและ1เมื่อมันไม่ได้;

ดังนั้นการวางไว้ด้วยกันในifการทดสอบเราechoตัวแปร$yournumberและ|ท่อไปยังgrepที่ที่มี-qสวิทช์เงียบตรงกับ-Eการขยายการแสดงออกปกติ'^[0-9]+$'การแสดงออก สถานะทางออกของgrepจะเป็น0ถ้าgrepพบการแข่งขันที่ประสบความสำเร็จและ1ไม่ได้ echo "Valid number."หากประสบความสำเร็จเพื่อให้ตรงกับเรา echo "Error: not a number."ถ้ามันไม่ตรงกับเรา


สำหรับลอยหรือคู่

เราสามารถเปลี่ยนการแสดงออกปกติจาก'^[0-9]+$'เป็น'^[0-9]*\.?[0-9]+$'ลอยหรือเป็นสองเท่า

ทดสอบลอย1120.01:

yournumber=1120.01
if echo "$yournumber" | grep -qE '^[0-9]*\.?[0-9]+$'; then
    echo "Valid number."
else
    echo "Error: not a number."
fi

เอาท์พุท: Valid number.

ทดสอบลอย11.20.01:

yournumber=11.20.01
if echo "$yournumber" | grep -qE '^[0-9]*\.?[0-9]+$'; then
    echo "Valid number."
else
    echo "Error: not a number."
fi

เอาท์พุท: Error: not a number.


สำหรับเชิงลบ

ในการอนุญาตให้จำนวนเต็มลบเพียงแค่เปลี่ยนการแสดงออกปกติจากการ'^[0-9]+$''^\-?[0-9]+$'

ในการอนุญาตให้ลอยเชิงลบหรือคู่เพียงแค่เปลี่ยนการแสดงออกปกติจากการ'^[0-9]*\.?[0-9]+$''^\-?[0-9]*\.?[0-9]+$'


คุณพูดถูก @CharlesDuffy ขอบคุณ ฉันล้างคำตอบ
โจเซฟชิห์

คุณอยู่ที่ @CharlesDuffy อีกครั้ง แก้ไขแล้ว!
Joseph Shih

1
LGTM; คำตอบที่แก้ไขได้ +1 ของฉัน สิ่งเดียวที่ฉันทำแตกต่างกัน ณ จุดนี้เป็นเพียงเรื่องของความคิดเห็นมากกว่าความถูกต้อง (f / e, การใช้[-]แทน\-และ[.]แทนที่จะ\.เป็น verbose อีกเล็กน้อย แต่มันหมายความว่าสายของคุณไม่ต้องเปลี่ยนถ้าพวกเขา ' ใช้ในบริบทที่แบ็กสแลชถูกใช้ไป)
Charles Duffy

4

http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_04_03.html

นอกจากนี้คุณยังสามารถใช้คลาสอักขระของ bash

if [[ $VAR = *[[:digit:]]* ]]; then
 echo "$VAR is numeric"
else
 echo "$VAR is not numeric"
fi

ตัวเลขจะรวมถึงช่องว่างจุดทศนิยมและ "e" หรือ "E" สำหรับทศนิยม

แต่ถ้าคุณระบุหมายเลขฐานสิบหกแบบ C คือ "0xffff" หรือ "0XFFFF", [[: digit:]] จะส่งกลับค่าจริง กับดักที่นี่นิดหน่อย bash ช่วยให้คุณทำอะไรบางอย่างเช่น "0xAZ00" และยังคงนับมันเป็นตัวเลข (ไม่ใช่สิ่งนี้จากมุมมองแปลก ๆ ของคอมไพเลอร์ GCC ที่ให้คุณใช้สัญกรณ์ 0x สำหรับฐานอื่นที่ไม่ใช่ 16 ??? )

คุณอาจต้องการทดสอบ "0x" หรือ "0X" ก่อนทดสอบว่าเป็นตัวเลขหรือไม่ถ้าข้อมูลของคุณไม่น่าเชื่อถืออย่างสมบูรณ์เว้นแต่คุณต้องการยอมรับตัวเลขฐานสิบหก ที่จะทำได้โดย:

if [[ ${VARIABLE:1:2} = "0x" ]] || [[ ${VARIABLE:1:2} = "0X" ]]; then echo "$VAR is not numeric"; fi

17
[[ $VAR = *[[:digit:]]* ]]จะคืนค่าจริงถ้าตัวแปรมีตัวเลขไม่ใช่ถ้าเป็นจำนวนเต็ม
เกล็นแจ็

[[ "z3*&" = *[[:digit:]]* ]] && echo "numeric"numericพิมพ์ 3.2.25(1)-releaseผ่านการทดสอบในรุ่นทุบตี
Jdamian

1
@ultraswadable โซลูชันของคุณจะตรวจพบสตริงเหล่านั้นที่มีอย่างน้อยหนึ่งหลักล้อมรอบ (หรือไม่) ด้วยอักขระอื่น ๆ ฉันลงคะแนน
Jdamian

วิธีการที่ถูกต้องชัดเจนจึงกลับรายการนี้และใช้[[ -n $VAR && $VAR != *[^[:digit:]]* ]]
eschwartz

@ eschwartz โซลูชันของคุณใช้ไม่ได้กับจำนวนลบ
Angel

4

วิธีที่ง่ายที่สุดคือการตรวจสอบว่ามีอักขระที่ไม่ใช่ตัวเลขหรือไม่ คุณแทนที่ตัวละครหลักทั้งหมดด้วยอะไรและตรวจสอบความยาว หากมีความยาวไม่ใช่ตัวเลข

if [[ ! -n ${input//[0-9]/} ]]; then
    echo "Input Is A Number"
fi

2
ในการจัดการกับจำนวนลบจะต้องใช้วิธีที่ซับซ้อนกว่านี้
Andy

... หรือเครื่องหมายบวกก็ได้
tripleee

@tripleee ฉันต้องการที่จะเห็นวิธีการของคุณถ้าคุณรู้วิธีที่จะทำ
Andy

4

เพราะฉันต้องยุ่งกับเรื่องนี้เมื่อเร็ว ๆ นี้และชอบappoach ของ karttu กับหน่วยทดสอบมากที่สุด ฉันแก้ไขรหัสและเพิ่มโซลูชันอื่น ๆ ด้วยลองด้วยตัวคุณเองเพื่อดูผลลัพธ์:

#!/bin/bash

    # N={0,1,2,3,...} by syntaxerror
function isNaturalNumber()
{
 [[ ${1} =~ ^[0-9]+$ ]]
}
    # Z={...,-2,-1,0,1,2,...} by karttu
function isInteger() 
{
 [[ ${1} == ?(-)+([0-9]) ]]
}
    # Q={...,-½,-¼,0.0,¼,½,...} by karttu
function isFloat() 
{
 [[ ${1} == ?(-)@(+([0-9]).*([0-9])|*([0-9]).+([0-9]))?(E?(-|+)+([0-9])) ]]
}
    # R={...,-1,-½,-¼,0.E+n,¼,½,1,...}
function isNumber()
{
 isNaturalNumber $1 || isInteger $1 || isFloat $1
}

bools=("TRUE" "FALSE")
int_values="0 123 -0 -123"
float_values="0.0 0. .0 -0.0 -0. -.0 \
    123.456 123. .456 -123.456 -123. -.456 \
    123.456E08 123.E08 .456E08 -123.456E08 -123.E08 -.456E08 \
    123.456E+08 123.E+08 .456E+08 -123.456E+08 -123.E+08 -.456E+08 \
    123.456E-08 123.E-08 .456E-08 -123.456E-08 -123.E-08 -.456E-08"
false_values="blah meh mooh blah5 67mooh a123bc"

for value in ${int_values} ${float_values} ${false_values}
do
    printf "  %5s=%-30s" $(isNaturalNumber $value) ${bools[$?]} $(printf "isNaturalNumber(%s)" $value)
    printf "%5s=%-24s" $(isInteger $value) ${bools[$?]} $(printf "isInteger(%s)" $value)
    printf "%5s=%-24s" $(isFloat $value) ${bools[$?]} $(printf "isFloat(%s)" $value)
    printf "%5s=%-24s\n" $(isNumber $value) ${bools[$?]} $(printf "isNumber(%s)" $value)
done

ดังนั้นisNumber ()รวมถึงเครื่องหมายขีดกลางเครื่องหมายจุลภาคและสัญลักษณ์เอ็กซ์โปเนนเชียลดังนั้นจึงส่งคืน TRUE บนจำนวนเต็ม & ลอยโดยที่isFloat ()ส่งคืนค่า FALSE จากค่าจำนวนเต็มและisInteger ()จะส่งคืน FALSE บนลอย เพื่อความสะดวกของคุณทั้งหมดในฐานะหนึ่งสมุทร:

isNaturalNumber() { [[ ${1} =~ ^[0-9]+$ ]]; }
isInteger() { [[ ${1} == ?(-)+([0-9]) ]]; }
isFloat() { [[ ${1} == ?(-)@(+([0-9]).*([0-9])|*([0-9]).+([0-9]))?(E?(-|+)+([0-9])) ]]; }
isNumber() { isNaturalNumber $1 || isInteger $1 || isFloat $1; }

โดยส่วนตัวฉันจะลบfunctionคำหลักออกเนื่องจากไม่มีประโยชน์ใด ๆ นอกจากนี้ฉันไม่แน่ใจเกี่ยวกับประโยชน์ของค่าส่งคืน ฟังก์ชั่นจะคืนสถานะการออกของคำสั่งสุดท้ายนอกจากคุณจะไม่ต้องทำreturnอะไรด้วยตัวเอง
Tom Fenech

นิสัยดีreturnทำให้สับสนและทำให้อ่านไม่ได้ การใช้functionคำหลักหรือไม่เป็นคำถามของรสนิยมส่วนตัวอย่างน้อยฉันก็ลบมันออกจาก liners หนึ่งเพื่อประหยัดพื้นที่ ขอบคุณ.
3ronco

อย่าลืมว่าจำเป็นต้องใช้เครื่องหมายอัฒภาคหลังจากการทดสอบสำหรับเวอร์ชันหนึ่งบรรทัด
Tom Fenech

2
isNumber จะส่งกลับ 'จริง' ในสตริงใด ๆ ที่มีตัวเลขอยู่
DrStrangepork

@DrStrangepork แน่นอนอาร์เรย์ false_values ​​ของฉันหายไปในกรณีนี้ ฉันจะดูมัน ขอบคุณสำหรับคำใบ้
3ronco

4

ฉันใช้expr มันจะส่งกลับไม่ใช่ศูนย์ถ้าคุณพยายามที่จะเพิ่มศูนย์ให้เป็นค่าที่ไม่ใช่ตัวเลข:

if expr -- "$number" + 0 > /dev/null 2>&1
then
    echo "$number is a number"
else
    echo "$number isn't a number"
fi

อาจเป็นไปได้ที่จะใช้bcหากคุณต้องการไม่ใช่จำนวนเต็ม แต่ฉันไม่เชื่อว่าbcมีพฤติกรรมเหมือนกัน การเพิ่มศูนย์ให้กับตัวเลขที่ไม่ได้ทำให้คุณเป็นศูนย์และจะส่งกลับค่าศูนย์เช่นกัน บางทีคุณสามารถรวมbcและexpr. ใช้ในการเพิ่มศูนย์ถึงbc $numberหากคำตอบคือ0ให้ลองexprตรวจสอบว่า$numberไม่ใช่ศูนย์


1
มันค่อนข้างแย่ เพื่อให้ดีขึ้นเล็กน้อยคุณควรใช้expr -- "$number" + 0; ๆ 0 isn't a numberนี้จะยังคงแกล้งทำเป็นว่า จากman expr:Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null or 0,
gniourf_gniourf

3

ฉันชอบคำตอบของ Alberto Zaccagni

if [ "$var" -eq "$var" ] 2>/dev/null; then

สิ่งที่จำเป็นต้องมีที่สำคัญ: - ไม่มี subshells วางไข่ - ไม่เรียกใช้ตัวแยกวิเคราะห์ RE - แอปพลิเคชันเชลล์ส่วนใหญ่จะไม่ใช้ตัวเลขจริง

แต่ถ้า$varมีความซับซ้อน (เช่นการเข้าถึงอาเรย์แบบเชื่อมโยง) และถ้าจำนวนจะเป็นจำนวนเต็มแบบไม่ลบ (ส่วนใหญ่ใช้กรณี) นี่อาจจะมีประสิทธิภาพมากกว่าหรือไม่

if [ "$var" -ge 0 ] 2> /dev/null; then ..

2

ฉันใช้ printf เป็นคำตอบอื่น ๆ ที่กล่าวถึงถ้าคุณระบุสตริงรูปแบบ "% f" หรือ "% i" printf จะทำการตรวจสอบให้คุณ ง่ายกว่าการตรวจสอบซ้ำอีกครั้งไวยากรณ์นั้นง่ายและสั้นและ printf นั้นแพร่หลาย ดังนั้นมันจึงเป็นทางเลือกที่ดีในความคิดของฉัน - คุณสามารถใช้แนวคิดต่อไปนี้เพื่อตรวจสอบสิ่งต่าง ๆ มันไม่เพียงมีประโยชน์สำหรับการตรวจสอบตัวเลข

declare  -r CHECK_FLOAT="%f"  
declare  -r CHECK_INTEGER="%i"  

 ## <arg 1> Number - Number to check  
 ## <arg 2> String - Number type to check  
 ## <arg 3> String - Error message  
function check_number() { 
  local NUMBER="${1}" 
  local NUMBER_TYPE="${2}" 
  local ERROR_MESG="${3}"
  local -i PASS=1 
  local -i FAIL=0   
  case "${NUMBER_TYPE}" in 
    "${CHECK_FLOAT}") 
        if ((! $(printf "${CHECK_FLOAT}" "${NUMBER}" &>/dev/random;echo $?))); then 
           echo "${PASS}"
        else 
           echo "${ERROR_MESG}" 1>&2
           echo "${FAIL}"
        fi 
        ;;                 
    "${CHECK_INTEGER}") 
        if ((! $(printf "${CHECK_INTEGER}" "${NUMBER}" &>/dev/random;echo $?))); then 
           echo "${PASS}"
        else 
           echo "${ERROR_MESG}" 1>&2
           echo "${FAIL}"
        fi 
        ;;                 
                     *) 
        echo "Invalid number type format: ${NUMBER_TYPE} to check_number()." 1>&2
        echo "${FAIL}"
        ;;                 
   esac
} 

>$ var=45

>$ (($(check_number $var "${CHECK_INTEGER}" "Error: Found $var - An integer is required."))) && { echo "$var+5" | bc; }


2

การแสดงออกปกติเทียบกับ การขยายพารามิเตอร์

เช่น เป็นคำตอบของ Charles Duffyทำงาน แต่ใช้ การแสดงออกปกติและฉันรู้ว่าสิ่งนี้จะช้าฉันต้องการที่จะแสดงวิธีอื่นโดยใช้เพียงการขยายพารามิเตอร์ :

สำหรับจำนวนเต็มบวกเท่านั้น:

is_num() { [ "$1" ] && [ -z "${1//[0-9]}" ] ;}

สำหรับจำนวนเต็ม:

is_num() { 
    local chk=${1#[+-]};
    [ "$chk" ] && [ -z "${chk//[0-9]}" ]
}

จากนั้นสำหรับการลอย:

is_num() { 
    local chk=${1#[+-]};
    chk=${chk/.}
    [ "$chk" ] && [ -z "${chk//[0-9]}" ]
}

เพื่อให้ตรงกับคำขอเริ่มต้น:

set -- "foo bar"
is_num "$1" && VAR=$1 || echo "need a number"
need a number

set -- "+3.141592"
is_num "$1" && VAR=$1 || echo "need a number"

echo $VAR
+3.141592

ตอนนี้การเปรียบเทียบเล็กน้อย:

มีการตรวจสอบเดียวกันตามคำตอบของ Charles Duffy :

cdIs_num() { 
    local re='^[+-]?[0-9]+([.][0-9]+)?$';
    [[ $1 =~ $re ]]
}

การทดสอบบางอย่าง:

if is_num foo;then echo It\'s a number ;else echo Not a number;fi
Not a number
if cdIs_num foo;then echo It\'s a number ;else echo Not a number;fi
Not a number
if is_num 25;then echo It\'s a number ;else echo Not a number;fi
It's a number
if cdIs_num 25;then echo It\'s a number ;else echo Not a number;fi
It's a number
if is_num 3+4;then echo It\'s a number ;else echo Not a number;fi
Not a number
if cdIs_num 3+4;then echo It\'s a number ;else echo Not a number;fi
Not a number
if is_num 3.1415;then echo It\'s a number ;else echo Not a number;fi
It's a number
if cdIs_num 3.1415;then echo It\'s a number ;else echo Not a number;fi
It's a number

ตกลงก็ไม่เป็นไร ตอนนี้ใช้เวลาทั้งหมดเท่าไหร่ (บนราสเบอร์รี่ปี่ของฉัน):

time for i in {1..1000};do is_num +3.14159265;done
real    0m2.476s
user    0m1.235s
sys     0m0.000s

จากนั้นด้วยนิพจน์ทั่วไป:

time for i in {1..1000};do cdIs_num +3.14159265;done
real    0m4.363s
user    0m2.168s
sys     0m0.000s

ผมเห็นแล้วผมไม่ต้องการที่จะใช้ regex เมื่อฉันสามารถใช้การขยายตัวพารามิเตอร์ ... เหยียดหยามของ RE จะทำให้สคริปต์ทุบตีช้า
เอฟ HAURI

คำตอบแก้ไข: ไม่จำเป็นต้องextglob(และเร็วขึ้นเล็กน้อย)!
F. Hauri

@CharlesDuffy บน raspberry pi ของฉันการวนซ้ำ 1,000 ครั้งจะใช้เวลา 2,5sec กับเวอร์ชันของฉันและ 4,4sec กับเวอร์ชันของคุณ!
F. Hauri

ไม่สามารถโต้แย้งได้ 👍
ชาร์ลส์ดัฟฟี่

1

วิธีจับหมายเลขติดลบ:

if [[ $1 == ?(-)+([0-9.]) ]]
    then
    echo number
else
    echo not a number
fi

นอกจากนี้ต้องเปิดใช้งานแบบขยายเพิ่มเติมก่อนจึงจะสามารถเปิดใช้งานได้ นี่เป็นคุณสมบัติ Bash-only ซึ่งถูกปิดใช้งานโดยค่าเริ่มต้น
tripleee

@tripleee globbing แบบขยายถูกเปิดใช้งานโดยอัตโนมัติเมื่อใช้ == หรือ! = When the ‘==’ and ‘!=’ operators are used, the string to the right of the operator is considered a pattern and matched according to the rules described below in Pattern Matching, as if the extglob shell option were enabled. gnu.org/software/bash/manual/bashref.html#index-_005b_005b
Badr Elmers

@BadrElmers ขอบคุณสำหรับการอัพเดท นี่ดูเหมือนจะเป็นพฤติกรรมใหม่ที่ไม่เป็นความจริงใน Bash 3.2.57 ของฉัน (MacOS Mojave) ฉันเห็นว่ามันทำงานได้ตามที่คุณอธิบายไว้ใน 4.4
tripleee

1

คุณสามารถใช้ "ให้" เช่นนี้:

[ ~]$ var=1
[ ~]$ let $var && echo "It's a number" || echo "It's not a number"
It\'s a number
[ ~]$ var=01
[ ~]$ let $var && echo "It's a number" || echo "It's not a number"
It\'s a number
[ ~]$ var=toto
[ ~]$ let $var && echo "It's a number" || echo "It's not a number"
It\'s not a number
[ ~]$ 

แต่ฉันชอบใช้โอเปอเรเตอร์ "= ~" Bash 3+ เหมือนคำตอบในกระทู้นี้


5
มันอันตรายมาก อย่าประเมินค่าทางคณิตศาสตร์ที่ไม่ผ่านการอนุมัติในเชลล์ มันจะต้องตรวจสอบวิธีอื่นก่อน
ormaaj

1
printf '%b' "-123\nABC" | tr '[:space:]' '_' | grep -q '^-\?[[:digit:]]\+$' && echo "Integer." || echo "NOT integer."

ลบ-\?รูปแบบการจับคู่แบบ grep ถ้าคุณไม่ยอมรับจำนวนเต็มลบ


2
Downvote เนื่องจากไม่มีคำอธิบาย มันทำงานอย่างไร มันดูซับซ้อนและเปราะบางและไม่ชัดเจนว่ามันจะยอมรับอะไร (ตัวอย่างเช่นการลบช่องว่างจำเป็นอย่างยิ่งเพราะอะไรมันจะพูดว่าตัวเลขที่มีช่องว่างที่ฝังอยู่นั้นเป็นตัวเลขที่ถูกต้องซึ่งอาจไม่เป็นที่ต้องการ)
tripleee

1

ทำสิ่งเดียวกันที่นี่ด้วยนิพจน์ทั่วไปที่ทดสอบส่วนทั้งหมดและส่วนทศนิยมหารด้วยจุด

re="^[0-9]*[.]{0,1}[0-9]*$"

if [[ $1 =~ $re ]] 
then
   echo "is numeric"
else
  echo "Naahh, not numeric"
fi

คุณช่วยอธิบายได้หรือไม่ว่าทำไมคำตอบของคุณจึงแตกต่างจากคำตอบเดิม ๆ เช่นคำตอบของ Charles Duffy ดีคำตอบของคุณถูกทำลายจริงเพราะมันตรวจสอบช่วงเวลาเดียว.
gniourf_gniourf

ไม่แน่ใจว่าจะเข้าใจช่วงเวลาเดียวที่นี่ ... มันเป็นช่วงเวลาหนึ่งหรือศูนย์ที่คาดหวัง .... แต่ไม่มีอะไรที่แตกต่างกันโดยพื้นฐานเพียงแค่พบว่า regex อ่านง่ายขึ้น
เจอโรม

การใช้ * ควรจับคู่กับกรณีในโลกแห่งความเป็นจริงมากขึ้น
เจอโรม

สิ่งที่คุณกำลังจับคู่สตริงที่ว่างเปล่าa=''และสตริงที่มีช่วงเวลาเท่านั้นa='.'เพื่อให้รหัสของคุณเสียเล็กน้อย ...
gniourf_gniourf

0

ฉันใช้สิ่งต่อไปนี้ (สำหรับจำนวนเต็ม):

## ##### constants
##
## __TRUE - true (0)
## __FALSE - false (1)
##
typeset -r __TRUE=0
typeset -r __FALSE=1

## --------------------------------------
## isNumber
## check if a value is an integer 
## usage: isNumber testValue 
## returns: ${__TRUE} - testValue is a number else not
##
function isNumber {
  typeset TESTVAR="$(echo "$1" | sed 's/[0-9]*//g' )"
  [ "${TESTVAR}"x = ""x ] && return ${__TRUE} || return ${__FALSE}
}

isNumber $1 
if [ $? -eq ${__TRUE} ] ; then
  print "is a number"
fi

เกือบจะถูกต้อง (คุณยอมรับสตริงที่ว่างเปล่า) แต่ซับซ้อนไปจนถึงจุดที่ทำให้งงงวย
Gilles 'หยุดชั่วร้าย'

2
ไม่ถูกต้อง: คุณยอมรับ-nฯลฯ (เพราะecho) และคุณยอมรับตัวแปรที่มีการขึ้นบรรทัดใหม่ (เนื่องจาก$(...)) และโดยวิธีการที่printไม่ได้เป็นคำสั่งเปลือกที่ถูกต้อง
gniourf_gniourf

0

ฉันลองสูตรของอัลตร้าซอว์เบลดเนื่องจากดูเหมือนว่าเป็นประโยชน์กับฉันมากที่สุดและไม่สามารถใช้งานได้ ในท้ายที่สุดฉันคิดวิธีอื่นแม้ว่าตามอื่น ๆ ในการทดแทนพารามิเตอร์เวลานี้ด้วยการเปลี่ยน regex:

[[ "${var//*([[:digit:]])}" ]]; && echo "$var is not numeric" || echo "$var is numeric"

มันจะลบทุกตัว: หลัก: อักขระคลาสใน $ var และตรวจสอบว่าเรามีสตริงว่างเปล่าหรือไม่ซึ่งหมายความว่าต้นฉบับเป็นเพียงตัวเลขเท่านั้น

สิ่งที่ฉันชอบเกี่ยวกับอันนี้คือขนาดเล็กและความยืดหยุ่น ในรูปแบบนี้ใช้งานได้เฉพาะกับฐานสิบจำนวนเต็ม 10 ตัวเท่านั้น แต่แน่นอนว่าคุณสามารถใช้การจับคู่รูปแบบเพื่อให้เหมาะกับความต้องการอื่น ๆ ได้


อ่านวิธีแก้ปัญหาของ mrucci มันก็เกือบจะเหมือนกับของฉัน แต่ใช้การแทนที่สตริงปกติแทนที่จะเป็น "รูปแบบที่ไม่ดี" ทั้งสองใช้กฎเดียวกันสำหรับการจับคู่รูปแบบและเป็น AFAIK โซลูชันที่ใช้แทนกันได้
ata

sedเป็น POSIX bashในขณะที่วิธีการแก้ปัญหาของคุณ ทั้งสองมีประโยชน์
v010dya

0

เร็ว & สกปรก: ฉันรู้ว่ามันไม่ใช่วิธีที่หรูหราที่สุด แต่ฉันมักจะเพิ่มศูนย์และทดสอบผลลัพธ์ ชอบมาก:

function isInteger {
  [ $(($1+0)) != 0 ] && echo "$1 is a number" || echo "$1 is not a number"
 }

x=1;      isInteger $x
x="1";    isInteger $x
x="joe";  isInteger $x
x=0x16 ;  isInteger $x
x=-32674; isInteger $x   

$ (($ 1 + 0)) จะส่งกลับ 0 หรือวางระเบิดถ้า $ 1 ไม่ใช่จำนวนเต็ม ตัวอย่างเช่น:

function zipIt  { # quick zip - unless the 1st parameter is a number
  ERROR="not a valid number. " 
  if [ $(($1+0)) != 0 ] ; then  # isInteger($1) 
      echo " backing up files changed in the last $1 days."
      OUT="zipIt-$1-day.tgz" 
      find . -mtime -$1 -type f -print0 | xargs -0 tar cvzf $OUT 
      return 1
  fi
    showError $ERROR
}

หมายเหตุ: ฉันคิดว่าฉันไม่เคยคิดที่จะตรวจสอบลอยหรือประเภทผสมที่จะทำให้ระเบิดสคริปต์ทั้งหมด ... ในกรณีของฉันฉันไม่ต้องการให้มันไปอีก ฉันจะเล่นกับวิธีแก้ปัญหาของ mrucci และ regex ของ Duffy - พวกเขาดูเหมือนแข็งแกร่งที่สุดใน bash framework ...


4
สิ่งนี้ยอมรับการแสดงออกทางคณิตศาสตร์เช่น1+1แต่ปฏิเสธจำนวนเต็มบวกบางตัวด้วยการนำหน้า0s (เพราะ08ค่าคงที่ฐานแปดไม่ถูกต้อง)
Gilles 'หยุดความชั่วร้าย'

2
นี้มีปัญหาอื่น ๆ เกินไป: ไม่เป็นตัวเลขและมันเป็นเรื่องที่ฉีดโค้ดลอง:0 isInteger 'a[$(ls)]'อ๊ะ
gniourf_gniourf

1
และการขยายตัวของ$((...))ไม่มีการอ้างอิงตัวเลขIFS=123จะเปลี่ยน
ไอแซค
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.