Uyku komutu olmadan bash'da meşgul beklemekten kaçınma


19

Ben bash gerçek olması için bir koşul bekleyebilirsiniz biliyorum:

while true; do
  test_condition && break
  sleep 1
done

Ancak her bir yinelemede (uyku) 1 alt işlem oluşturur. Yaparak onlardan kaçınabilirim:

while true; do
  test_condition && break
done

Ama çok fazla CPU kullanıyor (meşgul bekliyor). Alt süreçlerden ve meşgul beklemeden kaçınmak için, aşağıdaki çözümü buldum, ama çirkin buluyorum:

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.

Not: genel durumda, sadece read -t 1 varfifo olmadan kullanamam , çünkü stdin tüketecek ve stdin bir terminal veya boru değilse işe yaramayacaktır.

Alt süreçleri ve meşgul beklemeyi daha zarif bir şekilde önleyebilir miyim?


1
truebir yerleşiktir ve bash içinde bir alt süreç oluşturmaz. meşgul beklemek her zaman kötü olacaktır.
jordanm

@ joranm: haklısın true, soru güncellendi.
jfg956

Neden fifo olmadan olmasın? Basitçe read -t 1 var.
ott--

@ott: haklısın, ama bu stdin'i tüketecek. Ayrıca, stdin bir terminal veya boru değilse de çalışmaz.
jfg956 17:13

Sürdürülebilirlik bir sorunsa, sleepilk örnekteki gibi devam etmenizi şiddetle tavsiye ederim . İkincisi, çalışabilirken, gelecekte kimsenin ayarlaması kolay olmayacak. Basit kodun güvenli olma potansiyeli de daha yüksektir .
Kusalananda

Yanıtlar:


17

bash(En azından v2) ' nin yeni sürümlerinde yerleşikler enable -f filename commandnameçalışma zamanında (üzerinden ) yüklenebilir . Bu tür yüklenebilir birçok yapı da bash kaynakları ile dağıtılır ve sleepbunlar arasındadır. Kullanılabilirlik elbette işletim sisteminden işletim sistemine (ve hatta makineden makineye) değişebilir. Örneğin, openSUSE ürününde bu yerleşikler paket üzerinden dağıtılır bash-loadables.

Düzenleme: paket adını düzeltin, minimum bash sürümünü ekleyin.


Vay canına, aradığım şey bu ve kesinlikle yüklenebilir yapı hakkında bir şeyler öğreniyorum: +1. Bunu deneyeceğim, ama yine de en iyi cevap bu.
jfg956

1
İşe yarıyor ! Debian'da paket bash yapılıdır. Yalnızca kaynakları içerir ve Makefile düzenlenmelidir, ancak sleepyerleşik olarak yükleyebildim . Teşekkürler.
jfg956

9

Çok sayıda alt işlem oluşturmak bir iç döngüde kötü bir şeydir. sleepSaniyede bir işlem oluşturmak sorun yaratmaz. İle ilgili yanlış bir şey yok

while ! test_condition; do
  sleep 1
done

Dış işlemden gerçekten kaçınmak istiyorsanız, fifoyu açık tutmanız gerekmez.

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

Saniyede bir işlem fıstık olmak konusunda haklısın (ama sorum, onu kaldırmak için bir yol bulmakla ilgiliydi). Kısa versiyonu hakkında, benimkinden daha güzel, bu yüzden +1 (ama ben mkdirtarafından yapıldığı gibi kaldırdım mktemp(eğer değilse, bir yarış koşulu)). Ayrıca while ! test_condition;benim ilk çözüm daha güzel olan hakkında doğru .
jfg956

7

Son zamanlarda bunu yapmaya ihtiyacım vardı. Ben bash herhangi bir harici program çağırmadan sonsuza kadar uyku sağlayacak aşağıdaki fonksiyonu ile geldi:

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

NOT: Daha önce bunun her zaman dosya tanımlayıcısını açıp kapatacak bir sürümünü yayınladım, ancak bunu yapan bazı sistemlerde yüzlerce kez bir saniye sonunda kilitleneceğini buldum. Bu nedenle yeni çözüm, dosya tanımlayıcısını işleve yapılan çağrılar arasında tutar. Bash yine de çıkışta temizler.

Bu / bin / sleep gibi çağrılabilir ve istenen süre boyunca uyuyacaktır. Parametresiz olarak adlandırıldığında, sonsuza kadar askıda kalacaktır.

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

