ทดสอบว่ามีการตั้งค่าตัวแปรหลายตัว


19

ฉันต้องการตรวจสอบให้แน่ใจว่า ณ จุดหนึ่งของสคริปต์หลังจากsourceการตั้งค่าไฟล์ตัวแปรหลายตัวจะถูกตั้งค่าและหากไม่ใช่เพื่อหยุดการประมวลผลให้บอกผู้ใช้เกี่ยวกับตัวแปรที่หายไป ฉันเหนื่อย

for var in $one $two $three ; do
    ...

แต่ถ้ายกตัวอย่างเช่นไม่ได้ตั้งวงไม่เคยประหารชีวิต$two $twoสิ่งต่อไปที่ฉันลองคือ

for var in one two three ; do
    if [ -n ${!var} ] ; then
        echo "$var is set to ${!var}"
    else
        echo "$var is not set"
    fi
done

แต่ถ้าทั้งสองไม่ได้ตั้งค่าฉันยังคงได้รับ "two ถูกตั้งค่าเป็น" แทนที่จะเป็น "two is not set"

ฉันจะแน่ใจได้อย่างไรว่าตัวแปรที่จำเป็นทั้งหมดถูกตั้งค่า

Update / Solution:ฉันรู้ว่ามีความแตกต่างระหว่าง "set" และ "set, but empty" ตอนนี้ฉันใช้ (ขอบคุณ/programming//a/16753536/3456281และคำตอบสำหรับคำถามนี้) ต่อไปนี้:

if [ -n "${!var:-}" ] ; then

ดังนั้นหากvarตั้งค่าไว้ แต่ว่างเปล่าก็ถือว่ายังไม่ถูกต้อง


1
นอกจากนี้คุณยังสามารถเพิ่มset -uไปยังจุดเริ่มต้นของสคริปต์เพื่อยกเลิกทันทีเมื่อใช้ตัวแปร unset
n.st

คำตอบ:


8

ข้อผิดพลาดในการอ้างถึง

if [ -n "${!var}" ] ; then

สำหรับอนาคต: การตั้งค่า

set -x

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

bash -vx ./my/script.sh

ใช้งานได้ แต่จะเกิดอะไรขึ้นกับ / โดยที่ไม่มีราคา? วิธีการทั่วไปของฉันถูกต้องตั้งแต่แรกไหม?
Jasper

ใช่นั่นคือ precognitive: ฉันตอบคำถามก่อนที่จะถูกถาม ... 8-)
Hauke ​​Laging

4
@Jasper คุณควรเสมอพูดตัวแปร ที่ไม่เสียเวลามากขึ้นกว่าที่คิดว่าจำเป็นหรือไม่ทุกครั้ง แต่หลีกเลี่ยงข้อผิดพลาด
Hauke ​​Laging

ในทางตรงกันข้ามการอ้างอิงเพียงป้องกันการขยายตัว หากการขยายตัวเป็นสิ่งที่คุณสนใจการอ้างอิงนั้นไม่ใช่หนทางที่แน่นอน ตัวอย่างเช่น: cs = 673,290,765,; set - $ (IFS =,; echo $ cs); echo $ #; ผลลัพธ์: 3
mikeserv

2
@mikeserv เพียงเครื่องหมายคำพูดเดียวป้องกันการขยายสิ่งที่เห็นได้ชัดว่าฉันไม่แนะนำ เครื่องหมายคำพูดคู่ป้องกันการแยกคำ ฉันไม่ได้ปฏิเสธว่าอาจมีบางกรณีที่ต้องตัดการอ้างอิง แต่นั่นไม่ใช่ข้อโต้แย้งกับกฎทั่วไปของฉัน คนแบบไหนที่จะใช้สิ่งที่ชอบset -- $(IFS=, ; echo $cs)? คนประเภทไหนที่ต้องถามที่นี่ว่าทำไมถึงif [ -n ${!var} ] ; thenไม่ได้ผล? อาจจะไม่.
Hauke ​​Laging

5

สิ่งเดียวที่คุณต้องการคือคำพูดในการทดสอบของคุณ:

for var in one two three ; do
    if [ -n "${!var}" ] ; then
        echo "$var is set to ${!var}"
    else
        echo "$var is not set"
    fi
done

ใช้งานได้สำหรับฉัน


4

หากคุณต้องการให้โปรแกรมหยุดทำงาน:

