ข้อผิดพลาดทางไวยากรณ์ของ Bash เมื่อ“ อื่น” ตามหลังประโยค“ ว่าง” ที่ว่างเปล่า


35

ทำไมสคริปต์ต่อไปนี้จะไม่ทำงาน แต่ให้ข้อผิดพลาดทางไวยากรณ์ของelse:

LOGS3_DIR=~/logs
if [ -d "$LOGS3_DIR" ]; then
 cd
 cd "$LOGS3_DIR"
 echo "$LOGS3_DIR"
 for filename in `find "." -mtime 1 -type f`
  do
  if lsof "$filename" > /dev/null
  then
    # file is open
  else
    echo "deleting $filename"
    rm "$filename"
  fi
 done
fi

คำตอบ:


23

findอย่าใช้แทนคำสั่งในการส่งออกของ ที่นี่ทุกสิ่งสามารถทำได้ด้วยfind:

find . -mtime 1 -type f ! -exec lsof -t {} \; -exec rm -f {} \; > /dev/null

ด้วยไม่กี่findการใช้งาน (รวมทั้ง FreeBSD findที่มาจากและ GNU find), คุณสามารถใช้แทน-delete-exec rm...

เหตุผลที่คุณได้รับข้อผิดพลาดคือไม่มีคำสั่งระหว่างthenและelseและเชลล์บางตัว (เริ่มต้นด้วยเชลล์เป้าหมายที่ไวยากรณ์นั้นมาจาก) ต้องการอย่างน้อยหนึ่ง (และความคิดเห็นไม่ใช่คำสั่ง) โปรดทราบว่ามันเป็นกฎเกณฑ์โดยสมบูรณ์และไม่มีเหตุผลว่าทำไมเชลล์เหล่านั้นจะทำเช่นนั้น yashและzshไม่มีข้อ จำกัด เช่นนั้น ( if false; then else echo x; fiและif false; then else fiใช้ได้ดีกับพวกเขา)

อย่างที่คนอื่น ๆ พูดคุณสามารถใช้คำสั่ง noop เช่น:(หรือfor nothing in; do nothing; done) หรือย้อนกลับตรรกะด้วย!คำหลัก (มีอยู่ในเชลล์ POSIX แต่ไม่ใช่เชลล์เป้าหมาย (คุณจะพบว่าการใช้:เชลล์นั้นเป็นเรื่องปกติในเชลล์นั้น) mkshและyashเกิดขึ้นกับการสนับสนุนif false; then () else echo x; fi(ฉันจะไม่พึ่งพามันเท่าที่สามารถเปลี่ยนแปลงได้ในรุ่นอนาคต)

อีกวิธีคือ:

lsof... || {
  cmd1
  cmd2
}

แม้ว่าความแตกต่างอย่างหนึ่งคือสถานะการออกโดยรวมซึ่งจะเป็นของlsofหากlsofล้มเหลว


17
แม้ว่าวิธีนี้จะเป็นวิธีที่ดีกว่ามากในการทำสิ่งที่ผู้ใช้ @Nice กำลังพยายาม แต่ก็ไม่ได้ตอบคำถามเลย
SeeJayBee

ในขณะที่-execมักจะมีประโยชน์ตามที่เป็นอยู่xargsบางครั้งจำเป็นต้องใช้เปลือกวง ในกรณีนี้การwhile read nameวนซ้ำเป็นตัวเลือกที่ต้องการ (ในการทุบตีกับ GNU คุณสามารถใช้ตัวเลือก -0 สำหรับทั้งสอง;
Jan Hudec

@ JanHudec มีวิธีพกพาได้หลายวิธี -print0คือ-exec printf '%s\0' {} +(แต่คุณจะไม่สามารถจัดการกับเอาต์พุตนั้นได้ยกเว้นว่าคุณต้องการพิจารณาperl) และด้วยfind .//.การประมวลผลบางอย่างคุณสามารถหลีกเลี่ยงการขึ้นบรรทัดใหม่xargsได้ หมายเหตุว่ามันไม่ได้เป็นก็เป็นwhile read while IFS= read -r
Stéphane Chazelas

@ Chris ฉันได้เพิ่มคำตอบสำหรับคำถามจริงตั้งแต่คำตอบสิ้นสุดลงเป็นที่ยอมรับ
Stéphane Chazelas

90

ดูเหมือนว่าคุณต้องการที่จะทำไม่มี-opหากไฟล์ที่เปิดดังนั้นคุณจึงควรเพิ่ม:ซึ่งเป็นคำสั่งที่โมฆะในbash:

if lsof "$filename" > /dev/null; then
  # file is open
  :
else
  printf 'deleting %s\n' "$filename"
  rm -- "$filename"
fi

หากคุณไม่ได้ใช้:, ไม่สามารถแยกรหัสและจะแสดงข้อผิดพลาดเช่นbashbash: syntax error near unexpected token 'else'


ไม่ใหม่:และเป็นคำสั่งแรกที่แสดงใน bash-builtins
bolov


17

TL; DR

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

คำสั่งที่หายไป

รหัสต้นฉบับของคุณมีลักษณะดังนี้:

if lsof "$filename" > /dev/null
then
  # file is open
else
  echo "deleting $filename"
  rm "$filename"
fi

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

$ if true; then else echo; fi
bash: syntax error near unexpected token `else'

แก้ไขไวยากรณ์ของคุณด้วย Bourne Builtin

คุณสามารถแก้ไขปัญหานี้ได้โดยการวางคำสั่งจริงก่อนอื่นแต่ความคิดเห็นด้วยตัวเองจะไม่ทำ ส่วนif-thenต้องไม่ว่างเปล่า หากคุณต้องการตัวยึดตำแหน่งคุณสามารถใช้เครื่องหมายจุดคู่ในตัวได้ ตัวอย่างเช่น:

$ if true; then :; else echo; fi

เพียงแค่วาง:ลงในส่วนระหว่างนั้นและอื่น ๆจะแก้ไขข้อผิดพลาดทางไวยากรณ์ที่คุณกำลังประสบ


1
คำตอบของ Gnouc ซึ่งเป็นคำตอบที่ได้รับความนิยมมากที่สุดก็คือคำถามเดิมแล้ว
jlliagre

ตอบเฉพาะที่อยู่ข้อผิดพลาดทางไวยากรณ์ FWIW คุณสามารถทำซ้ำข้อผิดพลาดที่คล้ายกันกับเซมิโคลอน แต่เพียงผู้เดียวที่จุดเริ่มต้นของบรรทัด สิ่งนี้จะให้คำใบ้ที่แข็งแกร่ง $ ; -bash: syntax error near unexpected token ';'
Matthew Hannigan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.