쉘 스크립트에서 타임 아웃


53

나는이 쉘 스크립트 있어 표준 입력에서 읽기 . 드문 경우이지만 입력을 제공 할 준비가 된 사람이 없으며 스크립트가 시간 초과 되어야합니다 . 시간 초과의 경우 스크립트는 정리 코드를 실행해야합니다. 가장 좋은 방법은 무엇입니까?

이 스크립트는 C 컴파일러가없는 20 세기 유닉스 시스템과 busybox를 실행하는 임베디드 장치를 포함하여 이식성이 뛰어나므로 Perl, bash, 컴파일 된 언어 및 전체 POSIX.2를 신뢰할 수 없습니다. 특히 $PPID, read -t완벽 POSIX 호환 트랩을 사용할 수 없습니다. 임시 파일에 쓰는 것도 제외됩니다. 모든 파일 시스템이 읽기 전용으로 마운트 된 경우에도 스크립트가 실행될 수 있습니다.

일을 더 어렵게 만들기 위해 시간이 초과되지 않을 때 스크립트가 합리적으로 빠르기 를 원합니다 . 특히 포크와 exec가 특히 낮은 Windows (주로 Cygwin)에서 스크립트를 사용하므로 최소한의 사용을 유지하고 싶습니다.

간단히 말해서, 나는

trap cleanup 1 2 3 15
foo=`cat`

타임 아웃을 추가하고 싶습니다. 나는 대체 할 수 없습니다 catread내장한다. 시간 초과의 경우 cleanup함수 를 실행하고 싶습니다 .


