วิธีการเขียนสคริปต์ที่ทำงานโดยมีหรือไม่มีข้อโต้แย้ง?


13

ฉันมีสคริปต์ทุบตีซึ่งไปเช่นนี้

#!/bin/bash

if [ $1 = "--test" ] || [ $1 = "-t" ]; then
    echo "Testing..."
    testing="y"

else
    testing="n"
    echo "Not testing."

fi

ดังนั้นสิ่งที่ฉันต้องการที่จะสามารถทำได้ไม่เพียง แต่เรียกใช้ด้วย./script --testหรือ./script -tแต่ยังไม่มีข้อโต้แย้ง (เพียง./script) แต่ดูเหมือนว่าถ้าฉันทำกับรหัสปัจจุบันผลลัพธ์เป็นเพียง:

./script: line 3: [: =: unary operator expected
./script: line 3: [: =: unary operator expected
Not testing.

ดังนั้นฉันจะตั้งโปรแกรมเพื่อให้ทำงานโดยไม่มีข้อโต้แย้งทั้งหมดจะทำelseโดยไม่โยนข้อผิดพลาด? ผมทำอะไรผิดหรือเปล่า?


1
คุณต้องใช้เครื่องหมายสองตัวในการตรวจสอบ [[]]
Terrance

3
ดูBash
pitfall

@Terrance คุณไม่ต้องการใช้แม้ว่าคุณควรใช้มันหากคุณต้องการทุบตีเป้าหมาย
Ruslan

คำตอบ:


1

ว่า "วิธีการที่เหมาะสม" ของการใช้ตัวเลือกยาวในเปลือกสคริปต์ผ่านทางยูทิลิตี้ getopt GNU นอกจากนี้ยังมีgetopts ที่เป็น bash ในตัวแต่อนุญาตให้ใช้ตัวเลือกสั้น ๆ เช่น-tเท่านั้น เป็นตัวอย่างของgetoptการใช้งานที่สามารถพบได้ที่นี่

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

#!/bin/bash

# GNU getopt allows long options. Letters in -o correspond to
# comma-separated list given in --long.

opts=$(getopt -o t --long test -- "$*")
test $? -ne 0 && exit 2 # error happened

set -- $opts # some would prefer using eval set -- "$opts"
# if theres -- as first argument, the script is called without
# option flags
if [ "$1" = "--"  ]; then
    echo "Not testing"
    testing="n"
    # Here we exit, and avoid ever getting to argument parsing loop
    # A more practical case would be to call a function here
    # that performs for no options case
    exit 1
fi

# Although this question asks only for one 
# option flag, the proper use of getopt is with while loop
# That's why it's done so - to show proper form.
while true; do
    case "$1" in
        # spaces are important in the case definition
        -t | --test ) testing="y"; echo "Testing" ;;
    esac
    # Loop will terminate if there's no more  
    # positional parameters to shift
    shift  || break
done

echo "Out of loop"

ด้วยความเรียบง่ายเพียงเล็กน้อยและลบความคิดเห็นสิ่งนี้สามารถย่อให้:

#!/bin/bash
opts=$(getopt -o t --long test -- "$*")
test $? -ne 0 && exit 2 # error happened
set -- $opts    
case "$1" in
    -t | --test ) testing="y"; echo "Testing";;
    --) testing="n"; echo "Not testing";;
esac

16

มีหลายวิธี; สองสิ่งที่ชัดเจนที่สุดคือ:

  • ใส่$1เครื่องหมายคำพูดคู่:if [ "$1" = "--test" ]
  • ตรวจสอบจำนวนข้อโต้แย้งโดยใช้ $ #

ดีกว่ายังใช้ getopts


2
+1 สำหรับการใช้ getopts นั่นเป็นวิธีที่ดีที่สุดที่จะไป
Seth

