Визначення часу в сценарії оболонки


53

У мене є сценарій оболонки, який читається зі стандартного вводу . У рідкісних обставинах ніхто не буде готовий надати вхід, і сценарій повинен вимкнути час . У разі очікування сценарій повинен виконати деякий код очищення. Який найкращий спосіб зробити це?

Цей скрипт повинен бути дуже портативним , включаючи системи Unix 20 століття без компілятора С та вбудовані пристрої, на яких працює зайнята скринька, тому на Perl, bash, будь-яку мову компіляції та навіть на повний POSIX.2 не можна покладатися. Зокрема, $PPID, read -tі зовсім POSIX-сумісні пастки не доступні. Запис у тимчасовий файл також виключається; сценарій може працювати, навіть якщо всі файлові системи змонтовані лише для читання.

Для того, щоб зробити це складніше, я також хочу, щоб сценарій був досить швидким, коли він не вичерпується. Зокрема, я також використовую скрипт у Windows (головним чином у Cygwin), де fork та exec особливо низькі, тому я хочу звести їх використання до мінімуму.

Коротше кажучи, у мене є

trap cleanup 1 2 3 15
foo=`cat`

і я хочу додати тайм-аут. Я не можу замінити catз readвбудованою. У разі закінчення часу я хочу виконати cleanupфункцію.


Передумови: цей скрипт відгадує кодування терміналу, друкуючи деякі 8-бітні символи та порівнюючи положення курсору до і після. Початок тестів сценаріїв, що stdout підключений до підтримуваного терміналу, але іноді середовище бреше (наприклад, plinkвстановлює, TERM=xtermнавіть якщо воно викликаєтьсяTERM=dumb ). Відповідна частина сценарію виглядає так:

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

Як я можу змінити сценарій так, що якщо trне прочитав жодного входу через 2 секунди, він буде знищений і сценарій виконує cleanupфункцію?


Відповіді:


33

Як що до цього:

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

Тобто: запустіть команду, що виробляє вихід, і sleepв тій же групі процесів, група процесів тільки для них. Незалежно від того, яка команда повертається, спочатку вбивається вся група процесу.

Хтось би задумався: так, труба не використовується; це обходить за допомогою переадресації. Єдина мета цього - оболонка запустити два процеси в одній групі процесів.


Як зазначив Гілль у своєму коментарі, це не буде працювати в сценарії оболонки, оскільки процес сценарію буде вбито разом з двома підпроцесами.

Один із способів змусити команду запускатися в окрему групу процесів - це запустити нову інтерактивну оболонку:

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

Але з цим можуть бути застереження (наприклад, коли stdin - це не tty?). Швидше переадресація є для позбавлення від повідомлення "Припинено", коли інтерактивна оболонка вбита.

Випробувано з zsh, bashі dash. А як же старі?

B98 пропонує наступну зміну, працюючи на Mac OS X, з GNU bash 3.2.57 або Linux з тире:

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

-
1. крім того, setsidщо видається нестандартним.


1
Мені це дуже подобається, я не думав таким чином використовувати групу процесів. Це досить просто і немає гоночних умов. Мені потрібно ще трохи перевірити його на портативність, але це виглядає як переможець.
Жил 'SO- перестань бути злим'

На жаль, це виходить ефектно, як тільки я ставлю це до сценарію: конвеєр підстановки команд не працює у власній групі процесів, і в kill 0кінцевому підсумку вбиває абонента сценарію. Чи є портативний спосіб змусити трубопровід у власну технологічну групу?
Жил 'SO- перестань бути злим'

