จะบอกได้อย่างไรว่าสตริงไม่ได้ถูกกำหนดไว้ในสคริปต์เชลล์ Bash


151

ถ้าฉันต้องการตรวจสอบสตริงว่างฉันจะทำ

[ -z $mystr ]

แต่ถ้าฉันต้องการตรวจสอบว่าตัวแปรได้ถูกกำหนดเลย หรือไม่มีความแตกต่างในการเขียนสคริปต์ Bash?


27
โปรดอยู่ในนิสัยการใช้ [-z "$ mystr"] ซึ่งตรงข้ามกับ [-z $ mystr]
Charles Duffy

วิธีการทำสิ่งที่ตรงกันข้าม? ฉันหมายถึงเมื่อสตริงไม่เป็นโมฆะ
เปิดทางที่

1
@flow: แล้ว[ -n "${VAR+x}"] && echo not null
Lekensteyn

1
@CharlesDuffy ฉันเคยอ่านมาแล้วในแหล่งข้อมูลออนไลน์มากมาย ทำไมถึงชอบ
ffledgling

6
@Ayos เพราะถ้าคุณไม่มีเครื่องหมายอัญประกาศเนื้อหาของตัวแปรคือการแยกสตริงและกลม ถ้ามันเป็นสตริงว่างมันจะกลายเป็น[ -z ]แทน[ -z "" ]; ถ้ามันมีช่องว่างมันจะกลายเป็น[ -z "my" "test" ]แทน[ -z my test ]; และถ้าเป็น[ -z * ]เช่นนั้น*จะถูกแทนที่ด้วยชื่อของไฟล์ในไดเรกทอรีของคุณ
Charles Duffy

คำตอบ:


138

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

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "$VAR" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

คุณอาจรวมการทดสอบสองรายการในบรรทัดที่สองเข้าด้วยกันกับ:

if [ -z "$VAR" -a "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

แต่ถ้าคุณอ่านเอกสารสำหรับ Autoconf คุณจะพบว่าพวกเขาไม่แนะนำให้รวมข้อตกลงกับ ' -a' &&และไม่แนะนำให้ใช้การทดสอบอย่างง่ายแยกต่างหากรวมกับ ฉันไม่พบระบบที่มีปัญหา นั่นไม่ได้หมายความว่าพวกเขาไม่เคยมีอยู่ (แต่พวกเขาอาจจะหายากมากในวันนี้แม้ว่าพวกเขาจะไม่ได้หายากในอดีตอันไกลโพ้น)

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


ฉันเพิ่งถูกถามทางอีเมลเกี่ยวกับคำตอบนี้ด้วยคำถาม:

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

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi

สิ่งนี้จะไม่ประสบความสำเร็จหรือไม่

if [ -z "${VAR}" ]; then echo VAR is not set at all; fi

คำถามที่เป็นธรรม - คำตอบคือ 'ไม่ทางเลือกที่ง่ายกว่าของคุณไม่ทำแบบเดียวกัน'

สมมติว่าฉันเขียนสิ่งนี้ก่อนการทดสอบของคุณ:

VAR=

การทดสอบของคุณจะบอกว่า "VAR ไม่ได้ตั้งค่าเลย" แต่ฉันจะพูด (โดยนัยเพราะไม่มีอะไรเกิดขึ้น) "VAR ถูกตั้งค่าไว้ แต่ค่าของมันอาจว่างเปล่า" ลองใช้สคริปต์นี้:

(
unset VAR
if [ -z "${VAR+xxx}" ]; then echo JL:1 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:1 VAR is not set at all; fi
VAR=
if [ -z "${VAR+xxx}" ]; then echo JL:2 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:2 VAR is not set at all; fi
)

ผลลัพธ์คือ:

JL:1 VAR is not set at all
MP:1 VAR is not set at all
MP:2 VAR is not set at all

ในการทดสอบคู่ที่สองตัวแปรถูกตั้งค่า แต่ถูกตั้งค่าเป็นค่าว่าง นี่คือความแตกต่างที่${VAR=value}และ${VAR:=value}สัญลักษณ์ทำ เหมือนกันสำหรับ${VAR-value}และ${VAR:-value}และ${VAR+value}และ${VAR:+value}และอื่น ๆ


ในฐานะที่เป็นGiliชี้ให้เห็นในของเขาคำตอบถ้าคุณทำงานbashกับตัวเลือกแล้วคำตอบที่พื้นฐานข้างต้นล้มเหลวด้วยset -o nounset unbound variableแก้ไขได้ง่าย:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "${VAR-}" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

หรือคุณสามารถยกเลิกset -o nounsetตัวเลือกด้วยset +u( set -uเทียบเท่าset -o nounset)


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

3
สำหรับผู้ที่ต้องการค้นหาคำอธิบายของความหมายข้างต้นในหน้า bash man ให้ค้นหาส่วน "การขยายพารามิเตอร์" และจากนั้นสำหรับข้อความนี้: "เมื่อไม่ทำการขยายสตริงย่อยโดยใช้แบบฟอร์มเอกสารด้านล่างทดสอบ bash สำหรับ พารามิเตอร์ที่ไม่ได้ตั้งค่าหรือเป็นโมฆะ ['null' หมายถึงสตริงที่ว่างเปล่า] การไม่ใส่โคลอนจะทำให้เกิดการทดสอบเฉพาะสำหรับพารามิเตอร์ที่ไม่ได้ตั้งค่า (... ) $ {พารามิเตอร์: + คำ}: ใช้ค่าทางเลือกหากพารามิเตอร์ เป็นโมฆะหรือ unset ไม่มีอะไรถูกแทนที่มิฉะนั้นการขยายตัวของคำจะถูกแทนที่ "
David Tonhofer

2
@Swiss นี้ไม่ชัดเจนหรือโดยอ้อม แต่เป็นสำนวน บางทีสำหรับโปรแกรมเมอร์ที่ไม่คุ้นเคย${+}และ${-}ไม่ชัดเจน แต่ความคุ้นเคยกับโครงสร้างเหล่านี้เป็นสิ่งจำเป็นหากผู้ใช้ที่มีความสามารถในเชลล์
วิลเลียม Pursell

ดูstackoverflow.com/a/20003892/14731หากคุณต้องการใช้สิ่งนี้เมื่อset -o nounsetเปิดใช้งาน
Gili

ฉันทำการทดสอบนี้อย่างมาก เพราะผลลัพธ์ในสคริปต์ของฉันไม่สอดคล้องกัน ผมขอแนะนำให้ดูพื้นบ้านเป็น " [ -v VAR ]วิธีการ" ที่มีทุบตี -s v4.2 และเกิน
จะ

38
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO=""
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO="a"
~> if [ -z $FOO ]; then echo "EMPTY"; fi
~> 

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

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

คำอื่น:

~$ unset FOO
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
~$ FOO=""
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
DEFINED

ค่าเริ่มต้น:

~$ FOO=""
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
~$ unset FOO
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
UNDEFINED

แน่นอนว่าคุณต้องใช้อย่างใดอย่างหนึ่งต่อไปนี้วางค่าที่คุณต้องการแทน 'ค่าเริ่มต้น' และใช้ส่วนขยายโดยตรงหากเหมาะสม


1
แต่ฉันต้องการแยกแยะระหว่างว่าสตริงนั้นคือ "" หรือยังไม่ได้กำหนดเลย เป็นไปได้ไหม
Setjmp

1
คำตอบนี้บอกวิธีแยกแยะระหว่างสองกรณีนี้ ไปที่ลิงค์ bash faq ที่มีการอภิปรายเพิ่มเติม
Charles Duffy

1
ฉันเพิ่มว่าหลังจากความคิดเห็นของเขาชาร์ลส์
Vinko Vrsalovic

2
ค้นหา "การขยายพารามิเตอร์" ในหน้า bash man สำหรับ "ลูกเล่น" เหล่านี้ทั้งหมด เช่น $ {foo: -default} เพื่อใช้ค่าเริ่มต้น $ {foo: = default} เพื่อกำหนดค่าเริ่มต้น $ {foo:? ข้อความแสดงข้อผิดพลาด} เพื่อแสดงข้อความแสดงข้อผิดพลาดหาก foo ไม่ได้ตั้งค่า ฯลฯ
Jouni K . Seppänen

18

คู่มือการเขียนสคริปต์ทุบตีขั้นสูง 10.2 การทดแทนพารามิเตอร์:

  • $ {var + blahblah}: ถ้ามีการกำหนด var, 'blahblah' จะถูกแทนที่สำหรับการแสดงออกมิฉะนั้น null จะถูกแทนที่
  • $ {var-blahblah}: ถ้ามีการกำหนด var มันจะถูกแทนที่ตัวเองมิฉะนั้น 'blahblah' จะถูกแทนที่
  • $ {var? blahblah}: หากมีการกำหนด var มันจะถูกแทนที่ด้วยมิฉะนั้นจะมีฟังก์ชั่น 'blahblah' เป็นข้อความแสดงข้อผิดพลาด


เพื่อวางรากฐานโปรแกรมตรรกะของคุณว่าตัวแปร $ mystr ถูกกำหนดหรือไม่คุณสามารถทำสิ่งต่อไปนี้:

isdefined=0
${mystr+ export isdefined=1}

ตอนนี้ถ้า isdefined = 0 ดังนั้นตัวแปรจะไม่ถูกกำหนดถ้า isdefined = 1 ตัวแปรนั้นถูกกำหนดไว้

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


สิ่งที่มีประโยชน์อื่น ๆ :

เพื่อให้มีค่าเริ่มต้นที่ 7 กำหนดให้กับ $ mystr หากไม่ได้กำหนดไว้และปล่อยให้เป็นอย่างอื่น:

mystr=${mystr- 7}

เพื่อพิมพ์ข้อความแสดงข้อผิดพลาดและออกจากฟังก์ชั่นหากตัวแปรไม่ได้กำหนด:

: ${mystr? not defined}

ระวังที่นี่ฉันใช้ ':' เพื่อไม่ให้มีเนื้อหาของ $ mystr ดำเนินการเป็นคำสั่งในกรณีที่มีการกำหนดไว้


ฉันไม่รู้เกี่ยวกับ? ไวยากรณ์สำหรับตัวแปร BASH นั่นเป็นเรื่องดี
สวิส

ใช้ได้กับการอ้างอิงตัวแปรทางอ้อมด้วย สิ่งนี้ผ่านไป: var= ; varname=var ; : ${!varname?not defined}และสิ่งนี้สิ้นสุดลง: varname=var ; : ${!varname?not defined}. แต่มันก็เป็นนิสัยที่ดีที่จะใช้set -uซึ่งทำได้ง่ายกว่าเดิมมาก
ceving

11

บทสรุปของการทดสอบ

[ -n "$var" ] && echo "var is set and not empty"
[ -z "$var" ] && echo "var is unset or empty"
[ "${var+x}" = "x" ] && echo "var is set"  # may or may not be empty
[ -n "${var+x}" ] && echo "var is set"  # may or may not be empty
[ -z "${var+x}" ] && echo "var is unset"
[ -z "${var-x}" ] && echo "var is set and empty"

การปฏิบัติที่ดีในการทุบตี บางครั้งไฟล์พา ธ มีช่องว่างหรือเพื่อป้องกันการฉีดคำสั่ง
k107

1
ฉันคิดว่า${var+x}มันไม่จำเป็นยกเว้นถ้าชื่อตัวแปรได้รับอนุญาตให้มีช่องว่างในพวกเขา
Anne van Rossum

ไม่ใช่แค่การฝึกฝนที่ดีเท่านั้น มันจำเป็นต้องมี[เพื่อให้ได้ผลลัพธ์ที่ถูกต้อง หากvarไม่ได้ตั้งค่าหรือว่างเปล่าให้[ -n $var ]ลด[ -n ]สถานะการออกที่ 0 ในขณะที่[ -n "$var" ]มีสถานะการออกที่คาดหวัง (และถูกต้อง) ที่ 1
chepner

5

https://stackoverflow.com/a/9824943/14731มีคำตอบที่ดีกว่า (อันที่อ่านได้ง่ายกว่าและset -o nounsetใช้งานได้เมื่อเปิดใช้งาน) มันทำงานได้ประมาณนี้

if [ -n "${VAR-}" ]; then
    echo "VAR is set and is not empty"
elif [ "${VAR+DEFINED_BUT_EMPTY}" = "DEFINED_BUT_EMPTY" ]; then
    echo "VAR is set, but empty"
else
    echo "VAR is not set"
fi

4

วิธีที่ชัดเจนในการตรวจสอบตัวแปรที่กำหนดไว้คือ:

[ -v mystr ]

1
ไม่พบสิ่งนี้ใน man page ใด ๆ (สำหรับ bash หรือ test) แต่มันใช้งานได้สำหรับฉัน .. ความคิดใดที่เพิ่มรุ่นนี้?
Ash Berlin-Taylor

1
มันถูกบันทึกไว้ในเงื่อนไขการแสดงออกอ้างอิงจากtestผู้ประกอบการในปลายหางของส่วนบอร์นเชลล์ builtins ฉันไม่รู้ว่ามันถูกเพิ่มเมื่อใด แต่มันไม่ได้อยู่ในรุ่น Apple ของbash(อิงจากbash3.2.51) ดังนั้นจึงอาจเป็นคุณลักษณะ 4.x
Jonathan Leffler

1
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)นี้ไม่ได้ทำงานให้ฉันด้วย -bash: [: -v: unary operator expectedมีข้อผิดพลาด ตามความคิดเห็นที่นี่ต้องมีอย่างต่ำทุบตี 4.2 ในการทำงาน
คิวเมนตัส

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

1

ตัวเลือกอื่น: การขยาย "รายการดัชนีอาร์เรย์" :

$ unset foo
$ foo=
$ echo ${!foo[*]}
0
$ foo=bar
$ echo ${!foo[*]}
0
$ foo=(bar baz)
$ echo ${!foo[*]}
0 1

ครั้งเดียวที่สิ่งนี้ขยายเป็นสตริงว่างคือเมื่อfooไม่ได้ตั้งค่าดังนั้นคุณสามารถตรวจสอบได้โดยใช้เงื่อนไขแบบสตริง:

$ unset foo
$ [[ ${!foo[*]} ]]; echo $?
1
$ foo=
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=bar
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=(bar baz)
$ [[ ${!foo[*]} ]]; echo $?
0

ควรพร้อมใช้งานในเวอร์ชันทุบตีใด ๆ > = 3.0


1

เพื่อไม่ให้รถคันนี้หลั่งเร็วขึ้น แต่ต้องการเพิ่ม

shopt -s -o nounset

เป็นสิ่งที่คุณสามารถเพิ่มไปด้านบนของสคริปต์ซึ่งจะเกิดข้อผิดพลาดหากตัวแปรไม่ได้ประกาศไว้ที่ใดก็ได้ในสคริปต์ ข้อความที่คุณเห็นคือunbound variableแต่อย่างที่คนอื่นพูดถึงมันจะไม่จับสตริงว่างหรือค่าว่าง เพื่อให้แน่ใจว่าค่าบุคคลใดไม่ว่างเราสามารถทดสอบตัวแปรที่มีการขยายตัวยังเป็นที่รู้จักการขยายตัวเครื่องหมายดอลลาร์ซึ่งจะมีข้อผิดพลาด${mystr:?}parameter null or not set


1

คู่มือทุบตีอ้างอิงเป็นแหล่งเผด็จการของข้อมูลเกี่ยวกับทุบตี

นี่คือตัวอย่างของการทดสอบตัวแปรเพื่อดูว่ามีอยู่:

if [ -z "$PS1" ]; then
        echo This shell is not interactive
else
        echo This shell is interactive
fi

(จากส่วน 6.3.2 .)

โปรดทราบว่าช่องว่างหลังจากที่เปิด[และก่อนที่จะ]เป็นไม่ได้เป็นตัวเลือก


เคล็ดลับสำหรับผู้ใช้ที่เป็นกลุ่ม

ฉันมีสคริปต์ที่มีการประกาศหลายอย่างดังนี้:

export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"

แต่ฉันต้องการให้พวกเขาเลื่อนไปตามค่าที่มีอยู่ ดังนั้นฉันเขียนใหม่เพื่อให้มีลักษณะเช่นนี้:

if [ -z "$VARIABLE_NAME" ]; then
        export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"
fi

ฉันสามารถทำให้สิ่งนี้เป็นกลุ่มโดยใช้ regex อย่างรวดเร็ว:

s/\vexport ([A-Z_]+)\=("[^"]+")\n/if [ -z "$\1" ]; then\r  export \1=\2\rfi\r/gc

:นี้สามารถนำมาใช้โดยการเลือกสายที่เกี่ยวข้องสายตาพิมพ์แล้ว แถบคำสั่งเติมด้วย:'<,'>ล่วงหน้า วางคำสั่งด้านบนแล้วกด Enter


ทดสอบในเวอร์ชัน Vim นี้:

VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Aug 22 2015 15:38:58)
Compiled by root@apple.com

ผู้ใช้ Windows อาจต้องการจุดสิ้นสุดของบรรทัดที่แตกต่างกัน


0

นี่คือสิ่งที่ฉันคิดว่าเป็นวิธีที่ชัดเจนกว่ามากในการตรวจสอบว่ามีการกำหนดตัวแปร:

var_defined() {
    local var_name=$1
    set | grep "^${var_name}=" 1>/dev/null
    return $?
}

ใช้มันดังต่อไปนี้:

if var_defined foo; then
    echo "foo is defined"
else
    echo "foo is not defined"
fi

1
การเขียนฟังก์ชั่นไม่ใช่ความคิดที่แย่ การกล่าวอ้างgrepนั้นแย่มาก โปรดทราบว่าbashการสนับสนุน${!var_name}เป็นวิธีการรับค่าของตัวแปรที่มีชื่อระบุไว้ในที่$var_nameดังนั้นอัตราผลตอบแทนname=value; value=1; echo ${name} ${!name} value 1
Jonathan Leffler

0

เวอร์ชันที่สั้นกว่าเพื่อทดสอบตัวแปรที่ไม่ได้กำหนดสามารถเป็น:

test -z ${mystr} && echo "mystr is not defined"

-3

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


2
ฉันไม่ได้ลงคะแนนในเรื่องนี้ แต่มันเป็นเรื่องของค้อนขนาดใหญ่ที่จะแตกถั่วค่อนข้างเล็ก
Jonathan Leffler

ฉันชอบคำตอบนี้ดีกว่าคำตอบที่ยอมรับ เห็นได้ชัดว่าสิ่งที่ทำในคำตอบนี้ คำตอบที่ได้รับการยอมรับนั้นไม่ได้แย่เหมือนนรก
สวิส

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