ทดสอบว่าคำสั่งแสดงผลสตริงว่างหรือไม่


237

ฉันจะทดสอบว่าคำสั่งแสดงผลสตริงว่างเปล่าได้อย่างไร?


7
คำสั่งไม่ส่งคืนสตริง (ส่งคืนรหัสการออกจำนวนเต็มขนาดเล็กโดยปกติคือ 0 เพื่อความสำเร็จและ 1 หรือ 2 เมื่อล้มเหลว) แต่อาจส่งออกข้อมูลบางอย่างเพื่อใส่ในสตริง
Basile Starynkevitch

@BasileStarynkevitch นี่คือสิ่งที่ฉันต้องการฉันรู้ว่าคำสั่งส่งคืนรหัสออก แต่คำสั่งของฉันรหัสทางออกอยู่เสมอ 0.so ฉันต้องควบคุมผลลัพธ์
barp

1
คุณควรอ่าน Bash scripting tutorial tldp.org/LDP/abs/html
Basile Starynkevitch

Joey Hess มีประโยชน์ifneสำหรับสิ่งนี้ joeyh.name/code/moreutils
tripleee

คำตอบ:


309

ก่อนหน้านี้คำถามถามว่าจะตรวจสอบว่ามีไฟล์ในไดเรกทอรีหรือไม่ รหัสต่อไปนี้ประสบความสำเร็จ แต่ดูคำตอบของ rspสำหรับการแก้ปัญหาที่ดีกว่า


เอาท์พุทที่ว่างเปล่า

คำสั่งไม่ส่งคืนค่า - พวกเขาส่งออกพวกเขา คุณสามารถจับเอาท์พุทนี้โดยใช้การทดแทนคำสั่ง ; $(ls -A)เช่น คุณสามารถทดสอบสตริงที่ไม่ว่างใน Bash ดังนี้:

if [[ $(ls -A) ]]; then
    echo "there are files"
else
    echo "no files found"
fi

โปรดทราบว่าฉันใช้-Aมากกว่า-aเพราะจะไม่ใช้รายการไดเรกทอรีสัญลักษณ์ ( .) และ parent ( ..)

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


รหัสออก

หากคุณต้องการตรวจสอบว่าคำสั่งเสร็จสมบูรณ์แล้วคุณสามารถตรวจสอบ$?ซึ่งมีรหัสทางออกของคำสั่งสุดท้าย (ศูนย์สำหรับความสำเร็จไม่ใช่ศูนย์สำหรับความล้มเหลว) ตัวอย่างเช่น:

files=$(ls -A)
if [[ $? != 0 ]]; then
    echo "Command failed."
elif [[ $files ]]; then
    echo "Files found."
else
    echo "No files found."
fi

ข้อมูลเพิ่มเติมที่นี่


4
คุณสามารถละเว้น -nดังนั้นมันจะเป็นเพียงแค่if [[ $(ls -A) ]]; then
ผู้ใช้

6
วิธีการนี้จะให้เท็จสำหรับคำสั่งที่ส่งออกขึ้นบรรทัดใหม่เท่านั้น
Ciro Santilli 郝海东冠状病六四事件法轮功

89

TL; DR

if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]; then ...; fi

ขอบคุณnetj สำหรับคำแนะนำในการปรับปรุงต้นฉบับของฉัน:
if [[ $(ls -A | wc -c) -ne 0 ]]; then ...; fi


นี่เป็นคำถามเก่า แต่ฉันเห็นอย่างน้อยสองสิ่งที่ต้องการการปรับปรุงหรือการชี้แจงอย่างน้อย

ปัญหาแรก

ปัญหาแรกที่ผมเห็นก็คือว่าส่วนใหญ่ของตัวอย่างที่ให้ไว้ที่นี่ก็ไม่ได้ทำงาน พวกเขาใช้ls -alและls -Alคำสั่ง - ซึ่งทั้งสองออกสตริงที่ไม่ว่างเปล่าในไดเรกทอรีว่าง ตัวอย่างเหล่านั้นมักจะรายงานว่ามีไฟล์แม้ว่าจะไม่มีก็ตาม

