ฉันจะทดสอบได้อย่างไรว่าตัวแปรว่างเปล่าหรือมีช่องว่างเท่านั้น?


240

ไวยากรณ์ bash ต่อไปนี้ตรวจสอบว่าparamไม่ว่างเปล่า:

 [[ !  -z  $param  ]]

ตัวอย่างเช่น:

param=""
[[ !  -z  $param  ]] && echo "I am not zero"

ไม่มีการส่งออกและปรับ

แต่เมื่อparamว่างเปล่ายกเว้นอักขระเว้นวรรคหนึ่งตัว (ขึ้นไป) เคสจะแตกต่างกัน:

param=" " # one space
[[ !  -z  $param  ]] && echo "I am not zero"

เอาท์พุท "ฉันไม่ได้เป็นศูนย์"

ฉันจะเปลี่ยนการทดสอบเพื่อพิจารณาตัวแปรที่มีอักขระเว้นวรรคว่างเปล่าได้อย่างไร


4
จาก:man test -z STRING - the length of STRING is zeroหากคุณต้องการลบช่องว่างทั้งหมด$paramให้ใช้${param// /}
dchirikov

6
ว้าวมีไม่ง่ายtrim()ฟังก์ชั่นในตัวระวัง * ใด ๆ ที่ทั้งหมดหรือไม่ แฮ็คที่แตกต่างกันมากมายเพื่อให้ได้สิ่งที่ง่าย ...
ADTC

6
@ADTC $ (sed 's / ^ \ s + | \ s + $ // g' <<< $ string) ดูเหมือนจะง่ายพอสำหรับฉัน ทำไมต้องคิดค้นล้อใหม่
jbowman

3
เพราะอาจเป็นเงากว่า
Inversus

1
เพียงแค่สังเกตว่าเทียบเท่ากับ[[ ! -z $param ]] test ! -z $param
Noah Sussman

คำตอบ:


292

ก่อนอื่นให้สังเกตว่าการ-zทดสอบนั้นชัดเจนสำหรับ:

ความยาวของสตริงเป็นศูนย์

นั่นคือสตริงที่มีช่องว่างเท่านั้นไม่ควรเป็นจริงภายใต้-zเพราะมันมีความยาวไม่เป็นศูนย์

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

[[ -z "${param// }" ]]

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


สารัตถะของวิธีการทำงานคือว่า${var/pattern/string}แทนที่การแข่งขันครั้งแรกที่ยาวที่สุดของด้วยpattern stringเมื่อpatternเริ่มต้นด้วย/(ข้างต้น) แล้วมันแทนที่ทุกการแข่งขัน เนื่องจากการแทนที่ว่างเปล่าเราสามารถละเว้นค่าสุดท้าย/และstringค่าได้:

$ {พารามิเตอร์ / รูปแบบ / สตริง}

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

หลังจากนั้นเราก็ลงเอยด้วย${param// }การลบช่องว่างทั้งหมด

โปรดทราบว่าแม้ว่าจะมีอยู่ในksh(ที่มา) zshและbashไวยากรณ์นั้นไม่ใช่ POSIX และไม่ควรใช้ในshสคริปต์


ใช้sedพวกเขาบอกว่า ... แค่echoตัวแปรที่พวกเขาพูด ...
mikezter

1
อืมมม ถ้าเป็นเช่น${var/pattern/string}นั้นแล้วไม่ควรที่จะ[[ -z ${param/ /} ]]มีช่องว่างระหว่างเครื่องหมายทับเพื่อเป็นรูปแบบและไม่มีอะไรหลังจากเป็นสตริง?
Jesse Chisholm

@JesseChisholm "เนื่องจากการแทนที่ว่างเปล่าเราสามารถละเว้นค่าสุดท้าย / และค่าสตริง"; "ถ้าสตริงเป็นโมฆะการจับคู่ของรูปแบบจะถูกลบและรูปแบบ / ต่อไปนี้อาจถูกละเว้น"
Michael Homer

6
@MichaelHomer คำตอบ[[ -z "${param// }" ]]แน่ใจว่าดูเหมือนว่าpatternว่างเปล่าและreplacement stringเป็นช่องว่างเดียว AH! ฉันเห็นตอนนี้if pattern begins with a slashฉันพลาดส่วนนั้น ดังนั้นรูปแบบคือ/ และสตริงที่เหลือและดังนั้นจึงว่างเปล่า ขอบคุณ
Jesse Chisholm

bash note: หากทำงานใน set -o nounset (set -u) และ param ไม่ได้ตั้งค่า (แทนที่จะเป็น null หรือ space-filled) ดังนั้นสิ่งนี้จะสร้างข้อผิดพลาดตัวแปรที่ไม่ถูกผูกไว้ (set + u; ..... ) จะยกเว้นการทดสอบนี้
Brian Chrisman

31

วิธีง่ายๆในการตรวจสอบว่าสตริงมีอักขระในชุดที่ได้รับอนุญาตเท่านั้นคือการทดสอบว่ามีอักขระที่ไม่ได้รับอนุญาตหรือไม่ ดังนั้นแทนที่จะทดสอบว่าสตริงมีช่องว่างหรือไม่เท่านั้นทดสอบว่าสตริงมีอักขระบางตัวที่ไม่ใช่ช่องว่างหรือไม่ ใน bash, ksh หรือ zsh:

if [[ $param = *[!\ ]* ]]; then
  echo "\$param contains characters other than space"
else
  echo "\$param consists of spaces only"
fi

“ ประกอบด้วยช่องว่างเท่านั้น” รวมถึงกรณีของตัวแปรว่างเปล่า (หรือไม่ได้ตั้งค่า)

คุณอาจต้องการทดสอบอักขระช่องว่างใด ๆ ใช้[[ $param = *[^[:space:]]* ]]เพื่อใช้การตั้งค่าโลแคลหรือรายการอักขระช่องว่างที่ชัดเจนที่คุณต้องการทดสอบเช่น[[ $param = *[$' \t\n']* ]]เพื่อทดสอบช่องว่างแท็บหรือการขึ้นบรรทัดใหม่

การจับคู่สตริงกับรูปแบบที่มี=ภายใน[[ … ]]เป็นส่วนขยาย ksh (ยังมีอยู่ใน bash และ zsh) ในสไตล์ Bourne / POSIX คุณสามารถใช้caseโครงสร้างเพื่อจับคู่สตริงกับรูปแบบ โปรดทราบว่ารูปแบบเชลล์มาตรฐานใช้!เพื่อคัดค้านชุดอักขระแทนที่จะ^ชอบในไวยากรณ์นิพจน์ทั่วไปส่วนใหญ่

case "$param" in
  *[!\ ]*) echo "\$param contains characters other than space";;
  *) echo "\$param consists of spaces only";;
