ฉันจะรอไฟล์ในเชลล์สคริปต์ได้อย่างไร


15

ฉันพยายามเขียนเชลล์สคริปต์ที่จะรอให้ไฟล์ปรากฏใน/tmpไดเรกทอรีที่เรียกsleep.txtและเมื่อพบว่าโปรแกรมจะหยุดทำงานมิฉะนั้นฉันต้องการให้โปรแกรมอยู่ในสถานะสลีป (ถูกระงับ) จนกว่าไฟล์จะอยู่ . ตอนนี้ฉันสมมติว่าฉันจะใช้คำสั่งทดสอบ ดังนั้นสิ่งที่ชอบ

(if [ -f "/tmp/sleep.txt" ]; 
then stop 
   else sleep.)

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


$MAILPATHดูที่
mikeserv

คำตอบ:


23

ภายใต้ Linux คุณสามารถใช้ระบบย่อยเคอร์เนลinotifyเพื่อรอการปรากฏตัวของไฟล์ในไดเรกทอรี:

while read i; do if [ "$i" = sleep.txt ]; then break; fi; done \
   < <(inotifywait  -e create,open --format '%f' --quiet /tmp --monitor)
# script execution continues ...

(สมมติว่า Bash สำหรับ<()ไวยากรณ์การเปลี่ยนเส้นทางเอาต์พุต)

ข้อดีของวิธีนี้เมื่อเปรียบเทียบกับการสำรวจช่วงเวลาที่กำหนดเช่น

while [ ! -f /tmp/sleep.txt ]; do sleep 1; done
# script execution continues ...

คือเคอร์เนลพักมากขึ้น ด้วยข้อกำหนดคุณสมบัติเหตุการณ์ inotify เช่นcreate,openสคริปต์ถูกกำหนดเวลาสำหรับการดำเนินการเมื่อไฟล์ภายใต้/tmpถูกสร้างหรือเปิด ด้วยช่วงเวลาที่กำหนดไว้คุณจะเสียรอบ CPU สำหรับการเพิ่มเวลาแต่ละครั้ง

ฉันรวมopenกิจกรรมเพื่อลงทะเบียนด้วยtouch /tmp/sleep.txtเมื่อไฟล์มีอยู่แล้ว


ขอบคุณนี่ยอดเยี่ยมมาก! เหตุใดคุณจึงต้องใช้ลูป while หากคุณรู้จักชื่อไฟล์ ทำไมไม่เป็นเช่นนั้นinotifywait -e create,open --format '%f' --quiet /tmp/sleep.txt --monitor > /dev/null ; # continues once /tmp/sleep.txt is created/opened?
NHDaly

โอ้เคเค หลังจากติดตั้งเครื่องมือฉันเข้าใจแล้ว inotifywaitเป็นห่วงชั่วขณะหนึ่งและส่งสัญญาณออกทุกครั้งที่มีการเปิด / สร้างไฟล์ ดังนั้นคุณต้องใช้ while loop เพื่อหยุดพักเมื่อมีการเปลี่ยนแปลง
NHDaly

ยกเว้นว่าสิ่งนี้สร้างเงื่อนไขการแข่งขัน - ต่างจากรุ่นทดสอบ -e / sleep ไม่มีวิธีที่จะรับประกันได้ว่าไฟล์จะไม่ถูกสร้างขึ้นก่อนที่จะวนซ้ำ - ในกรณีนี้เหตุการณ์ที่จะพลาด คุณต้องเพิ่มสิ่งที่ต้องการ (sleep 1; [[-e /tmp/sleep.txt]] && touch /tmp/sleep.txt;) & ก่อนลูป หลักสูตรใดที่อาจสร้างปัญหาตามท้องถนนขึ้นอยู่กับตรรกะทางธุรกิจที่แท้จริง
n-alexander

@ n-alexander คำตอบก็คือสมมติว่าคุณรันสนิปเพตก่อนที่คุณจะเริ่มกระบวนการที่จะสร้างsleep.txtในบางช่วงเวลา ดังนั้นไม่มีสภาพการแข่งขัน คำถามไม่ได้มีอะไรที่เป็นนัยในสถานการณ์ที่คุณมีอยู่ในใจ
maxschlepzig

@maxschlepzig ตรงกันข้าม มันจะไม่สามารถสันนิษฐานได้ ข้อมูลพื้นฐานเกี่ยวกับ
n-alexander

10

เพียงแค่ทำการทดสอบของคุณในwhileวง:

while [ ! -f /tmp/sleep.txt ]; do sleep 1; done
# next command

ดังที่คุณได้เขียนไว้นี่เป็นตรรกะ: ในขณะที่ไฟล์ไม่มีอยู่สคริปต์จะหยุดทำงาน มิฉะนั้นจะทำ แก้ไข?
Mo Gainz