ด้วยเหตุนี้คุณควรใช้เพียงls -A- ทำไมทุกคนต้องการใช้-lสวิตช์ซึ่งหมายถึง "ใช้รูปแบบรายการแบบยาว" เมื่อสิ่งที่คุณต้องการคือการทดสอบว่ามีผลลัพธ์หรือไม่?

ดังนั้นคำตอบส่วนใหญ่ที่นี่จึงไม่ถูกต้อง

ปัญหาที่สอง

ปัญหาที่สองคือในขณะที่บางคำตอบที่ดีทำงาน (ผู้ที่ไม่ได้ใช้ls -alหรือls -Alแต่ls -Aแทน) พวกเขาทั้งหมดทำอะไรเช่นนี้:

  1. เรียกใช้คำสั่ง
  2. บัฟเฟอร์เอาต์พุตทั้งหมดใน RAM
  3. แปลงเอาต์พุตเป็นสตริงบรรทัดเดียวขนาดใหญ่
  4. เปรียบเทียบสตริงนั้นกับสตริงว่าง

สิ่งที่ฉันอยากจะแนะนำให้ทำคือ:

  1. เรียกใช้คำสั่ง
  2. นับอักขระในเอาต์พุตโดยไม่ต้องเก็บไว้
    • หรือดียิ่งขึ้น - นับจำนวนอักขระสูงสุด 1 ตัวusing head -c1
      (ขอบคุณnetjสำหรับโพสต์ความคิดนี้ในความคิดเห็นด้านล่าง)
  3. เปรียบเทียบตัวเลขนั้นกับศูนย์

ตัวอย่างเช่นแทนที่จะเป็น:

if [[ $(ls -A) ]]

ฉันจะใช้:

if [[ $(ls -A | wc -c) -ne 0 ]]
# or:
if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]

แทน:

if [ -z "$(ls -lA)" ]

ฉันจะใช้:

if [ $(ls -lA | wc -c) -eq 0 ]
# or:
if [ $(ls -lA | head -c1 | wc -c) -eq 0 ]

และอื่น ๆ

สำหรับเอาต์พุตขนาดเล็กอาจไม่เป็นปัญหา แต่สำหรับเอาต์พุตขนาดใหญ่ความแตกต่างอาจมีความสำคัญ:

$ time [ -z "$(seq 1 10000000)" ]

real    0m2.703s
user    0m2.485s
sys 0m0.347s

เปรียบเทียบกับ:

$ time [ $(seq 1 10000000 | wc -c) -eq 0 ]

real    0m0.128s
user    0m0.081s
sys 0m0.105s

และยิ่งดีกว่า:

$ time [ $(seq 1 10000000 | head -c1 | wc -c) -eq 0 ]

real    0m0.004s
user    0m0.000s
sys 0m0.007s

ตัวอย่างเต็ม

อัปเดตตัวอย่างจากคำตอบโดย Will Vousden:

if [[ $(ls -A | wc -c) -ne 0 ]]; then
    echo "there are files"
else
    echo "no files found"
fi

อัปเดตอีกครั้งหลังจากคำแนะนำโดยnetj :

if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]; then
    echo "there are files"
else
    echo "no files found"
fi

อัปเดตเพิ่มเติมโดยjakeonfire :

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

if ls -A | head -c1 | grep -E '.'; then
    echo "there are files"
fi

if ! ls -A | head -c1 | grep -E '.'; then
    echo "no files found"
fi

ยกเลิกช่องว่าง

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

| wc -c

คุณสามารถใช้:

| tr -d ' \n\r\t ' | wc -c

หรือด้วยhead -c1:

| tr -d ' \n\r\t ' | head -c1 | wc -c

