Bir kabuk komut dosyasında zaman aşımına uğradı


53

Standart girdiden okuyan bir kabuk betiğim var . Nadir durumlarda, girdi vermeye hazır hiç kimse olmayacak ve komut dosyası zaman aşımına uğramalı . Zaman aşımı durumunda, komut dosyası bir miktar temizleme kodu yürütmelidir. Bunu yapmanın en iyi yolu nedir?

Bu betiğin , C derleyicisi olmayan 20. yüzyıl unix sistemleri ve meşgul kutusu çalıştıran gömülü aygıtlar da dahil olmak üzere çok taşınabilir olması gerekir , bu nedenle Perl, bash, derlenmiş herhangi bir dil ve hatta POSIX.2'nin tümüne bile güvenilmez. Özellikle, $PPID, read -tve mükemmel POSIX uyumlu tuzaklar kullanılamaz. Geçici bir dosyaya yazma da dahil değildir; tüm dosya sistemleri salt okunur olarak takılı olsa bile komut dosyası çalıştırılabilir.

Sadece işleri zorlaştırmak için, zaman aşımına uğramazsa betiğin de oldukça hızlı olmasını istiyorum . Özellikle betiği, çatal ve exec'in çok düşük olduğu Windows'ta (özellikle Cygwin'de) kullanıyorum, bu yüzden kullanımlarını minimumda tutmak istiyorum.

Özetle, ben var

trap cleanup 1 2 3 15
foo=`cat`

ve bir zaman aşımı eklemek istiyorum. Yerleşik catile değiştiremiyorum read. Zaman aşımı durumunda, cleanupişlevi yürütmek istiyorum .


Arka plan: Bu betik, bazı 8-bit karakterleri yazdırarak ve imleç pozisyonunu önce ve sonra karşılaştırarak terminalin kodlamasını tahmin ediyor. Komut dizisinin başlangıcı, stdout'un desteklenen bir terminale bağlı olduğunu test eder, ancak bazen ortam yalan söyler (örneğin plinkçağrılsa TERM=xtermbile ayarlarTERM=dumb ). Komut dosyasının ilgili kısmı şuna benzer:

text='Éé'  # UTF-8; shows up as Ãé on a latin1 terminal
csi='␛['; dsr_cpr="${csi}6n"; dsr_ok="${csi}5n"  # ␛ is an escape character
stty_save=`stty -g`
cleanup () { stty "$stty_save"; }
trap 'cleanup; exit 120' 0 1 2 3 15     # cleanup code
stty eol 0 eof n -echo                # Input will end with `0n`
# echo-n is a function that outputs its argument without a newline
echo-n "$dsr_cpr$dsr_ok"              # Ask the terminal to report the cursor position
initial_report=`tr -dc \;0123456789`  # Expect ␛[42;10R␛[0n for y=42,x=10
echo-n "$text$dsr_cpr$dsr_ok"
final_report=`tr -dc \;0123456789`
cleanup
# Compute and return initial_x - final_x

Komut dosyasını nasıl değiştirebilirim, böylece tr2 saniye sonra herhangi bir girdi okumamışsa öldürülür ve komut dosyası cleanupişlevi yürütür ?


Yanıtlar:


33

Peki buna ne dersin:

foo=`{ { cat 1>&3; kill 0; } | { sleep 2; kill 0; } } 3>&1`

Yani: output-komutunu çalıştırın ve sleepaynı işlem grubunda, sadece onlar için bir işlem grubu çalıştırın. Hangisi önce dönerse, tüm işlem grubunu öldürür.

Herkes merak eder mi: Evet, boru kullanılmıyor; yönlendirmeleri kullanarak atlanır. Bunun tek amacı, kabuğun aynı işlem grubunda iki işlemi çalıştırmasını sağlamaktır.


Gilles'un yorumunda da belirtildiği gibi, bu bir kabuk senaryosunda çalışmaz çünkü senaryo süreci iki alt işlemle birlikte öldürülür.

Komutu ayrı bir işlem grubunda çalışmaya zorlamanın bir yolu, yeni bir etkileşimli kabuk başlatmaktır:

#!/bin/sh
foo=`sh -ic '{ cat 1>&3; kill 0; } | { sleep 2; kill 0; }' 3>&1 2>/dev/null`
[ -n "$foo" ] && echo got: "$foo" || echo timeouted

Ancak bununla ilgili uyarılar olabilir (örneğin, stdin tty olmadığında?). Stderr yeniden yönlendirme, etkileşimli kabuk öldürüldüğünde "Sonlandırıldı" iletisinden kurtulmak için oradadır.

İle test edilmiştir zsh, bashve dash. Peki ya yaşlılar?

B98 Mac OS X üzerinde çalışan GNU bash 3.2.57 ile veya Linux ile tire ile aşağıdaki değişiklikleri önerir:

foo=`sh -ic 'exec 3>&1 2>/dev/null; { cat 1>&3; kill 0; } | { sleep 2; kill 0; }'`

-
1. dışında setsidstandart olmayan görünüyor.


1
Bundan çok hoşlandım, bir süreç grubunu bu şekilde kullanmayı düşünmemiştim. Oldukça basit ve yarış koşulu yok. Taşınabilirliği test etmek için biraz daha test etmem gerekiyor, ancak bu kazanan gibi görünüyor.
Gilles 'SO- kötülük' dur '14

Ne yazık ki, bir betiğe koyduğum anda bu başarısızlıkla sonuçlanıyor: komut yerine geçen boru hattı kendi işlem grubunda çalışmaz kill 0ve betiğin arayanını da öldürür. Boru hattını kendi işlem grubuna zorlamak için taşınabilir bir yol var mı?
Gilles 'SO- kötülük' dur '16

@Gilles: Eek! bir yolunu bulamadı setprgp()olmadan setsidşimdi :-( için
Stéphane Gimenez

1
Hile yapmayı gerçekten seviyorum, bu yüzden ödül vereceğim. Linux üzerinde çalışıyor gibi görünüyor, henüz diğer sistemlerde test etmek için zamanım olmadı.
Gilles 'SO- kötülük' dur '20

2
@Zac: Orijinal durumda sıralamayı tersine çevirmek mümkün değildir, çünkü yalnızca ilk işlem stdin'e erişebilir.
Stéphane Gimenez

6
me=$$
(sleep 2; kill $me >/dev/null 2>&1) & nuker=$!
# do whatever
kill $nuker >/dev/null 2>&1

Zaten 15'i ( SIGTERMbaşka bir şey killsöylenmedikçe ne gönderirse bunun nümerik versiyonunu ) hapsoluyorsunuz, bu yüzden zaten gitmeniz iyi olmalı. Bununla birlikte eğer POSIX öncesi arıyorsanız, kabuk işlevlerinin de olamayabileceğinin farkında olun (Sistem V'in kabuğundan geliyorlardı).


O kadar basit değil. Sinyali yakalarsanız, birçok kabuk (kısa çizgi, bash, pdksh, zsh… muhtemelen ATT ksh hariç tümü) catçıkmak için beklerken yok sayar . Zaten biraz denedim ama şu ana kadar memnun kaldığım bir şey bulamadım.
Gilles 'SO- kötülük' dur

Taşınabilirlik: Neyse ki her yerde fonksiyonlarım var. Emin değilim $!, sanırım nadiren kullandığım bu makinelerin bazıları iş kontrolüne sahip değil, $!evrensel olarak mevcut mu?
Gilles 'SO- kötülük' dur

@Gilles: Hm, evet. Bir bashzamanlar kötüye kullanımıyla ilgili yaptığım gerçekten çirkin ve özel şeyleri hatırlıyorum -o monitor. Bunu yazdığımda gerçekten eski kabukları açısından düşünüyordum (v7'de çalıştı). Bununla birlikte, iki şeyden birini yapabileceğinizi düşünüyorum: (1) arka plan "ne olursa olsun" ve wait $!, veya (2) ayrıca SIGCLD/ gönderir SIGCHLD... ancak yeterince eski makinelerde, ikincisi ya varolmaz ya da taşınabilir değildir (birincisi Sistem'dir). III / V, sonuncu BSD ve V7'nin de yoktu).
geekosaur

@Gilles: $!en azından V7'ye geri döndü ve kesinlikle shiş kontrolü hakkında hiçbir şey bilmeyen bir şeye benziyor (aslında, BSD'de uzun süredir /bin/shiş kontrolü yapmadı; almak için koşmak zorunda kaldın csh- ama $!oradaydı) ).
geekosaur

