Thời gian trong một kịch bản shell


53

Tôi có một kịch bản shell đang đọc từ đầu vào tiêu chuẩn . Trong những trường hợp hiếm hoi, sẽ không có ai sẵn sàng cung cấp đầu vào và kịch bản phải hết thời gian . Trong trường hợp hết thời gian, tập lệnh phải thực thi một số mã dọn dẹp. Cách tốt nhất để làm điều đó là gì?

Tập lệnh này phải rất dễ mang theo , bao gồm các hệ thống unix thế kỷ 20 không có trình biên dịch C và các thiết bị nhúng đang chạy busybox, vì vậy Perl, bash, bất kỳ ngôn ngữ được biên dịch nào và thậm chí cả POSIX.2 đều không thể dựa vào. Đặc biệt, $PPID, read -tvà bẫy hoàn hảo POSIX-compliant không có sẵn. Viết vào một tập tin tạm thời cũng được loại trừ; tập lệnh có thể chạy ngay cả khi tất cả các hệ thống tập tin được gắn ở chế độ chỉ đọc.

Chỉ để làm cho mọi thứ trở nên khó khăn hơn, tôi cũng muốn kịch bản nhanh chóng hợp lý khi nó không hết thời gian. Đặc biệt, tôi cũng sử dụng tập lệnh trong Windows (chủ yếu ở Cygwin), trong đó fork và exec đặc biệt thấp, vì vậy tôi muốn giữ mức sử dụng của chúng ở mức tối thiểu.

Tóm lại, tôi có

trap cleanup 1 2 3 15
foo=`cat`

và tôi muốn thêm thời gian chờ. Tôi không thể thay thế catbằng tích readhợp. Trong trường hợp hết thời gian, tôi muốn thực hiện cleanupchức năng.


Bối cảnh: tập lệnh này đoán mã hóa của thiết bị đầu cuối bằng cách in một số ký tự 8 bit và so sánh vị trí con trỏ trước và sau. Sự khởi đầu của các bài kiểm tra kịch bản mà thiết bị xuất chuẩn được kết nối với một thiết bị đầu cuối được hỗ trợ, nhưng đôi khi môi trường đang nói dối (ví dụ: plinkđặt TERM=xtermngay cả khi nó được gọi vớiTERM=dumb ). Phần có liên quan của kịch bản trông như thế này:

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

Làm cách nào tôi có thể sửa đổi tập lệnh để nếu trkhông đọc bất kỳ đầu vào nào sau 2 giây, tập lệnh sẽ bị hủy và tập lệnh thực thi cleanupchức năng?


Câu trả lời:


33

Cái này thì sao:

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

Đó là: chạy lệnh sản xuất đầu ra và sleeptrong cùng một nhóm quy trình, một nhóm quy trình chỉ dành cho chúng. Lệnh nào trả về trước sẽ giết toàn bộ nhóm quy trình.

Có ai tự hỏi: Có, đường ống không được sử dụng; nó được bỏ qua bằng cách sử dụng các chuyển hướng. Mục đích duy nhất của nó là để shell chạy hai tiến trình trong cùng một nhóm tiến trình.


Như Gilles đã chỉ ra trong bình luận của mình, điều này sẽ không hoạt động trong một kịch bản shell bởi vì quá trình kịch bản sẽ bị giết cùng với hai quy trình con.

Một cách để buộc một lệnh chạy trong một nhóm quy trình riêng biệt là bắt đầu một lớp vỏ tương tác mới:

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

Nhưng có thể có những cảnh báo với điều này (ví dụ khi stdin không phải là một tty?). Chuyển hướng stderr ở đó để loại bỏ thông báo "Chấm dứt" khi lớp vỏ tương tác bị giết.

Đã thử nghiệm với zsh, bashdash. Nhưng những gì về oldies?

B98 đề xuất thay đổi sau, hoạt động trên Mac OS X, với GNU bash 3.2.57 hoặc Linux với dấu gạch ngang:

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

-
1. khác hơn setsidlà không chuẩn.


1
Tôi khá thích điều này, tôi đã không nghĩ đến việc sử dụng một nhóm quy trình theo cách đó. Nó khá đơn giản và không có điều kiện chủng tộc. Tôi cần kiểm tra tính di động hơn nữa, nhưng điều này có vẻ như là một người chiến thắng.
Gilles 'SO- ngừng trở nên xấu xa'

Thật không may, điều này thất bại một cách ngoạn mục ngay khi tôi đưa nó vào một kịch bản: đường ống trong thay thế lệnh không chạy trong nhóm quy trình riêng của nó và kill 0cuối cùng cũng giết chết người gọi kịch bản. Có một cách di động để buộc các đường ống vào nhóm quy trình riêng của mình?
Gilles 'SO- ngừng trở nên xấu xa'