esac

ในการทดสอบอักขระช่องว่าง$'…'ไวยากรณ์จะใช้เฉพาะกับ ksh / bash / zsh; คุณสามารถแทรกอักขระเหล่านี้ในสคริปต์ของคุณอย่างแท้จริง (โปรดทราบว่าการขึ้นบรรทัดใหม่จะต้องอยู่ในเครื่องหมายคำพูดเนื่องจากเครื่องหมายแบ็กสแลช + การขึ้นบรรทัดใหม่จะขยายเป็นไม่มีอะไรเลย) หรือสร้างเช่น

whitespace=$(printf '\n\t ')
case "$param" in
  *[!$whitespace]*) echo "\$param contains non-whitespace characters";;
  *) echo "\$param consists of whitespace only";;
esac

นี่เกือบจะยอดเยี่ยม - แต่ถึงกระนั้นก็ยังไม่ตอบคำถามตามที่ถาม ขณะนี้มันสามารถบอกคุณได้ความแตกต่างระหว่างล้างหรือตัวแปรเปลือกที่ว่างเปล่าและหนึ่งซึ่งมีช่องว่างเท่านั้น หากคุณจะยอมรับข้อเสนอแนะของฉันคุณจะเพิ่มรูปแบบการขยายตัวพระรามยังไม่ convered โดยคำตอบอื่น ๆ อย่างชัดเจนว่าการทดสอบตัวแปรเปลือกสำหรับการตั้ง - ผมหมายถึง${var?not set}- ผ่านการทดสอบและมีชีวิตรอดจะให้แน่ใจว่าตัวแปรจับคู่โดยคุณ*) caseจะมีผลกระทบที่ชัดเจน คำตอบเช่น
mikeserv