“Her neyse” arkaplan yapamam, çıktısına ihtiyacım var. Hakkında bilgi için teşekkürler $!.
Gilles 'SO- kötülük' dur

4

7.0 sürümünden itibaren coretuils bir zaman aşımı komutu içermesine rağmen, buna sahip olmayan bazı ortamlardan bahsettiniz. Neyse ki pixelbeat.org , yazılmış bir zaman aşımı betiğine sahiptir sh.

Daha önce birkaç kez kullandım ve çok iyi çalışıyor.

http://www.pixelbeat.org/scripts/timeout ( Not: Aşağıdaki komut dosyası, pixelbeat.org'daki bölümden biraz değiştirilmiştir, bu cevabın altındaki yorumlara bakınız.)

#!/bin/sh

# Execute a command with a timeout

# Author:
#    http://www.pixelbeat.org/
# Notes:
#    Note there is a timeout command packaged with coreutils since v7.0
#    If the timeout occurs the exit status is 124.
#    There is an asynchronous (and buggy) equivalent of this
#    script packaged with bash (under /usr/share/doc/ in my distro),
#    which I only noticed after writing this.
#    I noticed later again that there is a C equivalent of this packaged
#    with satan by Wietse Venema, and copied to forensics by Dan Farmer.
# Changes:
#    V1.0, Nov  3 2006, Initial release
#    V1.1, Nov 20 2007, Brad Greenlee <brad@footle.org>
#                       Make more portable by using the 'CHLD'
#                       signal spec rather than 17.
#    V1.3, Oct 29 2009, Ján Sáreník <jasan@x31.com>
#                       Even though this runs under dash,ksh etc.
#                       it doesn't actually timeout. So enforce bash for now.
#                       Also change exit on timeout from 128 to 124
#                       to match coreutils.
#    V2.0, Oct 30 2009, Ján Sáreník <jasan@x31.com>
#                       Rewritten to cover compatibility with other
#                       Bourne shell implementations (pdksh, dash)