3
สำนวนทั่วไปอีกอย่างหนึ่งคือ[ "x$1" = "x--test" ]การปกป้องอาร์กิวเมนต์บรรทัดคำสั่งอีกครั้งซึ่งเป็นตัวดำเนินการที่ถูกต้องสำหรับ[คำสั่ง (นี่เป็นอีกเหตุผลหนึ่งที่ทำให้เหตุผล[[ ]]นี้เป็นที่ต้องการ: เป็นส่วนหนึ่งของไวยากรณ์เชลล์ไม่ใช่เพียงคำสั่ง builtin)
Peter Cordes

7

คุณต้องพูดตัวแปรของคุณภายในเงื่อนไข if แทนที่:

if [ $1 = "--test" ] || [ $1 = "-t" ]; then

ด้วย:

if [ "$1" = '--test' ] || [ "$1" = '-t' ]; then  

จากนั้นมันจะทำงาน:

➜ ~ ./test.sh
ไม่ได้ทำการทดสอบ

เพิ่มตัวแปรของคุณเสมอ!


การอ้างคำพูดเดียวเป็นสิ่งจำเป็นหรือสามารถใช้การอ้างคำพูดคู่แทนได้หรือไม่?

คำตอบที่แม่นยำขึ้นอยู่กับว่าคุณใช้เชลล์ตัวไหน แต่จากคำถามของคุณฉันคิดว่าคุณกำลังใช้ลูกหลานของ Bourne-shell ความแตกต่างที่สำคัญระหว่างคำพูดเดี่ยวและคู่คือการขยายตัวของตัวแปรที่เกิดขึ้นในเครื่องหมายคำพูดคู่ แต่ไม่อยู่ในเครื่องหมายคำพูดเดี่ยว: $ FOO = บาร์ $ echo "$ FOO" บาร์ $ echo '$ FOO' $ FOO (hmm .... can ' รหัสรูปแบบ t ในความคิดเห็น ... )
JayEye

@ParanoidPanda คุณไม่จำเป็นต้องใช้เครื่องหมายคำพูดเดี่ยว ๆ เลย แต่เป็นวิธีที่ดีที่จะใช้มันกับตัวอักษรสตริง ด้วยวิธีนี้คุณจะไม่แปลกใจในภายหลังหากข้อมูลสตริงของคุณมีสัญลักษณ์ทุบตีต้องการขยาย
เซท

@ParanoidPanda What @JayEye พูดว่า: ด้วยเครื่องหมายอัญประกาศเดี่ยวไม่มีการขยายการfoo=bar echo '$foo'พิมพ์ตัวแปร$fooไปยังผลลัพธ์ แต่foo=bar echo "$foo"พิมพ์barแทน
EKons

4

คุณกำลังทุบตี Bash เป็นทางเลือกที่ยอดเยี่ยมสำหรับ[: [[. ด้วย[[คุณไม่ต้องกังวลว่าจะเกี่ยวกับข้อความและคุณได้รับผู้ประกอบการมากขึ้นกว่า[รวมถึงการสนับสนุนที่เหมาะสมสำหรับ||:

if [[ $1 = "--test" || $1 = "-t" ]]
then
    echo "Testing..."
    testing="y"
else
    testing="n"
    echo "Not testing."
fi

หรือคุณสามารถใช้นิพจน์ทั่วไป:

if [[ $1 =~ ^(--test|-t) ]]
then
    echo "Testing..."
    testing="y"
else
    testing="n"
    echo "Not testing."
fi

แน่นอนการอ้างอิงที่เหมาะสมควรทำเสมอ แต่ไม่มีเหตุผลที่จะต้อง[ใช้เมื่อใช้ bash


3

เพื่อทดสอบว่ามีข้อโต้แย้ง (โดยทั่วไปและดำเนินการตามนั้น) สิ่งนี้ควรใช้งานได้:

#!/bin/bash

check=$1

if [ -z "$check" ]; then
  echo "no arguments"
else echo "there was an argument"
fi

ทำไมไม่[ -z "$1" ]?
EKons

@ ΈρικΚωνσταντόπουλοςในกรณีนี้แน่นอนว่าตามปกติในสคริปต์ฉันเรียกตัวแปรครั้งเดียวอ้างอิงในขั้นตอนซึ่งมักจะมากกว่าหนึ่งครั้ง ตัดสินใจที่จะเก็บโปรโตคอลในตัวอย่าง
Jacob Vlijm

ในตัวอย่างนี้คุณดูเหมือนจะใช้$checkเพียงครั้งเดียวก่อนหน้าใด ๆshiftดังนั้นจึงควรใช้$1แทน
EKons

@ ΈρικΚωνσταντόπουλοςไม่แน่นอนอย่างที่กล่าวไว้มันเป็นตัวอย่างที่จะใช้ในสคริปต์ ในสคริปต์มันเป็นวิธีปฏิบัติที่ไม่ดีในการทำซ้ำซ้ำแล้วซ้ำอีก
Jacob Vlijm

ฉันเข้าใจว่าคุณหมายถึงอะไร แต่ควรฝึกฝนอย่าใช้ shebangs ใน#!การแสดงตัวอย่าง (คุณควรอธิบายเชลล์นอกโค้ด)
EKons
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.