หลีกเลี่ยงการรอไม่ว่างในการทุบตีโดยไม่มีคำสั่ง sleep


19

ฉันรู้ว่าฉันสามารถรอเงื่อนไขที่จะกลายเป็นจริงในการทุบตีโดยทำ:

while true; do
  test_condition && break
  sleep 1
done

แต่จะสร้างกระบวนการย่อย 1 กระบวนการในการทำซ้ำแต่ละครั้ง (sleep) ฉันสามารถหลีกเลี่ยงพวกเขาโดยทำ:

while true; do
  test_condition && break
done

แต่มันใช้ CPU เป็นจำนวนมาก (กำลังรอไม่ว่าง) เพื่อหลีกเลี่ยงกระบวนการย่อยและการรอคอยไม่ว่างฉันจึงตัดสินใจแก้ปัญหาดังกล่าว แต่ฉันพบว่ามันน่าเกลียด

my_tmp_dir=$(mktemp -d --tmpdir=/tmp)    # Create a unique tmp dir for the fifo.
mkfifo $my_tmp_dir/fifo                  # Create an empty fifo for sleep by read.
exec 3<> $my_tmp_dir/fifo                # Open the fifo for reading and writing.

while true; do
  test_condition && break
  read -t 1 -u 3 var                     # Same as sleep 1, but without sub-process.
done

exec 3<&-                                # Closing the fifo.
rm $my_tmp_dir/fifo; rmdir $my_tmp_dir   # Cleanup, could be done in a trap.

หมายเหตุ: ในกรณีทั่วไปฉันไม่สามารถใช้งานได้read -t 1 varโดยไม่ต้องใช้Fifo เพราะจะใช้ stdin และจะไม่ทำงานหาก stdin ไม่ใช่เทอร์มินัลหรือท่อ

ฉันสามารถหลีกเลี่ยงกระบวนการย่อยและการรอไม่ว่างอย่างสง่างามกว่าได้หรือไม่?


1
trueเป็น builtin และไม่สร้างกระบวนการย่อยใน bash การรอคอยที่วุ่นวายจะไม่ดีตลอดไป
จอร์แดน

@joranm: คุณถูกต้องเกี่ยวกับtrueคำถามการปรับปรุง
jfg956

ทำไมไม่ลองเล่น Fifo ล่ะ? read -t 1 varง่ายดาย
ott--

@ott: ถูกต้อง แต่จะใช้ stdin นอกจากนี้มันจะไม่ทำงานหาก stdin ไม่ใช่เทอร์มินัลหรือไปป์
jfg956

หากการบำรุงรักษาเป็นปัญหาฉันขอแนะนำอย่างยิ่งให้ดำเนินการsleepตามตัวอย่างแรก อย่างที่สองในขณะที่มันอาจใช้งานได้จะไม่ใช่เรื่องง่ายสำหรับทุกคนที่จะปรับในอนาคต รหัสง่าย ๆ มีศักยภาพที่ใหญ่กว่าสำหรับความปลอดภัย
Kusalananda

คำตอบ:


17

ในเวอร์ชันที่ใหม่กว่าbash(อย่างน้อย v2) บิวอินอาจถูกโหลด (ผ่านenable -f filename commandname) ที่รันไทม์ จำนวนบิวด์ที่สามารถโหลดได้ดังกล่าวยังถูกแจกจ่ายกับแหล่งทุบตีและsleepอยู่ในหมู่พวกเขาด้วย ความพร้อมใช้งานอาจแตกต่างจากระบบปฏิบัติการไปยังระบบปฏิบัติการ (และแม้กระทั่งเครื่องต่อเครื่อง) แน่นอน ตัวอย่างเช่นใน openSUSE, builtins bash-loadablesเหล่านี้จะกระจายผ่านแพคเกจ

แก้ไข: ชื่อแพคเกจแก้ไขเพิ่มรุ่นทุบตีขั้นต่ำ


ว้าวนี่คือสิ่งที่ฉันกำลังมองหาและฉันเรียนรู้บางอย่างเกี่ยวกับตัวเครื่องที่ใส่ได้: +1 ฉันจะลองสิ่งนี้ แต่มันก็เป็นคำตอบที่ดีที่สุด
jfg956

1
มันได้ผล ! บนเดเบียนแพคเกจเป็น bash-builtins มันมีแค่แหล่งที่มาและ Makefile ต้องแก้ไข แต่ฉันก็สามารถติดตั้งsleepในตัวได้ ขอบคุณ
jfg956

9