หรืออะไรทำนองนั้น

สรุป

  1. ก่อนอื่นให้ใช้คำสั่งที่ใช้งานได้

  2. ประการที่สองหลีกเลี่ยงการจัดเก็บใน RAM ที่ไม่จำเป็นและการประมวลผลข้อมูลขนาดใหญ่

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


3
[[ $(... | head -c | wc -c) -gt 0 ]]หรือ[[ -n $(... | head -c1) ]]ดีกว่าเพราะwc -cจะต้องใช้เอาต์พุตทั้งหมดของคำสั่ง
netj

@netj นี่เป็นความคิดที่ดีมาก ดูคำตอบที่อัปเดตของฉัน - ฉันเพิ่มข้อเสนอแนะของคุณ ขอบคุณมาก!
rsp

ความคิดใด ๆ เกี่ยวกับวิธีการได้รับผลลัพธ์ที่ค้างไว้?
fahrradflucht

ฉันคิดว่าต้นฉบับ (ไม่มีหัว) ดีกว่าในกรณีทั่วไป ไม่ใช่ความคิดที่ดีที่จะฆ่าคำสั่งทันทีที่เริ่มเขียนเอาต์พุต!
stk

@stk โปรแกรมส่วนใหญ่จะออกอย่างปลอดภัยหลังจากได้รับสัญญาณการฆ่า
cambunctious

40
if [ -z "$(ls -lA)" ]; then
  echo "no files found"
else
  echo "There are files"
fi

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

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

หมายเหตุ: ที่ls -laมักจะให้ผลตอบแทน.และ..ดังนั้นการใช้ที่จะไม่ทำงานให้ดูคำสั่ง ls หน้าคู่มือ นอกจากนี้ในขณะนี้อาจดูสะดวกและง่าย แต่ฉันคิดว่ามันจะแตกง่าย การเขียนสคริปต์ / แอปพลิเคชันขนาดเล็กที่ส่งคืนค่า 0 หรือ 1 ขึ้นอยู่กับผลลัพธ์นั้นมีความน่าเชื่อถือมากขึ้น!