การแก้ไข - อันที่จริงแล้วสิ่งนี้จะตอบคำถามที่ถามมา แต่เดิมได้มีการแก้ไขในลักษณะที่จะเปลี่ยนความหมายของคำถามและทำให้คำตอบของคุณเป็นโมฆะ
mikeserv

คุณจำเป็นต้องมีใน[[ $param = *[!\ ]* ]] mksh
Stéphane Chazelas

20

POSIXly:

case $var in
  (*[![:blank:]]*) echo '$var contains non blank';;
  (*) echo '$var contains only blanks or is empty or unset'
esac

ความแตกต่างระหว่างว่างเปล่า , ไม่ใช่ว่างเปล่า , ที่ว่างเปล่า , ไม่มีการตั้งค่า :

case ${var+x$var} in
  (x) echo empty;;
  ("") echo unset;;
  (x*[![:blank:]]*) echo non-blank;;
  (*) echo blank
esac

[:blank:]ใช้สำหรับอักขระระยะห่างแนวนอน (ช่องว่างและแท็บใน ASCII แต่อาจมีอยู่ในโลแคลของคุณอีกสองสามระบบบางระบบจะมีช่องว่างไม่แตก (หากมี) บางอันจะไม่) หากคุณต้องการตัวอักษรระยะห่างแนวตั้งเช่นกัน (เช่นการขึ้นบรรทัดใหม่หรือรูปแบบฟีด) แทนที่ด้วย[:blank:][:space:]


POSIXly เหมาะอย่างยิ่งเพราะมันจะทำงานกับเส้นประ (ดีกว่าเนื้อหา)
Ken Sharp