if [ "$#" -lt "2" ]; then
    echo "Usage:   `basename $0` timeout_in_seconds command" >&2
    echo "Example: `basename $0` 2 sleep 3 || echo timeout" >&2
    exit 1
fi

cleanup()
{
    trap - ALRM               #reset handler to default
    kill -ALRM $a 2>/dev/null #stop timer subshell if running
    kill $! 2>/dev/null &&    #kill last job
      exit 124                #exit with 124 if it was running
}

watchit()
{
    trap "cleanup" ALRM
    sleep $1& wait
    kill -ALRM $$
}

watchit $1& a=$!         #start the timeout
shift                    #first param was timeout for sleep
trap "cleanup" ALRM INT  #cleanup after timeout
"$@" < /dev/tty & wait $!; RET=$?    #start the job wait for it and save its return value
kill -ALRM $a            #send ALRM signal to watchit
wait $a                  #wait for watchit to finish cleanup
exit $RET                #return the value

Bu komutun çıktısını almaya izin vermiyor gibi görünüyor. Diğer cevapta Gilles tarafından "ne olursa olsun" "yorumunu yapamam.
Stéphane Gimenez

Ah, ilginç. Komutu / dev / stdin komutunu yönlendirmek için betiği değiştirdim (artık pixelbeat ile eşleşmeyecek). Testimde işe yarıyor.
bahamat

