시간 초과로 셸 함수 실행
이게 왜 작동할까요
timeout 10s echo "foo bar" # foo bar
하지만 이건 아니야
function echoFooBar {
echo "foo bar"
}
echoFooBar # foo bar
timeout 10s echoFooBar # timeout: failed to run command `echoFooBar': No such file or directory
어떻게 작동시킬 수 있습니까?
timeout
명령이므로 bash 셸의 하위 프로세스에서 실행됩니다. 따라서 현재 셸에 정의 된 함수에 액세스 할 수 없습니다.
이 명령 timeout
은 타임 아웃의 하위 프로세스-쉘의 손자 프로세스로 실행됩니다.
echo
셸이 내장되어 있고 별도의 명령이 있기 때문에 혼동 될 수 있습니다 .
당신이 할 수있는 일은 함수를 자체 스크립트 파일에 넣고 실행 가능하도록 chmod 한 다음 timeout
.
또는 하위 셸에서 함수를 실행하고 원래 프로세스에서 진행 상황을 모니터링하여 너무 오래 걸리면 하위 프로세스를 종료합니다.
Douglas Leeder가 말했듯이 신호를 보낼 시간 제한에 대한 별도의 프로세스가 필요합니다. 기능을 서브 쉘로 내보내고 서브 쉘을 수동으로 실행하여 해결하십시오.
export -f echoFooBar
timeout 10s bash -c echoFooBar
bash 쉘의 하위 프로세스를 시작하는 인라인 대안도 있습니다.
timeout 10s bash <<EOT
function echoFooBar {
echo foo
}
echoFooBar
sleep 20
EOT
타임 아웃과 동일하게 수행 할 수있는 함수를 만들 수 있지만 다른 함수에도 사용할 수 있습니다.
function run_cmd {
cmd="$1"; timeout="$2";
grep -qP '^\d+$' <<< $timeout || timeout=10
(
eval "$cmd" &
child=$!
trap -- "" SIGTERM
(
sleep $timeout
kill $child 2> /dev/null
) &
wait $child
)
}
그리고 아래와 같이 실행할 수 있습니다.
run_cmd "echoFooBar" 10
참고 : 솔루션은 내 질문 중 하나에서 나왔습니다. bash 명령 및 함수에 대한 시간 제한을 구현하는 우아한 솔루션
기존 스크립트 전체에 대한 추가 옵션으로 시간 제한을 추가하려는 경우 시간 제한 옵션을 테스트 한 다음 해당 옵션없이 자체 재귀 적으로 호출하도록 할 수 있습니다.
example.sh :
#!/bin/bash
if [ "$1" == "-t" ]; then
timeout 1m $0 $2
else
#the original script
echo $1
sleep 2m
echo YAWN...
fi
시간 초과없이이 스크립트 실행 :
$./example.sh -other_option # -other_option
# YAWN...
1 분 제한 시간으로 실행 :
$./example.sh -t -other_option # -other_option
function foo(){
for i in {1..100};
do
echo $i;
sleep 1;
done;
}
cat <( foo ) # Will work
timeout 3 cat <( foo ) # Will Work
timeout 3 cat <( foo ) | sort # Wont work, As sort will fail
cat <( timeout 3 cat <( foo ) ) | sort -r # Will Work
이 함수는 내장 기능 만 사용합니다.
필요에 따라 $ @를 직접 실행하는 대신 "$ *"를 평가하는 것이 좋습니다.
제한 시간 값인 첫 번째 인수 다음에 지정된 명령 문자열로 작업을 시작하고 작업 pid를 모니터합니다.
1 초마다 확인하고 bash는 0.01까지의 시간 제한을 지원하므로 조정할 수 있습니다.
또한 스크립트에 stdin이 필요한 경우
read
전용 fd (exec {tofd}<> <(:)
) 에 의존해야합니다.또한 기본값 인 킬 신호 (루프 내부의 신호)를 조정하고
-15
싶을 수도 있습니다.-9
## forking is evil
timeout() {
to=$1; shift
$@ & local wp=$! start=0
while kill -0 $wp; do
read -t 1
start=$((start+1))
if [ $start -ge $to ]; then
kill $wp && break
fi
done
}
Tiago Lopo의 답변에 대한 내 의견을 더 읽기 쉬운 형식으로 넣으십시오.
가장 최근의 서브 쉘에 타임 아웃을 적용하는 것이 더 읽기 쉽다고 생각합니다. 이렇게하면 문자열을 평가할 필요가 없으며 전체 스크립트를 좋아하는 편집기에서 쉘로 강조 표시 할 수 있습니다. 하위 eval
셸이 셸 함수에 생성 된 후에 명령을 간단히 입력합니다 (zsh로 테스트했지만 bash에서 작동해야 함).
timeout_child () {
trap -- "" SIGTERM
child=$!
timeout=$1
(
sleep $timeout
kill $child
) &
wait $child
}
사용 예 :
( while true; do echo -n .; sleep 0.1; done) & timeout_child 2
그리고 이런 식으로 쉘 기능 (백그라운드에서 실행되는 경우)과 함께 작동합니다.
print_dots () {
while true
do
sleep 0.1
echo -n .
done
}
> print_dots & timeout_child 2
[1] 21725
[3] 21727
...................[1] 21725 terminated print_dots
[3] + 21727 done ( sleep $timeout; kill $child; )
여러 인수로 명령을 처리 할 수있는 @Tiago Lopo의 답변을 약간 수정했습니다. 나는 또한 TauPan의 솔루션을 테스트했지만 Tiago의 솔루션과 달리 스크립트에서 여러 번 사용하면 작동하지 않습니다.
function timeout_cmd {
local arr
local cmd
local timeout
arr=( "$@" )
# timeout: first arg
# cmd: the other args
timeout="${arr[0]}"
cmd=( "${arr[@]:1}" )
(
eval "${cmd[@]}" &
child=$!
echo "child: $child"
trap -- "" SIGTERM
(
sleep "$timeout"
kill "$child" 2> /dev/null
) &
wait "$child"
)
}
다음은 위의 함수를 테스트하는 데 사용할 수있는 완전한 기능의 스크립트입니다.
$ ./test_timeout.sh -h
Usage:
test_timeout.sh [-n] [-r REPEAT] [-s SLEEP_TIME] [-t TIMEOUT]
test_timeout.sh -h
Test timeout_cmd function.
Options:
-n Dry run, do not actually sleep.
-r REPEAT Reapeat everything multiple times [default: 1].
-s SLEEP_TIME Sleep for SLEEP_TIME seconds [default: 5].
-t TIMEOUT Timeout after TIMEOUT seconds [default: no timeout].
예를 들어 다음과 같이 실행합니다.
$ ./test_timeout.sh -r 2 -s 5 -t 3
Try no: 1
- Set timeout to: 3
child: 2540
-> retval: 143
-> The command timed out
Try no: 2
- Set timeout to: 3
child: 2593
-> retval: 143
-> The command timed out
Done!
#!/usr/bin/env bash
#shellcheck disable=SC2128
SOURCED=false && [ "$0" = "$BASH_SOURCE" ] || SOURCED=true
if ! $SOURCED; then
set -euo pipefail
IFS=$'\n\t'
fi
#################### helpers
function check_posint() {
local re='^[0-9]+$'
local mynum="$1"
local option="$2"
if ! [[ "$mynum" =~ $re ]] ; then
(echo -n "Error in option '$option': " >&2)
(echo "must be a positive integer, got $mynum." >&2)
exit 1
fi
if ! [ "$mynum" -gt 0 ] ; then
(echo "Error in option '$option': must be positive, got $mynum." >&2)
exit 1
fi
}
#################### end: helpers
#################### usage
function short_usage() {
(>&2 echo \
"Usage:
test_timeout.sh [-n] [-r REPEAT] [-s SLEEP_TIME] [-t TIMEOUT]
test_timeout.sh -h"
)
}
function usage() {
(>&2 short_usage )
(>&2 echo \
"
Test timeout_cmd function.
Options:
-n Dry run, do not actually sleep.
-r REPEAT Reapeat everything multiple times [default: 1].
-s SLEEP_TIME Sleep for SLEEP_TIME seconds [default: 5].
-t TIMEOUT Timeout after TIMEOUT seconds [default: no timeout].
")
}
#################### end: usage
help_flag=false
dryrun_flag=false
SLEEP_TIME=5
TIMEOUT=-1
REPEAT=1
while getopts ":hnr:s:t:" opt; do
case $opt in
h)
help_flag=true
;;
n)
dryrun_flag=true
;;
r)
check_posint "$OPTARG" '-r'
REPEAT="$OPTARG"
;;
s)
check_posint "$OPTARG" '-s'
SLEEP_TIME="$OPTARG"
;;
t)
check_posint "$OPTARG" '-t'
TIMEOUT="$OPTARG"
;;
\?)
(>&2 echo "Error. Invalid option: -$OPTARG.")
(>&2 echo "Try -h to get help")
short_usage
exit 1
;;
:)
(>&2 echo "Error.Option -$OPTARG requires an argument.")
(>&2 echo "Try -h to get help")
short_usage
exit 1
;;
esac
done
if $help_flag; then
usage
exit 0
fi
#################### utils
if $dryrun_flag; then
function wrap_run() {
( echo -en "[dry run]\\t" )
( echo "$@" )
}
else
function wrap_run() { "$@"; }
fi
# Execute a shell function with timeout
# https://stackoverflow.com/a/24416732/2377454
function timeout_cmd {
local arr
local cmd
local timeout
arr=( "$@" )
# timeout: first arg
# cmd: the other args
timeout="${arr[0]}"
cmd=( "${arr[@]:1}" )
(
eval "${cmd[@]}" &
child=$!
echo "child: $child"
trap -- "" SIGTERM
(
sleep "$timeout"
kill "$child" 2> /dev/null
) &
wait "$child"
)
}
####################
function sleep_func() {
local secs
local waitsec
waitsec=1
secs=$(($1))
while [ "$secs" -gt 0 ]; do
echo -ne "$secs\033[0K\r"
sleep "$waitsec"
secs=$((secs-waitsec))
done
}
command=("wrap_run" \
"sleep_func" "${SLEEP_TIME}"
)
for i in $(seq 1 "$REPEAT"); do
echo "Try no: $i"
if [ "$TIMEOUT" -gt 0 ]; then
echo " - Set timeout to: $TIMEOUT"
set +e
timeout_cmd "$TIMEOUT" "${command[@]}"
retval="$?"
set -e
echo " -> retval: $retval"
# check if (retval % 128) == SIGTERM (== 15)
if [[ "$((retval % 128))" -eq 15 ]]; then
echo " -> The command timed out"
fi
else
echo " - No timeout"
"${command[@]}"
retval="$?"
fi
done
echo "Done!"
exit 0
이 라이너는 10 초 후 Bash 세션을 종료합니다.
$ TMOUT=10 && echo "foo bar"
ReferenceURL : https://stackoverflow.com/questions/9954794/execute-a-shell-function-with-timeout
'developer tip' 카테고리의 다른 글
phpMyAdmin에서 저장 프로 시저를 보려면 어떻게해야합니까? (0) | 2021.01.06 |
---|---|
SQL Server : 새 ID 열을 추가하고 열을 ID로 채우는 방법은 무엇입니까? (0) | 2021.01.06 |
JavaScript로 Internet Explorer 11 만 타겟팅하려면 어떻게해야합니까? (0) | 2021.01.06 |
PHP를 사용하여 JSON POST 읽기 (0) | 2021.01.06 |
웹 API 2 라우팅-리소스를 찾을 수 없습니다. (0) | 2021.01.06 |