zshดูเหมือนว่าจะมีปัญหากับ[![:blank:]]มันเพิ่ม:bเป็นตัวดัดแปลงที่ไม่ถูกต้อง ในshและkshเลียนแบบจะเพิ่มเหตุการณ์ไม่พบสำหรับ[
cuonglm

@cuonglm คุณลองทำอะไร var=foo zsh -c 'case ${var+x$var} in (x*[![:blank:]]*) echo x; esac'ก็โอเคสำหรับฉัน ไม่พบกิจกรรมจะใช้สำหรับเชลล์แบบโต้ตอบเท่านั้น
Stéphane Chazelas

@ StéphaneChazelas: อ่าใช่แล้วฉันลองด้วยเปลือกโต้ตอบ :bปรับปรุงที่ไม่ถูกต้องเป็นบิตแปลก
cuonglm

1
@ FrankankYu บน OS / X shถูกbashสร้างขึ้นด้วย--enable-xpg-echo-defaultและ--enable-strict-posix-defaultเพื่อให้เป็นไปตาม Unix (ซึ่งเกี่ยวข้องกับการecho '\t'ส่งออกแท็บ)
Stéphane Chazelas

4

เหตุผลเดียวที่เหลือในการเขียนเชลล์สคริปต์แทนที่จะเป็นสคริปต์ในภาษาสคริปต์ที่ดีคือถ้าความสะดวกในการพกพาเป็นเรื่องที่น่ากังวล มรดก/bin/shเป็นสิ่งเดียวที่คุณจะมั่นใจได้ว่าคุณมี แต่ Perl เช่นเป็นมากขึ้นมีแนวโน้มที่จะสามารถใช้งานข้ามแพลตฟอร์มกว่าทุบตี ดังนั้นไม่เคยเขียนสคริปต์เปลือกที่ใช้คุณลักษณะที่ไม่เป็นสากลอย่างแท้จริง - และเก็บไว้ในใจว่าหลายที่เป็นกรรมสิทธิ์ของผู้ขาย Unix แข็งสภาพแวดล้อมเปลือกของพวกเขาก่อนที่จะ POSIX.1-2001

มีวิธีพกพาเพื่อทำการทดสอบนี้ แต่คุณต้องใช้tr:

[ "x`printf '%s' "$var" | tr -d "$IFS"`" = x ]

(ค่าเริ่มต้น$IFSคือสะดวกเป็นช่องว่างแท็บและขึ้นบรรทัดใหม่)

( printfbuiltin นั้นพกพาสะดวกมาก แต่อาศัยความยุ่งยากน้อยกว่าการหาว่าechoคุณมีรุ่นไหน)


1
มันค่อนข้างซับซ้อน วิธีพกพาตามปกติเพื่อทดสอบว่าสตริงมีอักขระบางตัวด้วยcaseหรือไม่
Gilles

@Gilles ฉันไม่คิดว่าเป็นไปได้ที่จะเขียนcaseglob เพื่อตรวจจับสตริงที่ว่างเปล่าหรือมีอักขระช่องว่างเท่านั้น (มันจะง่ายกับ regexp แต่caseไม่ได้ทำ regexps สร้างในคำตอบของคุณจะไม่ทำงานถ้าคุณดูแลเกี่ยวกับการขึ้นบรรทัดใหม่..)
zwol

1
ฉันให้ช่องว่างเป็นตัวอย่างในคำตอบเพราะนั่นคือคำถามที่ถาม case $param in *[![:space:]]*) …มันเป็นเรื่องง่ายอย่างเท่าเทียมกันในการทดสอบสำหรับตัวละครช่องว่าง: หากคุณต้องการทดสอบIFSอักขระคุณสามารถใช้รูปแบบ*[$IFS]*ได้
Gilles

1
@mikeserv ในสคริปต์การป้องกันที่จริงจังจริงๆคุณตั้ง IFS ไว้<space><tab><newline>ที่จุดเริ่มต้นอย่างชัดเจนแล้วอย่าแตะอีกครั้ง ฉันคิดว่านั่นเป็นสิ่งที่ทำให้ไขว้เขว
zwol

1
เกี่ยวกับตัวแปรในรูปแบบฉันคิดว่ามันใช้ได้กับ Bourne ทุกรุ่นและ POSIX shell ทั้งหมด มันจะต้องใช้รหัสพิเศษในการตรวจสอบรูปแบบตัวพิมพ์และปิดการขยายตัวของตัวแปรและฉันไม่สามารถคิดเหตุผลที่จะทำ ฉันมีสองสามครั้งของมันในของฉัน.profileซึ่งถูกดำเนินการโดยเชลล์เป้าหมายจำนวนมาก แน่นอน[$IFS]จะไม่ทำสิ่งที่ตั้งใจถ้าIFSมีค่าบางอย่างที่ไม่ใช่ค่าเริ่มต้น (ที่ว่างเปล่า (หรือไม่มีการตั้งค่า) ที่มี]เริ่มต้นด้วย!หรือ^)
Gilles

4
if [[ -n "${variable_name/[ ]*\n/}" ]]
then
    #execute if the the variable is not empty and contains non space characters
else
    #execute if the variable is empty or contains only spaces
fi

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

0

สำหรับการทดสอบว่าตัวแปรว่างเปล่าหรือมีช่องว่างคุณสามารถใช้รหัสนี้:

${name:?variable is empty}

2
ฉันคิดว่าคำตอบของคุณถูกลดระดับลงเพราะ "หรือมีช่องว่าง" ไม่เป็นความจริง
แบร์นฮาร์ด

