Tránh bận rộn chờ đợi trong bash, không có lệnh ngủ


19

Tôi biết tôi có thể chờ đợi một điều kiện để trở thành sự thật trong bash bằng cách thực hiện:

while true; do
  test_condition && break
  sleep 1
done

Nhưng nó tạo ra 1 quy trình phụ ở mỗi lần lặp (ngủ). Tôi có thể tránh chúng bằng cách làm:

while true; do
  test_condition && break
done

Nhưng nó sử dụng rất nhiều CPU (chờ đợi bận rộn). Để tránh các quy trình phụ và chờ đợi bận rộn, tôi đã đưa ra giải pháp dưới đây, nhưng tôi thấy nó thật xấu xí:

my_tmp_dir=$(mktemp -d --tmpdir=/tmp)    # Create a unique tmp dir for the fifo.
mkfifo $my_tmp_dir/fifo                  # Create an empty fifo for sleep by read.
exec 3<> $my_tmp_dir/fifo                # Open the fifo for reading and writing.

while true; do
  test_condition && break
  read -t 1 -u 3 var                     # Same as sleep 1, but without sub-process.
done

exec 3<&-                                # Closing the fifo.
rm $my_tmp_dir/fifo; rmdir $my_tmp_dir   # Cleanup, could be done in a trap.

Lưu ý: trong trường hợp chung, tôi không thể đơn giản sử dụng read -t 1 varmà không có fifo, vì nó sẽ tiêu thụ stdin và sẽ không hoạt động nếu stdin không phải là thiết bị đầu cuối hoặc đường ống.

Tôi có thể tránh các quy trình phụ và chờ đợi một cách thanh lịch hơn không?


1
truelà một nội trang và không tạo ra một quy trình phụ trong bash. bận rộn chờ đợi sẽ luôn luôn xấu.
jordanm 17/03/13

@joranm: bạn nói đúng true, cập nhật câu hỏi.
jfg956 17/03/13

Tại sao không có fifo? Đơn giản read -t 1 varthôi.
ott--

@ott: bạn đúng, nhưng điều này sẽ tiêu thụ stdin. Ngoài ra, nó sẽ không hoạt động nếu stdin không phải là thiết bị đầu cuối hoặc đường ống.
jfg956

Nếu khả năng bảo trì là một vấn đề, tôi rất muốn đề xuất với sleepví dụ đầu tiên. Cái thứ hai, trong khi nó có thể hoạt động, sẽ không dễ dàng cho bất cứ ai điều chỉnh trong tương lai. Mã đơn giản cũng có tiềm năng lớn hơn để được an toàn.
Kusalananda

Câu trả lời:


17

Trong các phiên bản mới hơn của bash(ít nhất là v2), các nội trang có thể được tải (thông qua enable -f filename commandname) khi chạy. Một số nội dung có thể tải như vậy cũng được phân phối cùng với các nguồn bash và sleepnằm trong số đó. Tính khả dụng có thể khác nhau từ HĐH đến HĐH (và thậm chí từ máy này sang máy khác), tất nhiên. Ví dụ, trên openSUSE, các nội dung này được phân phối thông qua gói bash-loadables.

Chỉnh sửa: sửa tên gói, thêm phiên bản bash tối thiểu.


Ồ, đây là thứ tôi đang tìm kiếm và tôi chắc chắn đã học được điều gì đó về nội dung có thể tải: +1. Tôi sẽ thử điều này, và đó là câu trả lời tốt nhất.
jfg956

1
Nó hoạt động! Trên debian, gói là bash-dựng. Nó chỉ bao gồm các nguồn và Makefile phải được chỉnh sửa, nhưng tôi đã có thể cài đặt sleepdưới dạng dựng sẵn. Cảm ơn.
jfg956

9

Tạo ra nhiều quy trình con là một điều xấu trong một vòng lặp bên trong. Tạo một sleeptiến trình mỗi giây là OK. Không có gì sai với