@Gilles: Eek! không thể tìm cách để setprgp()không có setsidngay bây giờ :-(
Stéphane Gimenez

1
Tôi thực sự thích mánh khóe, vì vậy tôi đang thưởng tiền thưởng. Nó dường như hoạt động trên Linux, tôi chưa có thời gian để thử nghiệm nó trên các hệ thống khác.
Gilles 'SO- ngừng trở nên xấu xa'

2
@Zac: Không thể đảo ngược thứ tự trong trường hợp ban đầu, vì chỉ có quá trình đầu tiên mới có quyền truy cập vào stdin.
Stéphane Gimenez

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

Bạn đã bẫy 15 (phiên bản số của SIGTERM, đó là những gì được killgửi trừ khi được nói khác đi), vì vậy bạn nên sẵn sàng để đi. Điều đó nói rằng, nếu bạn đang xem trước POSIX, hãy lưu ý rằng các hàm shell có thể không tồn tại (chúng đến từ shell của System V).


Nó không đơn giản. Nếu bạn bẫy tín hiệu, nhiều shell (dash, bash, pdksh, zsh, có lẽ tất cả ngoại trừ ATT ksh) đều bỏ qua nó trong khi chúng đang chờ catthoát. Tôi đã thử nghiệm một chút nhưng không tìm thấy bất cứ điều gì tôi hài lòng cho đến nay.
Gilles 'SO- ngừng trở nên xấu xa'

Về tính di động: rất may tôi có chức năng ở khắp mọi nơi. Tôi không chắc chắn $!, tôi nghĩ rằng một số máy tôi sử dụng hiếm khi không có kiểm soát công việc, $!có sẵn trên toàn cầu không?
Gilles 'SO- ngừng trở nên xấu xa'

@Gilles: Hừm, yeh. Tôi nhớ lại một số thứ thực sự gớm ghiếc và bashcụ thể mà tôi đã từng làm liên quan đến lạm dụng -o monitor. Tôi đã suy nghĩ về các vỏ thực sự cổ xưa khi tôi viết nó (nó hoạt động trong phiên bản 7). Điều đó nói rằng, tôi nghĩ rằng bạn có thể thực hiện một trong hai điều khác: (1) nền "bất cứ điều gì" và wait $!, hoặc (2) cũng gửi SIGCLD/ SIGCHLD... nhưng trên các máy đủ cũ, cái sau không tồn tại hoặc không thể truy cập được (trước đây là Hệ thống III / V, BSD sau và V7 không có).
geekizard

@Gilles: $!quay trở lại V7 ít nhất, và chắc chắn có trước một người shgiống như biết bất cứ điều gì về kiểm soát công việc (thực tế, trong một thời gian dài /bin/shtrên BSD đã không kiểm soát công việc; bạn phải chạy cshđể có được nó - nhưng $!đã ở đó ).
geekizard

Tôi không thể nền tảng bất cứ điều gì, tôi cần đầu ra của nó. Cảm ơn thông tin về $!.
Gilles 'SO- ngừng trở nên xấu xa'

4

Mặc dù coretuils kể từ phiên bản 7.0 bao gồm lệnh hết thời gian bạn đã đề cập đến một số môi trường sẽ không có nó. May mắn thay pixelbeat.org có một kịch bản hết thời gian được viết sh.

Tôi đã sử dụng nó trước đây trong nhiều dịp và nó hoạt động rất tốt.

http://www.pixelbeat.org/scripts/timeout ( Lưu ý: Tập lệnh bên dưới đã được sửa đổi một chút so với tập lệnh trên pixelbeat.org, xem các bình luận bên dưới câu trả lời này.)

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

Có vẻ như điều này không cho phép lấy đầu ra của lệnh. Xem bình luận "Tôi không thể làm nền cho bất cứ điều gì khác" của Gilles trong câu trả lời khác.
Stéphane Gimenez

À, thật thú vị. Tôi đã sửa đổi tập lệnh (vì vậy nó sẽ không khớp với tập lệnh trên pixelbeat nữa) để chuyển hướng / dev / stdin vào lệnh. Nó dường như làm việc trong thử nghiệm của tôi.
bahamat

Điều này không hoạt động để có lệnh đọc từ đầu vào tiêu chuẩn, ngoại trừ (lẻ) trong bash. </dev/stdinlà một không-op. </dev/ttysẽ cho phép nó đọc từ thiết bị đầu cuối, đủ tốt cho trường hợp sử dụng của tôi.
Gilles 'SO- ngừng trở nên xấu xa'

@Giles: tuyệt, tôi sẽ thực hiện cập nhật đó.
bahamat

Điều này không thể hoạt động mà không cần nỗ lực nhiều hơn: Tôi cần truy xuất đầu ra của lệnh và tôi không thể làm điều đó nếu lệnh ở chế độ nền.
Gilles 'SO- ngừng trở nên xấu xa'

3

Những gì về (ab) sử dụng NC cho điều này

Như;

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

Hoặc cuộn lại thành một dòng lệnh duy nhất;

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

Phiên bản cuối cùng, mặc dù tìm kiếm stange thực sự hoạt động khi tôi thử nghiệm nó trên hệ thống linux - tôi đoán tốt nhất là nó sẽ hoạt động trên bất kỳ hệ thống nào, và nếu không phải là một biến thể của chuyển hướng đầu ra có thể giải quyết được tính di động. lợi ích ở đây là không có quá trình nền được tham gia.


Không đủ di động. Tôi không có nctrên một số boxen Unix cũ, cũng như trên nhiều Linux nhúng.
Gilles 'SO- ngừng trở nên xấu xa'

0

Một cách khác để chạy đường ống trong nhóm quy trình riêng của nó là chạy sh -c '....'trong thiết bị đầu cuối giả bằng cách sử dụng scriptlệnh (áp dụng ngầm định setsidchức năng).

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

Không đủ di động. Tôi không có scripttrên một số boxen Unix cũ, cũng như trên nhiều Linux nhúng.
Gilles 'SO- ngừng trở nên xấu xa'

0

Câu trả lời trong https://unix.stackexchange.com/a/18711 rất hay.

Tôi muốn đạt được một kết quả tương tự, nhưng không cần phải gọi lại shell một cách rõ ràng bởi vì tôi muốn gọi các hàm shell hiện có.

Sử dụng bash, có thể làm như sau:

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

Vì vậy, giả sử tôi đã có một hàm shell f:

f() { date ; kill 0 ; }

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

Thực hiện điều này tôi thấy:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.