@MoGainz ถูกต้อง จำนวนหลังจากนั้นsleepคือจำนวนวินาทีที่รอในการวนซ้ำแต่ละครั้ง - คุณสามารถเปลี่ยนได้ตามความต้องการของคุณ
jimmij

7

มีปัญหาเล็กน้อยเกี่ยวกับวิธีการพื้นฐานบางอย่างที่inotifywaitให้มา:

  • พวกเขาล้มเหลวที่จะหาไฟล์ที่ได้รับการสร้างขึ้นครั้งแรกเป็นชื่อชั่วคราวแล้วเปลี่ยนชื่อเป็นsleep.txt sleep.txtหนึ่งต้องตรงกับmoved_toเหตุการณ์นอกเหนือจากcreate
  • ชื่อไฟล์สามารถมีอักขระขึ้นบรรทัดใหม่การพิมพ์ชื่อของตัวคั่นบรรทัด newline ไฟล์ไม่เพียงพอที่จะตรวจสอบว่าsleep.txtมีการสร้าง จะเกิดอะไรขึ้นถ้าfoo\nsleep.txt\nbarมีการสร้างไฟล์เช่น
  • จะทำอย่างไรถ้าไฟล์ถูกสร้างขึ้นก่อนที่จะ inotifywaitเริ่มและติดตั้งนาฬิกา จากนั้นinotifywaitจะรอตลอดไปสำหรับไฟล์ที่มีอยู่แล้วที่นี่ คุณต้องตรวจสอบให้แน่ใจว่าไฟล์ไม่ได้อยู่ที่นั่นหลังจากติดตั้งนาฬิกา
  • โซลูชันบางตัวยังคงinotifywaitทำงานอยู่ (อย่างน้อยก็จนกว่าจะสร้างไฟล์อื่น) หลังจากพบไฟล์แล้ว

ในการจัดการกับสิ่งเหล่านั้นคุณสามารถทำได้:

sh -c 'echo "$$" &&
        LC_ALL=C exec inotifywait -me create,moved_to --format=/%f/ . 2>&1' | {
  IFS= read pid &&
    while IFS= read -r line && [ "$line" != "Watches established." ]; do
      : wait for watches to be established
    done
  [ -e sleep.txt ] || [ -L sleep.txt ] || grep -qxF /sleep.txt/ && kill "$pid"
}

โปรดทราบว่าเรากำลังเฝ้าดูการสร้างsleep.txtในไดเรกทอรีปัจจุบัน.(ดังนั้นคุณต้องทำcd /tmp || exitมาก่อนในตัวอย่างของคุณ) ไดเรกทอรีปัจจุบันไม่เปลี่ยนแปลงดังนั้นเมื่อท่อส่งคืนนั้นประสบความสำเร็จจะอยู่sleep.txtในไดเรกทอรีปัจจุบันที่สร้างขึ้น

แน่นอนคุณสามารถแทนที่.ด้วย/tmpด้านบน แต่ในขณะที่inotifywaitกำลังทำงานอยู่/tmpอาจถูกเปลี่ยนชื่อหลายครั้ง (ไม่น่าจะเป็นไปได้/tmpแต่มีบางสิ่งที่ต้องพิจารณาในกรณีทั่วไป) หรือระบบไฟล์ใหม่ที่ติดตั้งอยู่ดังนั้นเมื่อท่อส่งคืน เป็น/tmp/sleep.txtที่ได้รับการสร้างขึ้น แต่/new-name-for-the-original-tmp/sleep.txtแทน /tmpอาจมีการสร้างไดเรกทอรีใหม่ในช่วงเวลานั้นและไดเรกทอรีนั้นจะไม่ได้รับการตรวจสอบดังนั้นการsleep.txtสร้างที่นั่นจะไม่ถูกตรวจจับ


1

คำตอบที่ได้รับการยอมรับใช้งานได้จริง (ขอบคุณ maxschlepzig) แต่ปล่อยให้การตรวจสอบ inotifywait ในพื้นหลังจนกว่าสคริปต์ของคุณออก เพียงตอบตรงกับที่ตรงความต้องการของคุณ (เช่นการรอคอยสำหรับ sleep.txt ที่จะแสดงขึ้นภายใน tmp /) น่าจะเป็นสเตฟานของถ้าไดเรกทอรีที่จะถูกตรวจสอบโดย inotifywait มีการเปลี่ยนแปลงจากจุด (.) เพื่อ '/ tmp'

อย่างไรก็ตามหากคุณยินดีที่จะใช้ไดเรกทอรีชั่วคราวเท่านั้นสำหรับการตั้งค่าสถานะ sleep.txt ของคุณและสามารถเดิมพันได้ว่าไม่มีใครอื่นจะวางไฟล์ใด ๆ ในไดเรกทอรีนั้นเพียงแค่ขอให้ inotifywait ดูไดเรกทอรีนี้เพื่อสร้างไฟล์จะเพียงพอ:

ขั้นตอนที่ 1: สร้างไดเรกทอรีที่คุณจะตรวจสอบ:

