ตรวจสอบว่า $ REPLY อยู่ในช่วงตัวเลขหรือไม่


30

ฉันเขียนเชลล์สคริปต์สำหรับ Linux โดยใช้ Bash เพื่อแปลไฟล์วิดีโอใด ๆ ให้เป็น MP4 สำหรับสิ่งที่ฉันใช้avconvกับlibvorbisเสียง

ภายในสคริปต์ของฉันฉันมีคำถามสำหรับผู้ใช้:

read -p "- Audio Quality [scale from -2 to 10] ? "
    if [ -n "$REPLY" ] ; then
    ABITRATE="-aq $REPLY"
    fi

สตริง "ABITRATE" ของฉันเข้าสู่avconvบรรทัดคำสั่งสุดท้าย

แต่ฉันต้องการให้ผู้ใช้มีโอกาสตอบคำถามนั้นด้วยค่าเป็น Kb (Kilobit) และแปลเป็นมาตราส่วนที่libvorbisใช้ "สเกลจาก -2 ถึง 10" คือ:

Quality Kbit/s  Normalization
-----------------------------
 -2      ~32        y
 -1      ~48        y
  0      ~64        y
  1      ~80        y
  2      ~96        y
  3     ~112        y
  4     ~128        n
  5     ~160        n
  6     ~192        n
  7     ~224        n
  8     ~256        n
  9     ~320        n
 10     ~500        n

ฉันต้องการทราบวิธีตรวจสอบว่า $ REPLY ของฉันอยู่ในช่วงจำนวนหรือไม่ ตัวอย่างเช่นฉันต้องการให้สคริปต์ทำสิ่งนี้:

if [ $REPLY is a number between 1 and 32 ] ; then 
 REPLY="-2"
elif [ $REPLY is a number between 33 and 48 ] ; then 
 REPLY="-1"
fi

เป็นไปได้ (ฉันยินดีที่จะพูดว่า 'ใช่แน่นอนไม่ควรยาก' แต่ฉันไม่รู้จักไวยากรณ์ที่จะใช้)?


AFAIK, Vorbis ไม่ใช่ตัวแปลงสัญญาณเสียงที่ถูกต้องในไฟล์ MP4 (คุณต้องการใช้ AAC หรือ MP3 ที่เป็นไปได้) ...
evilsoup

ขอบคุณมันใช้งานได้ดีกับ VLC แต่ Totem ไม่ต้องการอ่าน ฉันเปลี่ยนมาเป็น libvo_aacenc
MrVaykadji

คำตอบ:


30

