Hindari sibuk menunggu di bash, tanpa perintah tidur

19

Saya tahu saya bisa menunggu dengan syarat untuk menjadi kenyataan di bash dengan melakukan:

while true; do
  test_condition && break
  sleep 1
done

Tapi itu menciptakan 1 sub-proses di setiap iterasi (tidur). Saya bisa menghindarinya dengan melakukan:

while true; do
  test_condition && break
done

Tetapi menggunakan banyak CPU (sibuk menunggu). Untuk menghindari sub-proses dan menunggu sibuk, saya datang dengan solusi di bawah, tapi saya merasa jelek:

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.

Catatan: dalam kasus umum, saya tidak bisa begitu saja menggunakan read -t 1 var tanpa fifo, karena akan mengkonsumsi stdin, dan tidak akan berfungsi jika stdin bukan terminal atau pipa.

Bisakah saya menghindari sub-proses dan sibuk menunggu dengan cara yang lebih elegan?

jfg956
sumber
1
trueadalah builtin dan tidak membuat sub proses dalam bash. penantian yang sibuk akan selalu buruk.
jordanm
@ joranm: Anda benar tentang true, pertanyaan diperbarui.
jfg956
Kenapa tidak tanpa fifo? Cukup read -t 1 var.
ott--
@ott: Anda benar, tetapi ini akan memakan stdin. Juga, itu tidak akan berfungsi jika stdin bukan terminal atau pipa.
jfg956
Jika perawatan adalah masalah, saya akan sangat menyarankan pergi dengan sleepseperti pada contoh pertama. Yang kedua, meskipun mungkin berhasil, tidak akan mudah bagi siapa pun untuk menyesuaikan diri di masa depan. Kode sederhana juga memiliki potensi lebih besar untuk aman.
Kusalananda

Jawaban:

17

Dalam versi terbaru bash(setidaknya v2), builtin dapat dimuat (via enable -f filename commandname) saat runtime. Sejumlah builtin yang dapat dimuat tersebut juga didistribusikan dengan sumber bash, dan sleepada di antara mereka. Ketersediaan mungkin berbeda dari OS ke OS (dan bahkan mesin ke mesin), tentu saja. Sebagai contoh, pada openSUSE, bawaan ini didistribusikan melalui paketbash-loadables .

Edit: perbaiki nama paket, tambahkan versi bash minimum.

Ansgar Esztermann
sumber
Wow, ini yang saya cari, dan saya pasti belajar sesuatu tentang builtable: +1. Saya akan mencoba ini, namun itu adalah jawaban terbaik.
jfg956
1
Berhasil ! Pada debian, paketnya adalah bash-builtin. Itu hanya mencakup sumber dan Makefile harus diedit, tetapi saya dapat menginstal sleepsebagai builtin. Terima kasih.
jfg956
9

Membuat banyak subproses adalah hal yang buruk di loop dalam. Membuat satu sleepproses per detik adalah OK. Tidak ada yang salah dengan itu

while ! test_condition; do
  sleep 1
done

Jika Anda benar-benar ingin menghindari proses eksternal, Anda tidak perlu membiarkan fifo tetap terbuka.

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
Gilles 'SANGAT berhenti menjadi jahat'
sumber
Anda benar tentang proses per detik menjadi kacang (tapi pertanyaan saya adalah menemukan cara untuk menghapusnya). Tentang versi yang lebih pendek, Ini lebih bagus daripada versi saya, jadi +1 (tapi saya menghapus mkdirseperti yang dilakukan oleh mktemp(jika tidak, ini adalah kondisi balapan)). Juga benar tentang while ! test_condition;yang lebih bagus daripada solusi awal saya.
jfg956
7

Saya baru-baru ini perlu melakukan ini. Saya datang dengan fungsi berikut yang akan memungkinkan bash untuk tidur selamanya tanpa memanggil program eksternal apa pun:

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 || :
}

CATATAN: Saya sebelumnya memposting versi ini yang akan membuka dan menutup deskriptor file setiap kali, tetapi saya menemukan bahwa pada beberapa sistem melakukan ini ratusan kali per detik akhirnya akan terkunci. Dengan demikian solusi baru membuat deskriptor file antara panggilan ke fungsi. Bash akan membersihkannya saat keluar.

Ini bisa disebut seperti / bin / sleep, dan itu akan tidur untuk waktu yang diminta. Disebut tanpa parameter, itu akan menggantung selamanya.

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