배경 :이 스크립트는 일부 8 비트 문자를 인쇄하고 커서 위치 전후를 비교하여 터미널의 인코딩을 추측합니다. 스크립트의 시작은 stdout이 지원되는 터미널에 연결되어 있는지 테스트하지만 때로는 환경이 거짓말하는 경우가 있습니다 (예 :로 호출하더라도 plink설정TERM=xtermTERM=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

tr2 초 후에도 입력을 읽지 않으면 종료되고 스크립트가 cleanup함수를 실행하도록 스크립트를 수정하려면 어떻게 해야 합니까?


2
쉘 스크립팅에 시간 초과를 도입하는 방법 도 참조하십시오 . 휴대 성이 떨어지는 솔루션
Gilles

답변:


33

이건 어때?

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

즉, 출력 생성 명령과 sleep동일한 프로세스 그룹에서 프로세스 그룹을 실행하십시오 . 어떤 명령이 먼저 반환 되더라도 전체 프로세스 그룹이 종료됩니다.

누구나 궁금해 할 것입니다. 예, 파이프가 사용되지 않습니다. 리디렉션을 사용하여 무시됩니다. 이것의 유일한 목적은 쉘이 동일한 프로세스 그룹에서 두 프로세스를 실행하도록하는 것입니다.


Gilles가 그의 의견에서 지적한 것처럼 스크립트 프로세스는 두 개의 하위 프로세스와 함께 종료되므로 쉘 스크립트에서는 작동하지 않습니다.

별도의 프로세스 그룹에서 명령을 강제 실행하는 방법 ¹은 새로운 대화식 쉘을 시작하는 것입니다.

#!/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가 아닌 경우?). 대화식 쉘이 종료 될 때 "종료 됨"메시지를 제거하기 위해 stderr 리디렉션이 있습니다.

테스트 zsh, bashdash. 하지만 옛날 사람들은 어떻습니까?

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
나는 이것을 아주 좋아한다. 나는 그런 식으로 프로세스 그룹을 사용하는 것을 생각하지 않았다. 매우 간단하고 경쟁 조건이 없습니다. 이식성에 대해 더 테스트해야하지만 이것은 승자처럼 보입니다.
Gilles

불행히도 이것은 스크립트에 이것을 넣 자마자 크게 실패합니다. 명령 대체의 파이프 라인은 자체 프로세스 그룹에서 실행되지 않고 kill 0스크립트 호출자를 죽입니다. 파이프 라인을 자체 프로세스 그룹으로 강제로 이식 할 수있는 방법이 있습니까?
Gilles

@ 질 : Eek! 할 수있는 방법을 찾을 수 없습니다 setprgp()없이 setsid지금 :-(에 대한
스테판 히메네스에게

1
나는 트릭을 정말 좋아하므로 현상금을 수여합니다. Linux에서 작동하는 것 같습니다. 다른 시스템에서 아직 테스트 할 시간이 없었습니다.
Gilles

2
@ Zac : 첫 번째 프로세스 만 stdin에 액세스하기 때문에 원래의 경우 순서를 반대로 바꿀 수 없습니다.
Stéphane Gimenez 2016 년

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

이미 15 (트래픽이 없는 경우 전송 SIGTERM되는 숫자 버전)를 트래핑 kill중이므로 이미 가야합니다. 즉, POSIX 이전을 살펴보면 쉘 기능이 존재하지 않을 수도 있습니다 (System V의 쉘에서 제공됨).


그렇게 간단하지 않습니다. 신호를 잡으면 많은 쉘 (대시, bash, pdksh, zsh… 아마도 ATT ksh를 제외한 모든 쉘)은 cat종료를 기다리는 동안 신호를 무시합니다 . 나는 이미 조금 실험했지만 지금까지 내가 만족하는 것을 찾지 못했습니다.
Gilles

이식성 : 고맙게도 나는 모든 곳에서 기능을 가지고 있습니다. 잘 모르겠습니다. $!사용하는 기계 중 일부가 작업 제어 기능이 거의 없으며 $!보편적으로 사용할 수 있다고 생각 합니다.
Gilles

@ 질 : 흠. 나는 bash한때 남용한 적이 있는 정말 끔찍하고 특정한 것들을 회상 -o monitor합니다. 나는 그것을 썼을 때 진정으로 고대 껍질을 생각하고있었습니다 (v7에서 작동했습니다). 즉, 나는 당신이 다른 두 가지 일 중 하나를 수행 할 수 있다고 생각합니다 : (1) "무엇이든"배경 wait $!또는 (2) 또한 보내기 SIGCLD/ SIGCHLD... 그러나 오래 된 충분한 기계에서 후자는 존재하지 않거나 이식 불가능합니다 (이전은 시스템입니다) III / V, 후자 BSD 및 V7에는 둘 다 없습니다.
geekosaur

@Gilles는 : $!적어도 V7로 되돌아 가고, 확실히 이전부터 sh오랫동안, 사실 작업 제어에 대해 아무것도 (알고 -like /bin/sh작업 제어를하지 않았다 BSD에를, 당신은 가지고 실행 csh그걸 얻기 위해 -하지만 $!이 있었다 ).
geekosaur

“무엇이든”배경을 얻을 수 없으므로 출력이 필요합니다. 에 대한 정보를 주셔서 감사합니다 $!.
Gilles

4

7.0 버전의 coretuils에 시간 초과 명령이 포함되어 있지만이를 포함하지 않는 일부 환경을 언급했습니다. 다행히 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

이것은 명령의 출력을 검색하는 것을 허용하지 않는 것 같습니다. 다른 답변에서 Gilles의 ""무엇이든 "을 언급 할 수 없습니다"를 참조하십시오.
Stéphane Gimenez

아, 흥미 롭습니다. / dev / stdin을 명령으로 리디렉션하도록 스크립트를 수정했습니다 (더 이상 픽셀 비트의 스크립트와 일치하지 않음). 내 테스트에서 작동하는 것 같습니다.
bahamat

이것은 bash에서 (이상하게)를 제외하고 표준 입력에서 명령을 읽도록 작동하지 않습니다. </dev/stdinno-op입니다. </dev/tty터미널에서 읽을 수있게되었으므로 사용 사례에 충분합니다.
Gilles

@Giles : 멋져요. 업데이트하겠습니다.
바하마

더 많은 노력 없이는 작동하지 않습니다. 명령의 출력을 검색해야하며 명령이 백그라운드에 있으면 수행 할 수 없습니다.
Gilles

3

이것을 위해 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 시스템에서 테스트 할 때 실제로 stange가 작동하지만 실제로는 모든 시스템에서 작동해야하며 출력 리디렉션의 변형이 없다면 이식성이 해결 될 수 있습니다. 여기서 이점은 백그라운드 프로세스가 포함되지 않는다는 것입니다.


휴대가 충분하지 않습니다. 나는이없는 nc오래된 유닉스 회양목에도 많은 임베디드 리눅스.
Gilles

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오래된 유닉스 회양목에도 많은 임베디드 리눅스.
Gilles

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.