N= 
${one?var 1 is unset} 
${two:?var 2 is unset or null}
${three:+${N:?var 3 is set and not null}}

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

( for v in "$one" "$two" "$three" ; do
    i=$((i+1)) ; : ${v:?var $i is unset or null...} 
done ) || _handle_it

ฉันมีจำนวนมากขึ้นที่จะพูดเกี่ยวกับเรื่องนี้ที่นี่


2

คุณสามารถเพิ่ม

set -u

ไปที่จุดเริ่มต้นของสคริปต์เพื่อให้ยุติเมื่อพยายามใช้ตัวแปรที่ไม่ได้ตั้งค่า

สคริปต์เช่น

#!/bin/sh
set -u
echo $foo

จะส่งผลให้

script.sh: 3: script.sh: foo: parameter ไม่ได้ตั้งค่า

หากคุณใช้bashแทนข้อผิดพลาดจะมีลักษณะดังนี้:

script.sh: บรรทัด 3: foo: ตัวแปรที่ไม่ได้ผูกไว้


และในความคิดของคุณสิ่งนี้ทำให้รู้สึกว่าสำหรับการตรวจสอบเวลาทำงาน?
Hauke ​​Laging

2
@HaukeLaging ฉันไม่ได้ติดตามคุณมากนัก - set -uป้องกันไม่ให้เกิดข้อผิดพลาดประเภทที่ OP พยายามหลีกเลี่ยงและ (ตรงกันข้ามกับโซลูชันอื่น ๆ ทั้งหมด) ไม่ได้ จำกัด เฉพาะตัวแปรชุดใดชุดหนึ่ง ในความเป็นจริงข้อควรระวังที่มีประโยชน์สำหรับเชลล์สคริปต์เกือบทั้งหมดจะทำให้มันล้มเหลวอย่างปลอดภัยแทนที่จะทำสิ่งที่ไม่คาดคิดเมื่อตัวแปรไม่ได้ตั้งค่า บทความนี้เป็นการอ้างอิงที่มีประโยชน์ในหัวข้อ
n.st

@ n.st ฉันไม่เห็นด้วย - ค่า null อาจมีประโยชน์เท่ากับค่าที่ไม่เป็นค่าว่างหากคุณวางแผน
mikeserv

1
@ n.st สิ่งนี้ไม่สมเหตุสมผลสำหรับ OP เนื่องจากเขาไม่ต้องการการป้องกันการเข้าถึงตัวแปรที่ไม่ได้ตั้งค่า (BTW: ชุด แต่ตัวแปรว่างจะทำให้เกิดข้อผิดพลาดเดียวกัน แต่ไม่ตอบสนองต่อ "การป้องกัน" ของคุณ) เขาต้องการเวลาตรวจสอบว่าตัวแปรไม่มีการตั้งค่า / ว่างเปล่า ข้อเสนอแนะของคุณอาจมีประโยชน์ในฐานะที่เป็นความช่วยเหลือในการพัฒนาทั่วไป แต่ไม่สามารถแก้ไขปัญหา OP
Hauke ​​Laging

set -uฉันสามารถใช้เช่นกันฉันคิดว่า แต่มันไม่ยืดหยุ่นเท่ากับการขยายพารามิเตอร์ที่เหมาะสม (ดูคำถามที่อัปเดตของฉัน) และด้วยการที่set -uฉันจะต้องทำการจำลองแบบเข้าถึงตัวแปรเป้าหมายทั้งหมดหากฉันต้องการตรวจสอบสถานะของพวกเขาในที่เดียว
Jasper

2

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

#!/bin/bash

required_vars=(one two three)

missing_vars=()
for i in "${required_vars[@]}"
do
    test -n "${!i:+y}" || missing_vars+=("$i")