ระวัง - นั่นจะฆ่าเชลล์ปัจจุบัน ดีกว่าที่จะใส่ไว้ใน (: $ {subshell?}) นอกจากนี้ - ที่จะเขียนถึง stderr - คุณอาจต้องการเปลี่ยนเส้นทาง และที่สำคัญที่สุดคือประเมินค่าจริงของตัวแปรหากไม่ได้ตั้งค่าหรือโมฆะ หากมีคำสั่งอยู่ที่นั่นคุณเพียงแค่เรียกใช้ เป็นการดีที่สุดที่จะทำการทดสอบ:นั้น
mikeserv

มันจะฆ่าเปลือกได้อย่างไร และคุณยังสามารถทดสอบสิ่งนี้ได้อันดับแรกให้กำหนดname=aaaaแล้วเรียกใช้คำสั่งนี้echo ${name:?variable is empty}มันจะพิมพ์ค่าของชื่อตัวแปรและตอนนี้เรียกใช้คำสั่งนี้echo ${name1:?variable is empty}มันจะพิมพ์-sh: name1: ตัวแปรว่างเปล่า
pratik

ใช่ - echo ${name:?variable is empty}จะประเมิน$nameว่าเป็นค่าที่ไม่เป็นโมฆะหรือจะฆ่าเชลล์ ดังนั้นด้วยเหตุผลเดียวกันคุณไม่prompt > $randomvarควรทำอย่างนั้น${var?}- ถ้ามีคำสั่งอยู่ในนั้นก็อาจจะวิ่ง - และคุณไม่รู้ว่ามันคืออะไร เชลล์แบบอินเทอร์แอคทีฟไม่ต้องออก - ซึ่งเป็นเหตุผลว่าทำไมคุณถึงไม่มี ถึงกระนั้นถ้าคุณต้องการที่จะเห็นการทดสอบบางอย่างมีจำนวนมากที่นี่ . อันนี้ไม่นาน ...
mikeserv

0

หากต้องการทดสอบว่าตัวแปรไม่ได้ตั้งค่าว่างหรือไม่ (มีค่า Null) หรือมีช่องว่างเท่านั้น:

 [ -z "${param#"${param%%[! ]*}"}" ] && echo "empty"

คำอธิบาย:

  • การขยายตัวของการ${param%%[! ]*}ลบออกจากที่ไม่ใช่พื้นที่แรกไปยังจุดสิ้นสุด
  • นั่นเป็นเพียงช่องว่างนำหน้า
  • การขยายตัวครั้งถัดไปจะลบช่องว่างนำหน้าออกจากตัวแปร
  • หากผลลัพธ์ว่างเปล่าแสดงว่าตัวแปรนั้นว่างเปล่าที่จะเริ่มต้นด้วย
  • หรือถ้าตัวแปรไม่ว่างการลบช่องว่างนำหน้าทำให้ว่างเปล่า
  • หากผลลัพธ์ว่างเปล่า-zให้สะท้อนemptyกลับ

ข้างต้นเป็น POSIX เข้ากันได้และทำงานในลูกหลานบอร์นใด ๆ

หากคุณต้องการที่จะขยายไปยังแท็บรวมถึงแท็บที่ชัดเจน:

 [ -z "${param#"${param%%[!     ]*}"}" ] && echo "empty"

หรือใช้ตัวแปรที่มีอักขระที่คุณต้องการลบ:

 var=$' \t'   # in ksh, bash, zsh
 [ -z "${param#"${param%%[!$var]*}"}" ] && echo "empty"

หรือคุณสามารถใช้ POSIX "การแสดงออกของคลาสตัวอักษร" (ช่องว่างหมายถึงช่องว่างและแท็บ):

 [ -z "${param#"${param%%[![:blank:]]*}"}" ] && echo "empty"

แต่นั่นจะล้มเหลวใน mksh, lksh และ posh


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