การสร้างกระบวนการย่อยจำนวนมากเป็นสิ่งที่ไม่ดีในลูปด้านใน การสร้างหนึ่งsleepกระบวนการต่อวินาทีก็โอเค ไม่มีอะไรผิดปกติกับ

while ! test_condition; do
  sleep 1
done

หากคุณต้องการหลีกเลี่ยงกระบวนการภายนอกคุณไม่จำเป็นต้องเปิดฟีเจอร์

my_tmpdir=$(mktemp -d)
trap 'rm -rf "$my_tmpdir"' 0
mkfifo "$my_tmpdir/f"

while ! test_condition; do
  read -t 1 <>"$my_tmpdir/f"
done

คุณพูดถูกเกี่ยวกับกระบวนการต่อวินาทีเป็นถั่วลิสง (แต่คำถามของฉันเกี่ยวกับการหาวิธีลบมัน) เกี่ยวกับเวอร์ชั่นที่สั้นกว่ามันดีกว่าของฉันดังนั้น +1 (แต่ฉันลบสิ่งmkdirที่มันทำโดยmktemp(ถ้าไม่ใช่มันเป็นเงื่อนไขการแข่งขัน)) ความจริงเกี่ยวกับสิ่งwhile ! test_condition;ที่ดีกว่าโซลูชันเริ่มต้นของฉัน
jfg956

7

ฉันเพิ่งมีความต้องการที่จะทำเช่นนี้ ฉันมาพร้อมกับฟังก์ชั่นต่อไปนี้ที่จะช่วยให้ bash นอนหลับตลอดไปโดยไม่ต้องเรียกโปรแกรมภายนอก:

snore()
{
    local IFS
    [[ -n "${_snore_fd:-}" ]] || { exec {_snore_fd}<> <(:); } 2>/dev/null ||
    {
        # workaround for MacOS and similar systems
        local fifo
        fifo=$(mktemp -u)
        mkfifo -m 700 "$fifo"
        exec {_snore_fd}<>"$fifo"
        rm "$fifo"
    }
    read ${1:+-t "$1"} -u $_snore_fd || :
}

หมายเหตุ: ก่อนหน้านี้ฉันโพสต์รุ่นนี้ซึ่งจะเปิดและปิดไฟล์ descriptor ทุกครั้ง แต่ฉันพบว่าในบางระบบที่ทำเช่นนี้หลายร้อยครั้งต่อวินาทีจะล็อคในที่สุด ดังนั้นโซลูชันใหม่จะเก็บไฟล์ descriptor ระหว่างการเรียกไปยังฟังก์ชัน Bash จะทำให้สะอาดขึ้นเมื่อออกไป

สิ่งนี้สามารถเรียกได้เหมือน / bin / sleep และมันจะเข้าสู่โหมดสลีปตามเวลาที่ร้องขอ เรียกว่าไม่มีพารามิเตอร์มันจะวางตลอดไป

snore 0.1  # sleeps for 0.1 seconds
snore 10   # sleeps for 10 seconds
snore      # sleeps forever

มีรายละเอียดมากเกินไปในบล็อกของฉันที่นี่


1
บล็อกที่ยอดเยี่ยม อย่างไรก็ตามฉันไปที่นั่นเพื่อค้นหาคำอธิบายว่าทำไมread -t 10 < <(:)กลับมาทันทีในขณะที่read -t 10 <> <(:)รอเต็ม 10 วินาที แต่ฉันยังไม่ได้รับ
อาเมียร์

ในread -t 10 <> <(:)สิ่งที่<>ยืนหยัดเพื่อ?
CodeMedic

<> เปิดตัวให้คำอธิบายไฟล์สำหรับการอ่านและการเขียนแม้ว่าการทดแทนกระบวนการพื้นฐาน <(:) อนุญาตเฉพาะการอ่าน นี่เป็นแฮ็คที่ทำให้ Linux และ Linux โดยเฉพาะสมมติว่ามีใครบางคนอาจเขียนถึงมันดังนั้นการอ่านจะหยุดรออินพุตที่จะไม่มาถึง มันจะไม่ทำสิ่งนี้กับระบบ BSD ซึ่งในกรณีนี้การแก้ปัญหาจะเริ่มขึ้น
กลอน

3

ในksh93หรือmksh, sleepเป็น builtin bashเปลือกจึงเป็นทางเลือกที่อาจจะมีการใช้เปลือกหอยเหล่านั้นแทน

zshนอกจากนี้ยังมีzselectในตัว (เต็มไปด้วยzmodload zsh/zselect) zselect -t <n>ที่สามารถนอนหลับสำหรับจำนวนที่กำหนดของร้อยของวินาทีกับ


2