while ! test_condition; do
  sleep 1
done

Nếu bạn thực sự muốn tránh quy trình bên ngoài, bạn không cần phải mở fifo.

my_tmpdir=$(mktemp -d)
trap 'rm -rf "$my_tmpdir"' 0
mkfifo "$my_tmpdir/f"

while ! test_condition; do
  read -t 1 <>"$my_tmpdir/f"
done

Bạn nói đúng về một quá trình mỗi giây là đậu phộng (nhưng câu hỏi của tôi là tìm cách loại bỏ nó). Về phiên bản ngắn hơn, Nó đẹp hơn phiên bản của tôi, vì vậy +1 (nhưng tôi đã loại bỏ mkdirnó như được thực hiện bởi mktemp(nếu không, đó là một điều kiện cuộc đua)). Cũng đúng về cái while ! test_condition;nào đẹp hơn giải pháp ban đầu của tôi.
jfg956

7

Gần đây tôi có nhu cầu làm việc này. Tôi đã đưa ra chức năng sau đây sẽ cho phép bash ngủ mãi mà không cần gọi bất kỳ chương trình bên ngoài nào:

snore()
{
    local IFS
    [[ -n "${_snore_fd:-}" ]] || { exec {_snore_fd}<> <(:); } 2>/dev/null ||
    {
        # workaround for MacOS and similar systems
        local fifo
        fifo=$(mktemp -u)
        mkfifo -m 700 "$fifo"
        exec {_snore_fd}<>"$fifo"
        rm "$fifo"
    }
    read ${1:+-t "$1"} -u $_snore_fd || :
}

LƯU Ý: Trước đây tôi đã đăng một phiên bản này sẽ mở và đóng bộ mô tả tệp mỗi lần, nhưng tôi thấy rằng trên một số hệ thống thực hiện việc này hàng trăm lần một giây cuối cùng sẽ bị khóa. Do đó, giải pháp mới giữ cho bộ mô tả tệp giữa các lệnh gọi đến hàm. Bash sẽ dọn sạch nó khi thoát ra.

Điều này có thể được gọi giống như / bin / ngủ, và nó sẽ ngủ trong thời gian yêu cầu. Được gọi mà không có tham số, nó sẽ treo mãi mãi.

snore 0.1  # sleeps for 0.1 seconds
snore 10   # sleeps for 10 seconds
snore      # sleeps forever

Có một bài viết với nhiều chi tiết trên blog của tôi ở đây


1
Mục blog tuyệt vời. Tuy nhiên, tôi đã đến đó để tìm lời giải thích tại sao read -t 10 < <(:)quay lại ngay lập tức trong khi read -t 10 <> <(:)chờ đủ 10 giây, nhưng tôi vẫn không nhận được.
Amir

Trong read -t 10 <> <(:)những gì <>đứng cho?
CodeMote

<> mở bộ mô tả tệp để đọc và ghi, mặc dù thay thế quy trình cơ bản <(:) chỉ cho phép đọc. Đây là một vụ hack khiến Linux và Linux đặc biệt, cho rằng ai đó có thể viết thư cho nó, vì vậy đọc sẽ chờ đợi đầu vào sẽ không bao giờ đến. Nó sẽ không làm điều này trên các hệ thống BSD, trong trường hợp đó, cách giải quyết sẽ bắt đầu.
chốt

3

Trong ksh93hoặc mksh, sleeplà một vỏ được tích hợp sẵn, vì vậy một cách khác có thể là sử dụng các vỏ đó thay vì bash.

zshcũng có một zselectnội trang (được tải zmodload zsh/zselect) có thể ngủ trong một số phần trăm giây nhất định với zselect -t <n>.


2

Như người dùng Yoi nói, nếu trong kịch bản của bạn là stdin mở ra, sau đó thay vì ngủ 1 bạn có thể chỉ cần sử dụng:

read -t 1 3<&- 3<&0 <&3

Trong Bash phiên bản 4.1 trở lên, bạn có thể sử dụng số float, vd read -t 0.3 ...

Nếu trong tập lệnh stdin bị đóng (tập lệnh được gọi my_script.sh < /dev/null &), thì bạn cần sử dụng một bộ mô tả mở khác, không tạo ra đầu ra khi đọc được thực thi, ví dụ. xuất sắc :

read -t 1 <&1 3<&- 3<&0 <&3

Nếu trong một tập lệnh, tất cả các bộ mô tả được đóng lại ( stdin , stdout , stderr ) (ví dụ vì được gọi là daemon), thì bạn cần tìm bất kỳ tệp tồn tại nào không tạo ra đầu ra:

read -t 1 </dev/tty10 3<&- 3<&0 <&3

read -t 1 3<&- 3<&0 <&3cũng giống như read -t 0. Nó chỉ đọc từ stdin với thời gian chờ.
Stéphane Chazelas

1

Điều này hoạt động từ một vỏ đăng nhập cũng như một vỏ không tương tác.

#!/bin/sh

# to avoid starting /bin/sleep each time we call sleep, 
# make our own using the read built in function
xsleep()
{
  read -t $1 -u 1
}

# usage
xsleep 3

Điều này cũng hoạt động trên Mac OS X v10.12.6
b01

1
Điều này không được khuyến khích. Nếu nhiều tập lệnh sử dụng điều này cùng một lúc thì tất cả chúng đều bị SIGSTOP'ed vì tất cả chúng đều cố gắng đọc stdin. Stdin của bạn bị chặn trong khi điều này chờ đợi. Đừng sử dụng stdin cho việc này. Bạn muốn mô tả tập tin mới khác nhau.
Định mức

1
@Normadize Có một câu trả lời khác ở đây ( unix.stackexchange.com/a/407383/147685 ) liên quan đến mối quan tâm của việc sử dụng mô tả tệp miễn phí. Phiên bản tối thiểu của nó là read -t 10 <> <(:).
Amir

0

Bạn có thực sự cần một fifo? Chuyển hướng stdin sang một mô tả tập tin khác cũng sẽ làm việc.

{
echo line | while read line; do
   read -t 1 <&3
   echo "$line"
done
} 3<&- 3<&0

Lấy cảm hứng từ: Đọc đầu vào trong bash bên trong vòng lặp while


1
Đây không phải là một giấc ngủ, điều này vẫn tiêu thụ stdin từ thiết bị đầu cuối.
jfg956

0

Một cải tiến nhỏ về các giải pháp được đề cập ở trên (mà tôi đã dựa trên điều này).

bash_sleep() {
    read -rt "${1?Specify sleep interval in seconds}" -u 1 <<<"" || :;
}

# sleep for 10 seconds
bash_sleep 10

Giảm nhu cầu cho một fifo và do đó không có việc phải làm.


1
Điều này không được khuyến khích. Nếu nhiều tập lệnh sử dụng điều này cùng một lúc thì tất cả chúng đều bị SIGSTOP'ed vì tất cả chúng đều cố gắng đọc stdin. Stdin của bạn bị chặn trong khi điều này chờ đợi. Đừng sử dụng stdin cho việc này. Bạn muốn mô tả tập tin mới khác nhau.
Định mức

@Normadize Đừng bao giờ nghĩ về điều đó; xin vui lòng bạn có thể xây dựng hoặc chỉ cho tôi một tài nguyên nơi tôi có thể đọc thêm về nó.
CodeMote

@CodeMedic Có một câu trả lời khác ở đây ( unix.stackexchange.com/a/407383/147685 ) liên quan đến mối quan tâm của việc sử dụng mô tả tệp miễn phí. Phiên bản tối thiểu của nó là read -t 10 <> <(:).
Amir
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.