อย่างไรก็ตามฉันต้องการพูดคุยเกี่ยวกับวิธีการที่แตกต่างกันและกึ่งใช้งานได้และการใช้ps
คำเตือนที่พวกเขามีเพราะฉันเห็นคนใช้พวกเขา
คำตอบนี้เป็นคำตอบของ"ทำไมไม่ใช้ps
และgrep
จัดการกับการล็อกในเชลล์"
วิธีการแตก # 1
ขั้นแรกให้แนวทางในอีกคำตอบหนึ่งที่มี upvotes ไม่กี่คนแม้ว่าจะไม่ได้ทำงาน (และไม่สามารถทำได้) และไม่เคยทดสอบอย่างชัดเจน:
running_proc=$(ps -C bash -o pid=,cmd= | grep my_script);
if [[ "$running_proc" != "$$ bash my_script" ]]; do
echo Already locked
exit 6
fi
มาแก้ไขข้อผิดพลาดทางไวยากรณ์และps
ข้อโต้แย้งที่ขาดและได้รับ:
running_proc=$(ps -C bash -o pid,cmd | grep "$0");
echo "$running_proc"
if [[ "$running_proc" != "$$ bash $0" ]]; then
echo Already locked
exit 6
fi
สคริปต์นี้จะมักจะออกจาก 6 ทุกครั้งไม่ว่าคุณจะใช้มัน
ถ้าคุณเรียกมันด้วย./myscript
แล้วps
การส่งออกจะเป็นเพียง12345 -bash
ซึ่งไม่ตรงกับสตริงที่จำเป็น12345 bash ./myscript
เพื่อที่จะล้มเหลว
ถ้าคุณใช้มันbash myscript
สิ่งต่าง ๆ น่าสนใจยิ่งขึ้น กระบวนการทุบตีส้อมที่จะเรียกใช้ท่อและเด็กเปลือกทำงานและps
grep
ทั้งเปลือกดั้งเดิมและเปลือกลูกจะปรากฏขึ้นในps
ผลลัพธ์บางอย่างเช่นนี้:
25793 bash myscript
25795 bash myscript
นั่นไม่ใช่เอาต์พุตที่คาดไว้$$ bash $0
ดังนั้นสคริปต์ของคุณจะออก
วิธีการแตก # 2
ในความเป็นธรรมกับผู้ใช้ที่เขียนวิธีการที่ไม่เหมาะสม # 1 ฉันทำสิ่งที่คล้ายกันเมื่อฉันลองทำสิ่งนี้เป็นครั้งแรก:
if otherpids="$(pgrep -f "$0" | grep -vFx "$$")" ; then
echo >&2 "There are other copies of the script running; exiting."
ps >&2 -fq "${otherpids//$'\n'/ }" # -q takes about a tenth the time as -p
exit 1
fi
นี้เกือบงาน แต่ความจริงแล้วการฟอร์กเพื่อให้ท่อวิ่งออกไป ดังนั้นอันนี้ก็จะออกเช่นกัน
แนวทางที่ไม่น่าเชื่อถือ # 3
pids_this_script="$(pgrep -f "$0")"
if not_this_process="$(echo "$pids_this_script" | grep -vFx "$$")"; then
echo >&2 "There are other copies of this script running; exiting."
ps -fq "${not_this_process//$'\n'/ }"
exit 1
fi
รุ่นนี้หลีกเลี่ยงการฟอร์กปัญหาในวิธี # 2 โดยรับ PID ทั้งหมดที่มีสคริปต์ปัจจุบันในอาร์กิวเมนต์บรรทัดคำสั่งของพวกเขาแล้วกรอง pidlist นั้นแยกจากกันเพื่อละเว้น PID ของสคริปต์ปัจจุบัน
สิ่งนี้อาจใช้งานได้ ... หากไม่มีกระบวนการอื่นใดที่มีบรรทัดคำสั่งที่ตรงกับ$0
และการให้สคริปต์จะเรียกว่าวิธีเดียวกันเสมอ (เช่นถ้ามันถูกเรียกด้วยเส้นทางสัมพัทธ์แล้วเส้นทางสัมบูรณ์อินสแตนซ์หลังจะไม่สังเกตเห็นอดีต )
แนวทางที่ไม่น่าเชื่อถือ # 4
ดังนั้นถ้าเราข้ามการตรวจสอบบรรทัดคำสั่งแบบเต็มเนื่องจากอาจไม่ได้ระบุว่าสคริปต์กำลังทำงานจริงและตรวจสอบlsof
แทนเพื่อค้นหากระบวนการทั้งหมดที่มีสคริปต์นี้เปิดอยู่
ใช่วิธีนี้ไม่เลวจริง ๆ :
if otherpids="$(lsof -t "$0" | grep -vFx "$$")"; then
echo >&2 "Error: There are other processes that have this script open - most likely other copies of the script running. Exiting to avoid conflicts."
ps >&2 -fq "${otherpids//$'\n'/ }"
exit 1
fi
แน่นอนหากสำเนาของสคริปต์ทำงานอยู่อินสแตนซ์ใหม่จะเริ่มต้นได้ดีและคุณจะมีสองชุดที่ทำงานอยู่
หรือหากสคริปต์ที่ใช้งานได้รับการแก้ไข (เช่นกับ Vim หรือ a git checkout
) ดังนั้นเวอร์ชั่น "ใหม่" ของสคริปต์จะเริ่มต้นโดยไม่มีปัญหาเนื่องจากทั้ง Vim และgit checkout
ผลลัพธ์เป็นไฟล์ใหม่ (inode ใหม่) แทนที่ อันเก่า.
อย่างไรก็ตามหากสคริปต์ไม่เคยถูกแก้ไขและไม่เคยคัดลอกดังนั้นเวอร์ชั่นนี้ค่อนข้างดี ไม่มีเงื่อนไขการแข่งขันเนื่องจากไฟล์สคริปต์ต้องเปิดอยู่ก่อนที่จะถึงการตรวจสอบ
อาจยังมีผลบวกปลอมหากกระบวนการอื่นเปิดไฟล์สคริปต์ แต่โปรดทราบว่าแม้ว่าจะเปิดให้แก้ไขใน Vim ได้ แต่ vim ไม่ได้เปิดไฟล์สคริปต์ไว้จริง ๆ ดังนั้นจะไม่มีผลบวกปลอม
แต่จำไว้ว่าอย่าใช้วิธีนี้หากสคริปต์อาจถูกแก้ไขหรือคัดลอกเนื่องจากคุณจะได้รับเนกาทีฟที่ผิดพลาดเช่นมีหลายอินสแตนซ์ที่ทำงานพร้อมกันดังนั้นความจริงที่ว่าการแก้ไขด้วย Vim นั้นไม่ได้ให้ผลเชิงบวกที่ผิด ถึงคุณ. ผมพูดถึงมัน แต่เนื่องจากวิธีการ # 3 ไม่ให้ผลบวกปลอม (เช่นปฏิเสธที่จะเริ่มต้น) ถ้าคุณมีสคริปต์ที่เปิดกับกลุ่ม
แล้วจะทำอย่างไรดี?
คำตอบด้านบนลงมติให้คำถามนี้ให้เป็นวิธีการที่ดีที่เป็นของแข็ง
บางทีคุณสามารถเขียนดีกว่า ... แต่ถ้าคุณไม่เข้าใจปัญหาและข้อแม้ทั้งหมดด้วยวิธีการข้างต้นคุณไม่น่าจะเขียนวิธีการล็อคที่หลีกเลี่ยงพวกเขาทั้งหมด
kill
แก้ไข และดูเหมือนว่าเป็นวิธีปฏิบัติที่ดีในการจัดเก็บ pid ของตัวเองใน lockfile แทนที่จะแค่แตะมัน