Buradaki blogumda çok fazla ayrıntı içeren bir yazı var


1
Mükemmel blog girişi. Ancak, oraya tam 10 saniye beklerken neden read -t 10 < <(:)hemen geri döndüğüne dair bir açıklama arıyorum read -t 10 <> <(:), ama hala anlamıyorum.
Amir

Gelen read -t 10 <> <(:)yaptığı nedir <>ne demek?
CodeMedic

<> dosya tanımlayıcıyı okuma ve yazma için açar, ancak temel işlem değişikliği <(:) yalnızca okumaya izin verir. Bu, Linux ve özellikle Linux'un birinin yazabileceğini varsaymasına neden olan bir hack'tir, bu nedenle okuma, asla ulaşmayacak girdileri beklemektedir. BSD sistemlerinde bunu yapmayacak, bu durumda geçici çözüm devreye girecek.
cıvata

3

Olarak ksh93ya da mksh, sleepalternatif yerine bu kabukları kullanmak olabilir, böylece, bir kabuk yerleşiğidir bash.

zshayrıca belirli bir yüzlerce saniye boyunca uyuyabilecek bir zselectyerleşik (yüklü zmodload zsh/zselect) vardır zselect -t <n>.


2

Kullanıcı yoi'nin dediği gibi, betiğinizde stdin açılmışsa , uyku 1 yerine şunları kullanabilirsiniz:

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

Bash sürüm 4.1 ve daha yenisinde kayan sayı kullanabilirsiniz, ör. read -t 0.3 ...

Bir komut dosyasında stdin kapalıysa (komut dosyası çağrılır my_script.sh < /dev/null &), o zaman okuma yürütüldüğünde çıktı üretmeyen başka bir açık tanımlayıcı kullanmanız gerekir , örn. stdout :

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

Bir komut dosyasında tüm tanımlayıcı kapalı ise ( stdin , stdout , stderr ) (örneğin daemon olarak adlandırılır), o zaman çıktı üretmeyen var olan bir dosyayı bulmanız gerekir:

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

read -t 1 3<&- 3<&0 <&3ile aynıdır read -t 0. Sadece zaman aşımı ile stdin'den okuyor.
Stéphane Chazelas

1

Bu, bir giriş kabuğundan ve etkileşimli olmayan bir kabuktan çalışır.

#!/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

Bu aynı zamanda Mac OS Xv10.12.6
b01

1
Bu önerilmez. Birden fazla komut dosyası bunu aynı anda kullanırsa, hepsi stdin okumaya çalışırken SIGSTOP'lu olurlar. Bu beklerken stdin bloke olur. Bunun için stdin kullanmayın. Yeni farklı dosya tanımlayıcıları istiyorsunuz.
2018

1
@Normadize Burada, ücretsiz dosya tanımlayıcılarını kullanma kaygısı ile ilgili başka bir yanıt ( unix.stackexchange.com/a/407383/147685 ) var. Çıplak asgari versiyonu read -t 10 <> <(:).
Amir

0

Gerçekten bir fifoya mı ihtiyacınız var? Stdin'i başka bir dosya tanımlayıcıya yönlendirmek de işe yarayacaktır.

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

İlham alan: Bir süre döngüsü içinde bash girişini okuyun


1
Bu bir uyku yapmıyor, hala terminalden stdin tüketiyor.
jfg956

0

Yukarıda belirtilen çözümlerde hafif bir iyileşme (buna dayanıyorum).

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

# sleep for 10 seconds
bash_sleep 10

Bir fifo ihtiyacını azalttı ve böylece temizlik yapmayın.


1
Bu önerilmez. Birden fazla komut dosyası bunu aynı anda kullanırsa, hepsi stdin okumaya çalışırken SIGSTOP'lu olurlar. Bu beklerken stdin bloke olur. Bunun için stdin kullanmayın. Yeni farklı dosya tanımlayıcıları istiyorsunuz.
2018

@Normadize Bunu hiç düşünmedim; lütfen beni daha fazla okuyabileceğim bir kaynak üzerinde durdurabilir veya yönlendirebilir misiniz?
CodeMedic

@CodeMedic Burada, ücretsiz dosya tanımlayıcıları kullanma kaygısı ile ilgili başka bir yanıt ( unix.stackexchange.com/a/407383/147685 ) var. Çıplak asgari versiyonu read -t 10 <> <(:).
Amir
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.