คุณอาจต้องการใช้$(แทน backquote เพราะ$(ทำรังได้ดีกว่า
Basile Starynkevitch

คุณพูดถูกมันเป็นการดีกว่าที่จะใช้มันตลอดเวลาแม้ว่าเราไม่ต้องการทำรังที่นี่
Veger

23

สำหรับผู้ที่ต้องการวิธีแก้ปัญหาที่ไม่ขึ้นต่อกันของเวอร์ชันที่ใช้งานได้จริง (ในความเป็นจริงควรทำงานกับเชลล์สมัยใหม่อื่น ๆ ) และผู้ที่ชื่นชอบการใช้งานแบบตอร์ปิโดสำหรับงานที่รวดเร็ว ไปเลย!

ls | grep . && echo 'files found' || echo 'files not found'

(บันทึกเป็นหนึ่งในความคิดเห็นที่กล่าวถึงls -alและในความเป็นจริงเพียง-lและ-aจะกลับบางสิ่งบางอย่างดังนั้นในคำตอบของฉันฉันใช้ง่ายls


5
หากคุณใช้grep -q ^แทนอักขระขึ้นบรรทัดใหม่จะถูกจับคู่และ grep จะไม่พิมพ์อะไรไปยังเอาต์พุตมาตรฐาน นอกจากนี้มันจะออกทันทีที่ได้รับอินพุตใด ๆ แทนที่จะรอให้อินพุตสตรีมสิ้นสุด
mortehu

ควรเริ่มต้นด้วยls -Aถ้าคุณต้องการที่จะรวมจุดไฟล์ (หรือไดเรกทอรี)
wrlee

13

คู่มืออ้างอิง Bash

6.4 การแสดงออกตามเงื่อนไขของ Bash

-z string
     True if the length of string is zero.

-n string
string
     True if the length of string is non-zero.

คุณสามารถใช้ชวเลขรุ่น:

if [[ $(ls -A) ]]; then
  echo "there are files"
else
  echo "no files found"
fi

1
มี -z หรือ -n หายไปใน parenthessis [[... ]]
71GA

4
@ 71GA: บางทีนั่นอาจจะเป็นเรื่องง่ายที่จะมองข้าม แต่ถ้าคุณจะดูอย่างคุณจะเห็นว่าเพียงแค่เป็นคำพ้องสำหรับstring -n string
ผู้ใช้

10

จอนหลินแสดงความคิดเห็นls -alจะส่งออกเสมอ (สำหรับ.และ..) คุณต้องการls -Alหลีกเลี่ยงไดเรกทอรีทั้งสองนี้

ตัวอย่างเช่นคุณสามารถวางผลลัพธ์ของคำสั่งลงในตัวแปรเชลล์:

v=$(ls -Al)

รูปแบบที่เก่ากว่าและไม่สามารถซ้อนได้คือ

v=`ls -Al`

แต่ฉันชอบสัญลักษณ์ที่ซ่อนอยู่$(...)

คุณสามารถทดสอบว่าตัวแปรนั้นไม่ว่างเปล่าหรือไม่

if [ -n "$v" ]; then
    echo there are files
else
    echo no files
fi

และคุณสามารถรวมทั้งสองเป็น if [ -n "$(ls -Al)" ]; then


5

ฉันเดาว่าคุณต้องการผลลัพธ์ของls -alคำสั่งดังนั้นในทุบตีคุณมีสิ่งที่ชอบ:

LS=`ls -la`

if [ -n "$LS" ]; then
  echo "there are files"
else
  echo "no files found"
fi

สิ่งนี้ไม่ทำงาน: คำสั่งlsไม่เคยถูกดำเนินการ คุณตรวจสอบว่าการขยายตัวของตัวแปร LSไม่ว่างเปล่าหรือไม่
gniourf_gniourf

1
@gniourf_gniourf - อะไรทำให้คุณคิดอย่างนั้น คาถา backtick เป็นที่ยอมรับไม่ได้เป็นที่รักหรือมีความยืดหยุ่นเป็น $ () แต่มันทำงาน ...
tink

-aสวิทช์จะไม่กลับไม่มีเนื้อหา (เช่นก็จะกลับเนื้อหาว่ามีไฟล์หรือไม่) เนื่องจากมีอยู่เสมอโดยปริยาย.และ..ไดเรกทอรีปัจจุบัน
wrlee

3

นี่คือวิธีแก้ปัญหาสำหรับกรณีที่รุนแรงมากขึ้น:

if [ `command | head -c1 | wc -c` -gt 0 ]; then ...; fi

สิ่งนี้จะได้ผล

  • สำหรับเชลล์เป้าหมายทั้งหมด
  • หากเอาต์พุตคำสั่งเป็นศูนย์ทั้งหมด
  • อย่างมีประสิทธิภาพโดยไม่คำนึงถึงขนาดเอาต์พุต

อย่างไรก็ตาม

  • คำสั่งหรือกระบวนการย่อยจะถูกฆ่าเมื่อมีการส่งออกอะไร

1

คำตอบทั้งหมดที่ได้รับจนถึงการจัดการกับคำสั่งที่ยุติและส่งออกสตริงที่ไม่ว่างเปล่า

ส่วนใหญ่จะหักในความรู้สึกต่อไปนี้:

  • พวกเขาไม่สามารถจัดการกับคำสั่งที่แสดงบรรทัดใหม่ได้อย่างถูกต้องเท่านั้น
  • เริ่มต้นจากBash≥4.4ข้อผิดพลาดมาตรฐานส่วนใหญ่จะเป็นสแปมหากคำสั่งเอาท์พุทเป็นโมฆะไบต์ (เนื่องจากใช้แทนคำสั่ง)
  • ส่วนใหญ่จะไหลออกเต็มสตรีมออกดังนั้นจะรอจนกว่าคำสั่งจะยุติก่อนที่จะตอบ คำสั่งบางคำไม่เคยยุติ (ลองเช่นyes)

ดังนั้นเพื่อแก้ไขปัญหาเหล่านี้ทั้งหมดและตอบคำถามต่อไปนี้อย่างมีประสิทธิภาพ

ฉันจะทดสอบว่าคำสั่งแสดงผลสตริงว่างเปล่าได้อย่างไร?

คุณสามารถใช้ได้:

if read -n1 -d '' < <(command_here); then
    echo "Command outputs something"
else
    echo "Command doesn't output anything"
fi

นอกจากนี้คุณยังอาจเพิ่มหมดเวลาบางอย่างเพื่อที่จะทดสอบว่าคำสั่ง outputs สตริงไม่ว่างเปล่าภายในเวลาที่กำหนดโดยใช้read's -tตัวเลือก เช่นการหมดเวลา 2.5 วินาที:

if read -t2.5 -n1 -d '' < <(command_here); then
    echo "Command outputs something"
else
    echo "Command doesn't output anything"
fi

สังเกต. หากคุณคิดว่าคุณต้องพิจารณาว่าคำสั่งแสดงผลสตริงที่ไม่ว่างเปล่าหรือไม่คุณอาจพบปัญหา XY มาก


ฉันคิดว่าคำตอบนี้ค่อนข้างน่าชื่นชม ผมได้รับการปฏิบัติงานการทดสอบบางส่วนของโซลูชั่นที่นี่ใช้ 100 ซ้ำสำหรับแต่ละสถานการณ์การป้อนข้อมูล 3 ( seq 1 10000000, seq 1 20และprintf"") การจับคู่เดียวที่วิธีอ่านนี้ไม่ชนะคือด้วย-z "$(command)"วิธีการป้อนข้อมูลที่ว่างเปล่า ถึงอย่างนั้นมันก็สูญเสียประมาณ 10-15% เท่านั้น ดูเหมือนว่าพวกเขาจะทำลายแม้แต่seq 1 10น้อย ไม่ว่า-z "$(command)"วิธีการที่จะกลายเป็นดังนั้นช้าเป็นขนาดอินพุตเติบโตที่ฉันต้องการหลีกเลี่ยงการใช้มันสำหรับการป้อนข้อมูลใด ๆ ตัวแปรขนาด
abathur

0

ต่อไปนี้เป็นวิธีทางเลือกที่เขียน std-out และ std-err ของคำสั่งเป็นไฟล์ชั่วคราวจากนั้นตรวจสอบว่าไฟล์นั้นว่างเปล่าหรือไม่ ข้อดีของวิธีการนี้คือมันจับทั้งเอาต์พุตและไม่ใช้ sub-shells หรือไพพ์ ลักษณะหลังเหล่านี้มีความสำคัญเนื่องจากสามารถแทรกแซงการดักจับการออกจากการทุบตี bash (เช่นที่นี่ )

tmpfile=$(mktemp)
some-command  &> "$tmpfile"
if [[ $? != 0 ]]; then
    echo "Command failed"
elif [[ -s "$tmpfile" ]]; then
    echo "Command generated output"
else
    echo "Command has no output"
fi
rm -f "$tmpfile"

0

บางครั้งคุณต้องการบันทึกผลลัพธ์หากไม่ว่างเปล่าให้ส่งผ่านไปยังคำสั่งอื่น ถ้าเป็นเช่นนั้นคุณสามารถใช้สิ่งที่ชอบ

list=`grep -l "MY_DESIRED_STRING" *.log `
if [ $? -eq 0 ]
then
    /bin/rm $list
fi

ด้วยวิธีนี้rmคำสั่งจะไม่หยุดหากรายการว่างเปล่า

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