Bu komutun bash (tuhaf bir şekilde) dışında standart girdiden okunması için çalışmaz. </dev/stdinbir ameliyat değildir. </dev/ttykullanım durumum için yeterince iyi olan terminalden okumasını sağlayacaktır.
Gilles 'SO- kötülük' dur '15

@Giles: cool, Bu güncellemeyi yapacağım.
bahamat

Bu oldukça fazla çaba göstermeden çalışamaz: Komutun çıktısını almam gerekiyor ve komut arka planda ise bunu yapamam.
Gilles 'SO- kötülük' dur '16

3

(Ab) bunun için NC kullanmaya ne dersin

Sevmek;

   $ nc -l 0 2345 | cat &  # output come from here
   $ nc -w 5 0 2345   # input come from here and times out after 5 sec

Veya tek bir komut satırına yuvarlandı;

   $ foo=`nc -l 0 2222 | nc -w 5 0 2222`

Son versiyon, linux sistemde test ettiğimde aslında stange görünmese de işe yarıyor - en iyi tahminim, herhangi bir sistemde çalışması gerektiği ve çıktı yönlendirme değişiminin taşınabilirlik için çözülmeyeceğidir. Buradaki fayda, hiçbir arka plan işlemine karışmadığıdır.


Yeterince taşınabilir değil. Bende olmayan ncbazı eski Unix boxen üzerinde, ne de pek gömülü Linux üzerinde.
Gilles 'SO- kötülük yapmayı bırak'

0

Boru hattını kendi işlem grubunda çalıştırmanın bir başka yolu da (dolaylı olarak fonksiyonu uygulayan ) komutunu sh -c '....'kullanarak sözde bir terminalde çalıştırmaktır .scriptsetsid

#!/bin/sh
stty -echo -onlcr
# GNU script
foo=`script -q -c 'sh -c "{ cat 1>&3; kill 0; } | { sleep 5; kill 0; }" 3>&1 2>/dev/null' /dev/null`
# FreeBSD script
#foo=`script -q /dev/null sh -c '{ cat 1>&3; kill 0; } | { sleep 5; kill 0; }' 3>&1 2>/dev/null`
stty echo onlcr
echo "foo: $foo"


# alternative without: stty -echo -onlcr
# cr=`printf '\r'`
# foo=`script -q -c 'sh -c "{ { cat 1>&3; kill 0; } | { sleep 5; kill 0; } } 3>&1 2>/dev/null"' /dev/null | sed -e "s/${cr}$//" -ne 'p;N'`  # GNU
# foo=`script -q /dev/null sh -c '{ { cat 1>&3; kill 0; } | { sleep 5; kill 0; } } 3>&1 2>/dev/null' | sed -e "s/${cr}$//" -ne 'p;N'`  # FreeBSD
# echo "foo: $foo"

Yeterince taşınabilir değil. Bende olmayan scriptbazı eski Unix boxen üzerinde, ne de pek gömülü Linux üzerinde.
Gilles 'SO- kötülük yapmayı bırak'

0

Https://unix.stackexchange.com/a/18711 adresindeki cevap çok iyi.

Benzer bir sonuç elde etmek istedim, ancak kabuğu tekrar çağırmak zorunda kalmadan, mevcut kabuk fonksiyonlarını çağırmak istedim.

Bash kullanarak, aşağıdakileri yapmak mümkündür:

eval 'set -m ; ( ... ) ; '"$(set +o)"

Diyelim ki zaten bir kabuk fonksiyonum var. f :

f() { date ; kill 0 ; }

echo Before
eval 'set -m ; ( f ) ; '"$(set +o)"
echo After

Bunu çalıştırırken görüyorum:

$ sh /tmp/foo.sh
Before
Mon 14 Mar 2016 17:22:41 PDT
/tmp/foo.sh: line 4: 17763 Terminated: 15          ( f )
After

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.