Ada langganan dengan rincian berlebihan di blog saya di sini

baut
sumber
1
Entri blog yang sangat baik. Namun, saya pergi ke sana mencari penjelasan mengapa read -t 10 < <(:)kembali segera sambil read -t 10 <> <(:)menunggu 10 detik penuh, tapi saya masih belum mengerti.
Amir
Dalam read -t 10 <> <(:)apa <>stand for?
CodeMedic
<> membuka file descriptor untuk membaca dan menulis, meskipun proses substitusi yang mendasarinya <(:) hanya memungkinkan membaca. Ini adalah peretasan yang menyebabkan Linux, dan Linux secara khusus, untuk menganggap seseorang mungkin menulis untuk itu, jadi membaca akan menunggu masukan yang tidak akan pernah tiba. Ini tidak akan melakukan ini pada sistem BSD, dalam hal ini solusinya akan masuk.
baut
3

Di ksh93atau mksh, sleepadalah shell builtin, jadi alternatifnya mungkin menggunakan shell itu alih-alih bash.

zshjuga memiliki zselectbuiltin (sarat dengan zmodload zsh/zselect) yang dapat tidur selama beberapa ratus detik zselect -t <n>.

Pengamat
sumber
2

Seperti kata pengguna yoi , jika dalam skrip Anda dibuka stdin , maka alih-alih tidur 1 Anda cukup menggunakan:

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

Dalam Bash versi 4.1 dan yang lebih baru Anda dapat menggunakan nomor float, mis read -t 0.3 ...

Jika dalam skrip, stdin ditutup (skrip dipanggil my_script.sh < /dev/null &), maka Anda perlu menggunakan deskriptor terbuka lainnya, yang tidak menghasilkan output saat membaca dijalankan, mis. stdout :

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

Jika dalam naskah semua deskriptor ditutup ( stdin , stdout , stderr ) (misalnya karena disebut sebagai daemon), maka Anda perlu menemukan file yang ada yang tidak menghasilkan output:

read -t 1 </dev/tty10 3<&- 3<&0 <&3
Mysak
sumber
read -t 1 3<&- 3<&0 <&3sama dengan read -t 0. Hanya membaca dari stdin dengan timeout.
Stéphane Chazelas
1

Ini berfungsi dari shell login dan juga shell non-interaktif.

#!/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
Omar
sumber
Ini juga bekerja pada Mac OS X v10.12.6
b01
1
Ini tidak disarankan. Jika banyak skrip menggunakan ini pada saat yang sama maka mereka semua mendapatkan SIGSTOP karena mereka semua mencoba membaca stdin. Stdin Anda diblokir saat ini menunggu. Jangan gunakan stdin untuk ini. Anda ingin deskriptor file baru yang berbeda.
Normadize
1
@Normadize Ada jawaban lain di sini ( unix.stackexchange.com/a/407383/147685 ) yang berkaitan dengan keprihatinan menggunakan deskriptor file gratis. Versi minimumnya adalah read -t 10 <> <(:).
Amir
0

Apakah Anda benar-benar membutuhkan fifo? Mengarahkan stdin ke deskriptor file lain juga harus berfungsi.

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

Terinspirasi oleh: Baca input dalam bash di dalam loop sementara

yoi
sumber
1
Ini bukan melakukan tidur, ini masih mengkonsumsi stdin dari terminal.
jfg956
0

Sedikit perbaikan pada solusi yang disebutkan di atas (yang saya telah berdasarkan ini).

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

# sleep for 10 seconds
bash_sleep 10

Mengurangi kebutuhan untuk fifo dan karenanya tidak perlu melakukan pembersihan.

CodeMedic
sumber
1
Ini tidak disarankan. Jika banyak skrip menggunakan ini pada saat yang sama maka mereka semua mendapatkan SIGSTOP karena mereka semua mencoba membaca stdin. Stdin Anda diblokir saat ini menunggu. Jangan gunakan stdin untuk ini. Anda ingin deskriptor file baru yang berbeda.
Normadize
@Normadize Tidak pernah memikirkan itu; tolong bisa jelaskan atau arahkan saya ke sumber di mana saya bisa membaca lebih lanjut tentang itu.
CodeMedic
@CodeMedic Ada jawaban lain di sini ( unix.stackexchange.com/a/407383/147685 ) yang berkaitan dengan keprihatinan menggunakan deskriptor file gratis. Versi minimumnya adalah read -t 10 <> <(:).
Amir