directoryToPutSleepFile=$(mktemp -d)

ขั้นตอนที่ 2: ตรวจสอบว่ามีไดเรกทอรีอยู่จริง

until [ -d $directoryToPutSleepFile ]; do sleep 0.1; done

ขั้นตอนที่ 3: รอจนกว่าไฟล์ใด ๆ จะปรากฏขึ้นภายใน $directoryToPutSleepFile

inotifywait -e create --format '%f' --quiet $directoryToPutSleepFile

ไฟล์ที่คุณจะใส่เข้าไป$directoryToPutSleepFileนั้นมีชื่อว่า sleep.txt awake.txt ไม่ว่าอะไรก็ตาม ช่วงเวลาที่ไฟล์ใด ๆถูกสร้างขึ้นใน$directoryToPutSleepFileสคริปต์ของคุณจะดำเนินการต่อผ่านinotifywaitคำสั่ง


1
แต่นั่นอาจพลาดการสร้างไฟล์หากอีกไฟล์ถูกสร้างขึ้นก่อนหน้านี้ทำให้เกิดการsleep.txtสร้างขึ้นระหว่างการเชิญสองครั้งที่ไม่แจ้งเตือน
Stéphane Chazelas

ดูคำตอบของฉันสำหรับวิธีอื่นในการแก้ไข
Stéphane Chazelas

กรุณาลองรหัสของฉัน inotifywait ถูกเรียกเพียงครั้งเดียว เมื่อ sleep.txt ถูกสร้างขึ้น (หรืออ่าน / เขียนถ้ามี) inotifywait เอาต์พุต "sleep.txt" และการดำเนินการจะดำเนินการต่อหลังจากคำสั่ง 'เสร็จสิ้น'
nikolaos

มันถูกเรียกหลายครั้งจนกระทั่งไฟล์ที่เรียกว่าsleep.txtถูกสร้างขึ้น ลองใช้ตัวอย่างtouch /tmp/foo /tmp/sleep.txtจากนั้นหนึ่งอินสแตนซ์ของinotifywaitจะรายงานfooและออกและเมื่อถึงเวลาที่การแจ้งเตือนเริ่มต้นครั้งต่อไป sleep.txt จะมีมานานแล้วดังนั้นการแจ้งเตือนแบบไม่รอจะตรวจพบการสร้าง
Stéphane Chazelas

คุณถูกต้องเกี่ยวกับการร้องขอหลายรายการ: การร้องขอหนึ่งครั้งสำหรับทุกไฟล์ที่สร้างภายใต้ / tmp ฉันปรับปรุงคำตอบของฉัน ขอบคุณสำหรับความคิดเห็นของคุณ
nikolaos

0

โดยทั่วไปแล้วมันค่อนข้างยากที่จะทำอะไร แต่การทดสอบ / การนอนหลับง่าย ๆ เชื่อถือได้ ปัญหาหลักคือการทดสอบจะแข่งกับการสร้างไฟล์ - ยกเว้นว่าการทดสอบซ้ำแล้วซ้ำอีกเหตุการณ์การสร้างสามารถพลาด นี่คือทางออกที่ดีที่สุดของคุณในกรณีส่วนใหญ่ที่คุณจะใช้ Shell เพื่อเริ่มต้นด้วย:

#!/bin/bash
while [[ ! -e /tmp/file ]] ; do
    sleep 1
done

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


2
นี่เป็นเพียงซ้ำกับคำตอบของ jimmij
maxschlepzig

0

ขึ้นอยู่กับคำตอบที่ได้รับการยอมรับของ maxschlepzig (และความคิดจากคำตอบที่ได้รับการยอมรับใน/superuser/270529/monitoring-a-file-until-a-string-is-found ) ผมเสนอต่อไปนี้ดีขึ้น ( ในมุมมองของฉัน) คำตอบที่ยังสามารถทำงานกับหมดเวลา:

# Enable pipefail, so if the left side of the pipe fails it does not get silently ignored
set -o pipefail
( timeout 120 inotifywait -e create,open --format '%f' --quiet /tmp --monitor & ) | while read i; do if [ "$i" == 'sleep.txt' ]; then break; fi; done
EXIT_STATUS=$?
if [ "${EXIT_STATUS}" == '124' ]; then
  echo "Timeout happened"
fi

ในกรณีที่ไฟล์ไม่ถูกสร้าง / เปิดภายในระยะหมดเวลาที่กำหนดสถานะการออกคือ 124 (ตามเอกสารการหมดเวลา (หน้าคน)) ในกรณีที่ได้รับการสร้าง / เปิดจากนั้นสถานะการออกเป็น 0 (สำเร็จ)

ใช่ inotifywait จะทำงานใน sub-shell ด้วยวิธีนี้และ sub-shell นั้นจะทำงานให้เสร็จสิ้นเมื่อการหมดเวลาเกิดขึ้นหรือเมื่อสคริปต์หลักจบการทำงาน

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