[builtin คำสั่ง / เปลือกมีการทดสอบเปรียบเทียบดังนั้นคุณก็สามารถทำได้

if [ "$REPLY" -ge 1 -a "$REPLY" -le 32 ]; then REPLY=-2;
elif [ "$REPLY" -ge 33 -a "$REPLY" -le 48 ]; then REPLY=-1; fi

โดยที่-geหมายถึงมากกว่าหรือเท่ากับ (และอื่น ๆ ) -aเป็นตรรกะ "และ" [คำสั่งเป็นเพียงคำสั่งไม่ไวยากรณ์พิเศษ (ก็จริงเช่นเดียวกับtest: ตรวจสอบman test) ดังนั้นจึงต้องการพื้นที่หลังจากที่มัน หากคุณเขียน[$REPLYมันจะพยายามค้นหาคำสั่งที่มีชื่อ[$REPLYและเรียกใช้งานซึ่งจะไม่ทำงาน ]เดียวกันจะไปปิด

แก้ไข: เพื่อทดสอบว่าหมายเลขนั้นเป็นจำนวนเต็มหรือไม่ (หากสามารถเกิดขึ้นได้ในรหัสของคุณ) ให้ทำการทดสอบก่อน

if [[ "$REPLY" =~ ^[0-9]+$ ]]; then
   existing code
else echo "$REPLY is not an integer" >&2 && exit 1; fi

แน่นอนว่านิพจน์วงเล็บเหลี่ยมทั้งหมดนี้คืนค่า 0 (จริง) หรือ 1 (เท็จ) และสามารถรวมกันได้ ไม่เพียง แต่คุณสามารถใส่ทุกอย่างไว้ในวงเล็บเดียวกันคุณยังสามารถทำได้

if [[ "$REPLY" =~ ^[0-9]+$ ]] && [ "$REPLY" -ge 1 -a "$REPLY" -le 32 ]; then ...

หรือสิ่งที่คล้ายกัน


สิ่งที่ฉันกำลังมองหาขอขอบคุณ! ฉันสามารถใช้นิพจน์เปรียบเทียบแบบง่าย ๆ แทนได้>=ไหม?
MrVaykadji

Bash อนุญาตให้ทดสอบได้หลายประเภท คุณมีแบบดั้งเดิมเหล่านี้วงเล็บที่ทำงานเท่าที่เห็นใน[ man testเหล่านี้เป็นแบบดั้งเดิมและพิสูจน์คนโง่ จากนั้นคุณมีจำนวนมากในตัวทุบตี คุณมี[[สิ่งที่คล้ายกัน แต่ไม่เหมือนกันทุกประการเนื่องจากชื่อนี้ไม่ได้ขยายชื่อพา ธ (นั่นคือ <=> หมายถึงการเปรียบเทียบสตริงและการเปรียบเทียบจำนวนเต็มเหมือนกับใน[) ทั้งสองยังมีการทดสอบมากมายสำหรับการมีอยู่ของไฟล์การอนุญาตและอื่น ๆ จากนั้นคุณมีคำตอบเดี่ยว(และคู่ที่((ใช้ในคำตอบของ @ devnull ตรวจสอบภายใต้man bash Compound Commands
orion

1
@MrVaykadji ฉันขอแนะนำให้คุณทดสอบด้วยว่าตัวแปรเป็นตัวเลขหรือไม่คุณอาจได้รับผลลัพธ์ที่ไม่คาดคิด:foo='a'; [[ "$foo" -lt 32 ]] && echo yes
terdon

12

คุณสามารถพูดได้ว่า:

((REPLY>=1 && REPLY<=32)) && REPLY=-2
((REPLY>=33 && REPLY<=48)) && REPLY=-1

ข้อความจากคู่มือ :

((...))

(( expression ))

การแสดงออกทางคณิตศาสตร์ได้รับการประเมินตามกฎที่อธิบายไว้ด้านล่าง (ดูเชลล์คณิตศาสตร์ ) หากค่าของนิพจน์ไม่ใช่ศูนย์สถานะการส่งคืนจะเป็น 0 มิฉะนั้นสถานะการส่งคืนคือ 1 ซึ่งเทียบเท่ากับ

let "expression"

ฉันชอบความเรียบง่าย แต่อะไรคือ((อะไร ฉันพยายามใช้มันอย่างรวดเร็วและดูเหมือนว่าจะทำงานได้if [ ] ; thenแต่ฉันไม่รู้ว่ามีอยู่จริง
MrVaykadji

@MrVaykadji เพิ่มการอ้างอิงจากคู่มือ แจ้งให้เราทราบหากยังไม่ชัดเจน
devnull

1
@MrVaykadji นอกจากนี้ยังบอกว่าจะเทียบเท่ากับการพูดif [ condition ]; then foo; fi condition && foo
devnull

ตกลงดี! ฉันต้องการยอมรับทั้ง aswers ของคุณ (Orion และคุณ) ถ้าฉันทำได้ ขอบคุณมากสำหรับสิ่งนี้ฉันได้เรียนรู้มากมาย
MrVaykadji

คุณอาจต้องการตัดค่าศูนย์นำหน้าถ้าคุณใช้สิ่งนี้ a=08; (( a > 1 ))จะเกิดข้อผิดพลาดตั้งแต่ 08 ถือเป็นฐานแปด 10#$REPLYคุณยังสามารถบังคับกับทศนิยม cmd && cmdไม่เหมือนกันif cmd; then ...เมื่อคุณต้องการชิ้นelseส่วนการโยงตรรกะ&&และ||อาจทำให้เกิดข้อบกพร่องที่ลึกซึ้ง
llua

4

คุณสามารถทำสิ่งนี้:

#!/usr/bin/env bash
read -p "- Audio Quality [scale from -2 to 10] ? "
if [ -n "$REPLY" ] ; then
    ABITRATE="-aq $REPLY"
fi

echo "You chose : $ABITRATE : $REPLY"
## If 0 < $REPLY < 33 and $REPLY is a number
if [[ "$REPLY" -gt 0 && "$REPLY" -lt 33 && "$REPLY" =~ '^[0-9]$' ]]
then
    echo "GOOD"
else
    echo "BAD"
fi

2

ก่อนอื่นให้ทดสอบว่าอินพุตเป็นตัวเลขหรือไม่ ตัวอย่างเช่นการใช้ตัวดำเนินการจับคู่นิพจน์ทั่วไปของนิพจน์เงื่อนไข bash :

if [[ $REPLY =~ -?[0-9]+ ]]; then
  echo "Invalid input (not numeric): $REPLY"
  exit 2
fi

ในการทดสอบช่วงตัวเลขคุณมีความเป็นไปได้สองอย่าง:

  • ตัว-gtดำเนินการของนิพจน์เงื่อนไขภายใน[ … ]หรือ[[ … ]](ระวังว่า<และ>ตัวดำเนินการเปรียบเทียบสตริงไม่ใช่การเปรียบเทียบค่าตัวเลขดังนั้นจึง[[ 10 < 9 ]]เป็นจริง)
  • ปกติทางคณิตศาสตร์((…))ประกอบการภายใน

ดังนั้น:

if ((REPLY >= -2 && REPLY <= 10)); then
  : # do nothing -- pass directly to libvorbis
elif ((REPLY <= 24)); then
  echo "Value outside supported range: $REPLY"
  exit 2
elif ((REPLY <= 135)); then
  REPLY=$(((REPLY+8) / 16 - 4))
elif ((REPLY <= 271)); then
  REPLY=$(((REPLY+16) / 32))
elif ((REPLY <= 400)); then
  REPLY=9
elif ((REPLY <= 707)); then
  REPLY=10
else
  echo "Value outside supported range: $REPLY"
  exit 2
fi

(คุณอาจต้องการใช้กฎการประมาณที่แตกต่างกันฉันไม่รู้ว่าสิ่งที่ฉันเลือกนั้นดีที่สุดหรือไม่)


1

ในการตรวจสอบอย่างถูกต้องว่าสตริงเป็นตัวเลข (ทศนิยม) อันดับแรกเราต้องกำหนดว่าอะไรคือจำนวนเต็มทศนิยม คำจำกัดความที่เรียบง่ายและค่อนข้างสมบูรณ์คือ:

ลำดับของเครื่องหมายทางเลือก (+ หรือ -) ตามด้วยตัวเลขทศนิยมไม่เกิน 18 (นัยสำคัญ)

และขั้นตอนนี้มีความจำเป็น:

  1. ลบอักขระทั้งหมดที่ไม่ใช่ตัวเลขทศนิยม (หลังเครื่องหมาย)
  2. ลบศูนย์นำหน้าทางเลือกทั้งหมด เลขศูนย์นำหน้าจะทำให้เชลล์เชื่อว่าตัวเลขอยู่ในรูปฐานแปด
  3. จำกัด ขนาดสูงสุดของจำนวนเต็มเป็น 18 หลัก ต่ำกว่า 2 ** 63-1 (จำนวนเต็มสูงสุด 64 บิต)

regex เพียงอันเดียวจะทำสิ่งต่อไปนี้ให้มากที่สุด:

re='^([+-])?0*([0-9]{1,18})$'
[[ $number =~ $re ]] && integer=${BASH_REMATCH[*]:1}

รหัสในการประมวลผลตัวเลขหลายตัวคือ:

#!/bin/bash
DebugLevel=4     # 1:fatal 2:error 3:warn 4:info 5:debug 6:trace

SayMsg    (){   local a; a=$1; shift ;            # Log level
                [[ $a -le $DebugLevel ]] && printf '%s' "$@" $'\n' >&2 ;
            }
SayError  (){   a=$1; shift; printf '%s' "$@" $'\n' >&2; exit   "$a";   }

parseint  (){   local re # Parse the first argument as an integer or fail
                re='^([+-])?0*([0-9]{1,18})$'
                [[ $1 =~ $re ]] || { SayMsg 4 "Invalid number $1"; return 2; }
                integer=${BASH_REMATCH[1]}${BASH_REMATCH[2]}
                echo "integer=$integer"
             }

while read val; do
    parseint "$val"
    done <<-\_EOT_
    0
    1
    10
    100
    2345
    123456789012345678
    923456789012345678
    999999999999999999
    0000000012345
    +023
    -00045
    -76
    ""
    ''
    a
    abc
    1234567890123456789
    7.23
    -8.17
    1e3
    10+11
    _EOT_

สิ่งที่จะพิมพ์:

integer=0
integer=1
integer=10
integer=100
integer=2345
integer=123456789012345678
integer=923456789012345678
integer=999999999999999999
integer=12345
integer=+23
integer=-45
integer=-76
Invalid number ""
Invalid number ''
Invalid number 
Invalid number a
Invalid number abc
Invalid number 1234567890123456789
Invalid number 7.23
Invalid number -8.17
Invalid number 1e3
Invalid number 10+11

หลังจากที่จำนวนนั้นสะอาดและชัดเจนการทดสอบที่ขาดหายไปเพียงอย่างเดียวคือการ จำกัด ช่วงของค่า เส้นคู่เรียบง่ายนี้จะทำเช่นนั้น:

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