ตามที่ผู้ใช้yoiกล่าวว่าหากในสคริปต์ของคุณเปิดstdinดังนั้นแทนที่จะนอน 1คุณสามารถใช้:

read -t 1 3<&- 3<&0 <&3

ใน Bash เวอร์ชั่น 4.1 และใหม่กว่าคุณสามารถใช้เลขทศนิยมเช่น read -t 0.3 ...

หากในสคริปต์stdinถูกปิด (เรียกว่าสคริปต์my_script.sh < /dev/null &) คุณต้องใช้ตัวอธิบายเปิดอีกอันหนึ่งซึ่งไม่ได้สร้างเอาต์พุตเมื่อมีการดำเนินการอ่านเช่น stdout :

read -t 1 <&1 3<&- 3<&0 <&3

หากในสคริปต์ descriptor ทั้งหมดถูกปิด ( stdin , stdout , stderr ) (เช่นเพราะเรียกว่าเป็น daemon) คุณต้องค้นหาไฟล์ที่มีอยู่ซึ่งไม่ได้สร้างเอาต์พุต:

read -t 1 </dev/tty10 3<&- 3<&0 <&3

read -t 1 3<&- 3<&0 <&3read -t 0เป็นเช่นเดียวกับ เป็นเพียงการอ่านจาก stdin พร้อมการหมดเวลา
Stéphane Chazelas

1

สิ่งนี้ทำงานได้จากเชลล์การเข้าสู่ระบบรวมถึงเชลล์ที่ไม่มีการโต้ตอบ

#!/bin/sh

# to avoid starting /bin/sleep each time we call sleep, 
# make our own using the read built in function
xsleep()
{
  read -t $1 -u 1
}

# usage
xsleep 3

สิ่งนี้ใช้ได้กับ Mac OS X v10.12.6
b01

1
ไม่แนะนำ หากสคริปต์หลายตัวใช้สิ่งนี้ในเวลาเดียวกันพวกเขาทั้งหมดจะได้รับ SIGSTOP'ed เพราะพวกเขาพยายามอ่าน stdin stdin ของคุณถูกบล็อกในขณะที่รอ อย่าใช้ stdin สำหรับสิ่งนี้ คุณต้องการไฟล์อธิบายที่แตกต่างใหม่
ทำให้เป็นปกติ

1
@Nadadize มีคำตอบอื่นที่นี่ ( unix.stackexchange.com/a/407383/147685 ) ที่เกี่ยวข้องกับข้อกังวลของการใช้ descriptor ไฟล์ฟรี read -t 10 <> <(:)รุ่นต่ำสุดเปลือย
อาเมียร์

0

คุณต้องการ Fifo จริงๆหรือ? การเปลี่ยนเส้นทาง stdin ไปยังตัวอธิบายไฟล์อื่นควรใช้งานได้เช่นกัน

{
echo line | while read line; do
   read -t 1 <&3
   echo "$line"
done
} 3<&- 3<&0

แรงบันดาลใจจาก: อ่านอินพุตใน bash ภายใน a while loop


1
นี่ไม่ได้หลับ แต่ยังคงกิน stdin จากเครื่องเทอร์มินัล
jfg956

0

การปรับปรุงเล็กน้อยในการแก้ปัญหาดังกล่าวข้างต้น (ซึ่งฉันได้ตามนี้)

bash_sleep() {
    read -rt "${1?Specify sleep interval in seconds}" -u 1 <<<"" || :;
}

# sleep for 10 seconds
bash_sleep 10

ลดความต้องการฟีเจอร์และทำให้ไม่มีการทำความสะอาด


1
ไม่แนะนำ หากสคริปต์หลายตัวใช้สิ่งนี้ในเวลาเดียวกันพวกเขาทั้งหมดจะได้รับ SIGSTOP'ed เพราะพวกเขาพยายามอ่าน stdin stdin ของคุณถูกบล็อกในขณะที่รอ อย่าใช้ stdin สำหรับสิ่งนี้ คุณต้องการไฟล์อธิบายที่แตกต่างใหม่
ทำให้เป็นปกติ

@ ยกเลิกไม่คิดอย่างนั้น คุณช่วยอธิบายเพิ่มเติมหรือชี้ให้ฉันไปยังแหล่งข้อมูลที่ฉันสามารถอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ได้
CodeMedic

@CodeMedic มีคำตอบอื่นที่นี่ ( unix.stackexchange.com/a/407383/147685 ) ที่เกี่ยวข้องกับข้อกังวลของการใช้อธิบายไฟล์ฟรี read -t 10 <> <(:)รุ่นต่ำสุดเปลือย
อาเมียร์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.