@Gilles: Еек! не міг знайти спосіб setprgp()без setsidзараз :-(
Stephane Хіменес

1
Мені дуже подобається трюк, тому я нагороджую щедротою. Здається, працює на Linux, я ще не встиг перевірити його на інших системах.
Жил 'SO- перестань бути злим'

2
@ Zac: Неможливо змінити замовлення в початковому випадку, оскільки лише перший процес отримує доступ до stdin.
Стефан Гіменес

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

Ви вже перебуваєте в пастці 15 (числова версія SIGTERM, яку killвисилаєте, якщо не сказано інше), тож вам уже слід добре піти. Це означає, що якщо ви дивитесь на pre-POSIX, майте на увазі, що функції оболонки можуть не існувати (вони надходили з оболонки System V).


Це не так просто. Якщо ви вловлюєте сигнал, багато снарядів (тире, баш, pdksh, zsh… напевно, всі, крім ATT ksh), ігнорують його, поки вони чекають catвиходу. Я вже трохи експериментував, але поки не знайшов нічого, чим я задоволений.
Жил "ТАК - перестань бути злим"

Щодо портативності: на щастя, у мене є функції скрізь. Я не впевнений у цьому $!, я думаю, що деякі з цих машин, якими я користуюсь рідко, не мають управління роботою, є $!загальнодоступними?
Жил "ТАК - перестань бути злим"

@Gilles: Хм, так. Я пригадую кілька справді жахливих та bashспецифічних речей, які я колись робив із приводу зловживань -o monitor. Я думав з точки зору справді древніх снарядів, коли писав це (це працювало в v7). З цього приводу я думаю, що ви можете зробити будь-яку з двох інших речей: (1) фон "що завгодно" та wait $!(2) також надсилати SIGCLD/ SIGCHLD... але на досить старих машинах останній або не існує, або не переноситься (перший - System III / V, останні BSD і V7 не мали жодного).
geekosaur

@Gilles: $!принаймні повертається до V7 і, безумовно, передує sh-подібному, що нічого не знав про контроль роботи (насправді, довгий час /bin/shBSD не займався контролем роботи; вам довелося бігти, cshщоб отримати його - але чи $!був там ).
geekosaur

Я не можу творити «що завгодно», мені потрібен його вихід. Дякуємо за інформацію про $!.
Жил "ТАК - перестань бути злим"

4

Хоча coretuils версії 7.0 включає в себе команду timeout, ви згадали про деякі середовища, які не матимуть цього. На щастя, на pixelbeat.org написано сценарій очікування sh.

Я використовував його раніше кілька разів, і він працює дуже добре.

http://www.pixelbeat.org/scripts/timeout ( Примітка: Сценарій нижче був дещо змінений із сценарію на pixelbeat.org, дивіться коментарі нижче цієї відповіді.)

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

Здається, що це не дозволяє отримати вихід команди. Дивіться коментар "Я не можу задля" будь-якого "" коментаря Жиля в іншій відповіді.
Стефан Гіменез

Ах, цікаво. Я змінив сценарій (щоб він більше не відповідав тому, який використовується в pixelbeat), щоб перенаправити / dev / stdin у команду. Здається, це працює у моєму тестуванні.
bahamat

Це не працює, щоб команда читалася зі стандартного вводу, за винятком (як не дивно) в bash. </dev/stdinє неоперативним. </dev/ttyдозволить йому читати з терміналу, що досить добре для мого випадку використання.
Жил 'SO- перестань бути злим'

@Giles: класно, я оновлю це оновлення.
bahamat

Це не може працювати без значно більших зусиль: мені потрібно отримати вихід команди, і я не можу це зробити, якщо команда знаходиться у фоновому режимі.
Жил 'ТАК - перестань бути злим'

3

Як щодо (ab) використання NC для цього

Подібно до;

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

Або згорнутий в єдиний командний рядок;

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

Остання версія, хоча дивлячись на сцені насправді працює, коли я тестую її в системі Linux - я найкраще здогадуюсь, що вона повинна працювати в будь-якій системі, і якщо ні, то перенаправлення виводу може вирішити питання щодо її мобільності. користь тут полягає в тому, що фонові процеси не задіяні.


Недостатньо портативний. У мене немає ні ncна якому старому Unix boxen, ні на багатьох вбудованих Linux.
Жил 'ТАК - перестань бути злим'

0

Інший спосіб запустити конвеєр у своїй власній групі процесів - це запустити sh -c '....'в псевдотерміналі за допомогою scriptкоманди (яка неявно застосовує setsidфункцію).

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

Недостатньо портативний. У мене немає ні scriptна якому старому Unix boxen, ні на багатьох вбудованих Linux.
Жил "ТАК - перестань бути злим"

0

Відповідь на https://unix.stackexchange.com/a/18711 дуже приємна.

Я хотів досягти подібного результату, але без необхідності явно знову викликати оболонку, тому що хотів викликати існуючі функції оболонки.

Використовуючи bash, можна зробити наступне:

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

Отже, припустимо, у мене вже функція оболонки f:

f() { date ; kill 0 ; }

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

Виконуючи це, я бачу:

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

-2
timeout_handler () { echo Timeout. goodbye.;  exit 1; }
trap timeout_handler ALRM
( sleep 60; kill -ALRM $$ ) &

Це не працює з тієї ж причини, що і у відповіді Гекаусора . Мені потрібно отримати вихід зовнішньої команди, і сигнал ігнорується під час виконання цієї команди.
Жиль "ТАК - перестань бути злим"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.