done
if [ ${#missing_vars[@]} -ne 0 ]
then
    echo "The following variables are not set, but should be:" >&2
    printf ' %q\n' "${missing_vars[@]}" >&2
    exit 1
fi

ฉันใช้ตัวแปรอาร์เรย์เพื่อติดตามว่าตัวแปรใดที่ไม่ได้ตั้งค่าไว้และใช้ผลลัพธ์เพื่อเขียนข้อความที่ผู้ใช้พบเจอ

หมายเหตุ:

  • ฉันยกมา${required_vars[@]}ในforวงส่วนใหญ่ออกจากนิสัย - ฉันจะไม่แนะนำให้รวม metacharacters เชลล์ในชื่อตัวแปรของคุณ!
  • ฉันไม่ได้พูด${#missing_vars[@]}เพราะมันเป็นจำนวนเต็มเสมอแม้ว่าคุณจะผิดปกติพอที่จะเพิกเฉยต่อคำแนะนำก่อนหน้านี้
  • ฉันใช้%qเมื่อพิมพ์ %sโดยปกติจะเพียงพอ
  • เอาต์พุตข้อผิดพลาดจะไปที่สตรีมข้อผิดพลาดเสมอ>&2ดังนั้นจึงไม่ได้รับการไพพ์ไปยังคำสั่งดาวน์สตรีม
  • ความเงียบเป็นสีทอง - อย่าพิมพ์ข้อมูลความคืบหน้าหรือข้อมูลการดีบั๊กเว้นแต่ได้รับการร้องขอเป็นพิเศษ ทำให้ข้อผิดพลาดชัดเจนขึ้น

1

bash4.2 ให้คุณทดสอบว่าตัวแปรถูกตั้งค่าด้วย-vโอเปอเรเตอร์หรือไม่ ตัวแปร unset และชุดตัวแปรเป็นสตริงว่างมีเงื่อนไขที่แตกต่างกันสองประการ:

$ unset foo
$ [[ -v foo ]] && echo foo is set
$ [[ -z "$foo" ]] && echo foo is empty
foo is empty
$ foo=
$ [[ -v foo ]] && echo foo is set
foo is set
$ [[ -z "$foo" ]] && echo foo is empty
foo is empty

ฉันถูกถามเกี่ยวกับ "หลาย" ตัวแปรดังนั้นฉันกำลังมองหาสิ่งที่ต้องการ ... ร้าย
แจสเปอร์

คุณไม่จำเป็นต้องร้ายตั้งแต่-vเตะชื่อของตัวแปรเพื่อfor var in one two three; [[ -v $var ]] && ...; doneจะตรวจสอบว่าแต่ละone, twoและthreeมีการตั้งค่าในลำดับ
chepner

1

ฉันคิดว่าถ้าคุณหมายความว่าnot setดังนั้นตัวแปรจะต้องไม่เริ่มต้น ถ้าคุณใช้[ -n "${!var}" ]ดังนั้นตัวแปรที่ว่างเปล่าเหมือนtwo=""จะล้มเหลวขณะที่มันเป็นชุด คุณสามารถลองสิ่งนี้:

one=1
three=3

for var in one two three; do
  declare -p $var > /dev/null 2>&1 \
  && printf '%s is set to %s\n' "$var" "${!var}" \
  || printf '%s is not set\n' "$var"
done

0

วิธีแก้ปัญหาที่กระชับ (แม้ว่าจะแฮ็กเล็กน้อย) เพื่อจัดการกรณีที่มันตกลงสำหรับsourceสคริปต์ d เพื่อตั้งค่าตัวแปรให้เป็นค่า Null คือการเริ่มต้นตัวแปรเป็นค่าพิเศษก่อนที่จะจัดหาสคริปต์แล้วตรวจสอบค่านั้นหลังจากนั้น , เช่น

one=#UNSET#
two=#UNSET#
three=#UNSET#

. set_vars_script

if [[ $one$two$three == *#UNSET#* ]] ; then
  echo "One or more essential variables are unset" >&2
  exit 1
fi

1
อีกวิธีที่ดี แต่ฉันต้องการให้ข้อความแสดงข้อผิดพลาดเฉพาะเจาะจงมากกว่านั้น "ตัวแปรหนึ่งตัวหรือมากกว่านั้นไม่ได้ตั้งค่า" ...
Jasper

0

ฉันได้ขยายคำตอบของ @ mikeservแล้ว

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

REQD_VALUES=("VARIABLE" "FOO" "BAR" "OTHER_VARIABLE")
( i=0; for var_name in ${REQD_VALUES[@]}; do
    VALUE=${!var_name} ;
    i=$((i+1)) ; : ${VALUE:?$var_name is missing}
done ) || usage

ตัวอย่างเอาต์พุตเมื่อไม่มีค่า:

./myscript.sh: line 42: VALUE: OTHER_VARIABLE is missing

โปรดทราบว่าคุณสามารถเปลี่ยนตัวแปรที่เรียกว่า VALUE เป็นชื่อทางเลือกที่เหมาะสมกับผลลัพธ์ที่คุณต้องการได้ดีขึ้น

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