Bash: giấc ngủ vô hạn (chặn vô hạn)


158

Tôi sử dụng startxđể bắt đầu X sẽ đánh giá của tôi .xinitrc. Trong tôi .xinitrcbắt đầu quản lý cửa sổ của tôi bằng cách sử dụng /usr/bin/mywm. Bây giờ, nếu tôi giết WM của mình (để kiểm tra một số WM khác), X cũng sẽ chấm dứt vì .xinitrctập lệnh đạt EOF. Vì vậy, tôi đã thêm điều này vào cuối của tôi .xinitrc:

while true; do sleep 10000; done

Bằng cách này, X sẽ không chấm dứt nếu tôi giết WM của mình. Bây giờ câu hỏi của tôi: làm thế nào tôi có thể làm một giấc ngủ vô hạn thay vì lặp đi lặp lại giấc ngủ? Có một lệnh nào sẽ giống như đóng băng kịch bản không?

Trân trọng

Câu trả lời:


330

sleep infinity thực hiện chính xác những gì nó gợi ý và hoạt động mà không lạm dụng mèo.


16
Mát mẻ. Thật không may, busybox của tôi không hiểu.
không phải người dùng

12
BSD (hoặc ít nhất là OS X) cũng không hiểu sleep infinity, mặc dù đó là một điều tuyệt vời để tìm hiểu về Linux. Tuy nhiên, while true; do sleep 86400; donenên là một sự thay thế đầy đủ.
Ivan X

16
Về điều này, tôi đã thực hiện một số nghiên cứu tôi đã ghi lại trong một câu trả lời riêng biệt. Để tóm tắt: infinityđược chuyển đổi trong C từ "chuỗi" thành a double. Sau đó, nó doubleđược cắt ngắn đến các giá trị tối đa được phép timespec, có nghĩa là một lượng giây rất lớn (phụ thuộc vào kiến ​​trúc) nhưng, theo lý thuyết, là hữu hạn.
jp48

72

tail không chặn

Như mọi khi: Đối với mọi thứ, có một câu trả lời ngắn gọn, dễ hiểu, dễ làm theo và hoàn toàn sai. Ở đây tail -f /dev/nullrơi vào loại này;)

Nếu bạn nhìn vào nó với strace tail -f /dev/nullbạn sẽ nhận thấy rằng giải pháp này còn lâu mới bị chặn! Nó thậm chí còn tồi tệ hơn sleepgiải pháp trong câu hỏi, vì nó sử dụng (dưới Linux) các tài nguyên quý giá như inotifyhệ thống. Ngoài ra các quá trình khác mà viết để /dev/nulltạo tailvòng lặp. (Trên Ubuntu64 16.10 của tôi, điều này thêm vài 10 tòa nhà mỗi giây trên một hệ thống đã bận rộn.)

Câu hỏi là cho một lệnh chặn

Thật không may, không có điều đó ..

Đọc: Tôi không biết cách nào để lưu trữ cái này với shell trực tiếp.

Tất cả mọi thứ (thậm chí sleep infinity) có thể bị gián đoạn bởi một số tín hiệu. Vì vậy, nếu bạn muốn thực sự chắc chắn rằng nó không đặc biệt trở lại, nó phải chạy trong một vòng lặp, giống như bạn đã làm cho bạn sleep. Xin lưu ý rằng (trên Linux) /bin/sleeprõ ràng đã bị giới hạn sau 24 ngày (hãy xem strace sleep infinity), do đó, điều tốt nhất bạn có thể làm là:

while :; do sleep 2073600; done

(Lưu ý rằng tôi tin rằng sleepcác vòng lặp bên trong có giá trị cao hơn 24 ngày, nhưng điều này có nghĩa là: Nó không bị chặn, nó rất chậm lặp. Vậy tại sao không di chuyển vòng lặp này ra bên ngoài?)

.. nhưng bạn có thể đến khá gần với một cái tên không tên fifo

Bạn có thể tạo một cái gì đó thực sự chặn miễn là không có tín hiệu gửi đến quá trình. Sau đây sử dụng bash 4, 2 PID và 1 fifo:

bash -c 'coproc { exec >&-; read; }; eval exec "${COPROC[0]}<&-"; wait'

Bạn có thể kiểm tra xem cái này có thực sự chặn stracenếu bạn thích:

strace -ff bash -c '..see above..'

Làm thế nào điều này đã được xây dựng

readchặn nếu không có dữ liệu đầu vào (xem một số câu trả lời khác). Tuy nhiên, tty(aka. stdin) Thường không phải là một nguồn tốt, vì nó bị đóng khi người dùng đăng xuất. Ngoài ra nó có thể ăn cắp một số đầu vào từ tty. Không hay.

Để tạo readkhối, chúng ta cần chờ một thứ giống như fifosẽ không bao giờ trả lại bất cứ thứ gì. Trong bash 4đó có một lệnh chính xác có thể cung cấp cho chúng ta như vậy fifo: coproc. Nếu chúng tôi cũng chờ chặn read(đó là của chúng tôi coproc), chúng tôi đã hoàn thành. Đáng buồn thay, điều này cần phải tiếp tục mở hai PID và a fifo.

Biến thể có tên fifo

Nếu bạn không bận tâm sử dụng tên fifo, bạn có thể làm như sau:

mkfifo "$HOME/.pause.fifo" 2>/dev/null; read <"$HOME/.pause.fifo"

Không sử dụng một vòng lặp trong quá trình đọc là một chút cẩu thả, nhưng bạn có thể sử dụng lại điều này bao nhiêu lần fifotùy thích và thực hiện thuật ngữ reads bằng cách sử dụng touch "$HOME/.pause.fifo"(nếu có nhiều hơn một lần đọc chờ, tất cả sẽ bị chấm dứt cùng một lúc).

Hoặc sử dụng Linux pause()syscall

Đối với việc chặn vô hạn, có một cuộc gọi nhân Linux, được gọi pause(), thực hiện điều chúng ta muốn: Đợi mãi mãi (cho đến khi có tín hiệu đến). Tuy nhiên, không có chương trình không gian người dùng cho việc này (chưa).

C

Tạo một chương trình như vậy là dễ dàng. Đây là đoạn trích để tạo một chương trình Linux rất nhỏ được gọi là pausetạm dừng vô thời hạn (nhu cầu diet, gccv.v.):

printf '#include <unistd.h>\nint main(){for(;;)pause();}' > pause.c;
diet -Os cc pause.c -o pause;
strip -s pause;
ls -al pause

python

Nếu bạn không muốn tự biên dịch một cái gì đó, nhưng bạn đã pythoncài đặt, bạn có thể sử dụng cái này trong Linux:

python -c 'while 1: import ctypes; ctypes.CDLL(None).pause()'

(Lưu ý: Sử dụng exec python -c ...để thay thế lớp vỏ hiện tại, điều này giải phóng một PID. Giải pháp có thể được cải thiện với một số chuyển hướng IO, giải phóng các FD không sử dụng. Điều này tùy thuộc vào bạn.)

Cách thức hoạt động (tôi nghĩ): ctypes.CDLL(None)tải thư viện C tiêu chuẩn và chạy pause()chức năng trong đó trong một số vòng lặp bổ sung. Ít hiệu quả hơn phiên bản C, nhưng hoạt động.

Đề nghị của tôi cho bạn:

Ở trong giấc ngủ lặp. Thật dễ hiểu, rất dễ mang theo và hầu hết thời gian.


1
@Andrew Thông thường bạn không cần trap(điều chỉnh hành vi của vỏ thành tín hiệu) cũng như nền (cho phép vỏ chặn tín hiệu từ thiết bị đầu cuối, như Strg + C). Như vậy sleep infinitylà đủ (hành xử như exec sleep infinitythể đó là tuyên bố cuối cùng. Để thấy sự khác biệt sử dụng strace -ffDI4 bash -c 'YOURCODEHERE'). Giấc ngủ lặp là tốt hơn, bởi vì sleepcó thể trở lại trong một số trường hợp nhất định. Ví dụ: bạn không muốn X11 tắt đột ngột trên a killall sleep, chỉ vì .xstartupkết thúc sleep infinitythay vì vòng lặp ngủ.
Tino

Có thể hơi mơ hồ, nhưng s6-pauselà một lệnh userland để chạy pause(), tùy ý bỏ qua các tín hiệu khác nhau.
Patrick

@Tino /bin/sleepkhông giới hạn sau 24 ngày như bạn nói. Sẽ thật tốt nếu bạn có thể cập nhật điều đó. Trên Linux ngay bây giờ, này đang hoạt động. Nó giới hạn các nanosleep()tòa nhà riêng lẻ đến 24 ngày, nhưng gọi chúng trong một vòng lặp. Vì vậy, sleep infinitykhông nên thoát sau 24 ngày. Các doublevô cực dương được chuyển đổi sang một struct timespec. Nhìn vào rpl_nanosleepGDB, infinityđược chuyển đổi sang { tv_sec = 9223372036854775807, tv_nsec = 999999999 }trên Ubuntu 16.04.
nh2

@ nh2 Nó đã được đề cập trong văn bản rằng giấc ngủ có thể lặp lại thay vì bị chặn hoàn toàn. Tôi đã chỉnh sửa nó một chút để hy vọng làm cho sự thật này rõ ràng hơn một chút. Xin lưu ý " có lẽ " này, bởi vì stracemột mình tôi không thể chứng minh được thực tế là có một số mã lặp được biên dịch sleepvà tôi không muốn đợi 24 ngày chỉ để kiểm tra điều này (hoặc dịch ngược /bin/sleep). Luôn luôn tốt hơn để lập trình phòng thủ, nếu không có bằng chứng toán học cứng, rằng một cái gì đó thực sự là, như nó có vẻ là. Cũng không bao giờ tin bất cứ điều gì:killall -9 sleep
Tino

Tùy chọn tạm dừng () có thể được thực hiện khá dễ dàng với perl: perl -MPOSIX -e 'pause ()'
tgoodhart

70

Có thể điều này có vẻ xấu, nhưng tại sao không chạy catvà để nó chờ đầu vào mãi mãi?


4
Điều này không hoạt động nếu bạn không có ống treo để đọc. Xin tư vấn.
Matt Joiner

2
@Matt, có thể làm một cái ống và catnó? mkfifo pipe && cat pipe
Michał Trybus

Những gì @twalberg nói, nhưng ngoài ra, bạn có thể ngay lập tức gán lại cho 3 và hủy liên kết nó, như được hiển thị ở đây: superuser.com/a/633185/762481
jp48

32

TL; DR: sleep infinitythực sự ngủ thời gian tối đa cho phép, là hữu hạn.

Tự hỏi tại sao điều này không được ghi lại ở bất cứ đâu, tôi bận tâm đọc các nguồn từ GNU coreutils và tôi thấy nó thực thi đại khái những gì sau:

  1. Sử dụng strtodtừ C stdlib trên đối số đầu tiên để chuyển đổi 'vô cực' thành độ chính xác kép. Vì vậy, giả sử độ chính xác kép của IEEE 754, giá trị vô cực dương 64 bit được lưu trong secondsbiến.
  2. Gọi xnanosleep(seconds)( tìm thấy trong gnulib ), điều này lần lượt gọi dtotimespec(seconds)( cũng trong gnulib ) để chuyển đổi từ doublesang struct timespec.
  3. struct timespecchỉ là một cặp số: phần nguyên (tính bằng giây) và phần phân số (tính bằng nano giây). Chuyển đổi vô cùng tích cực thành số nguyên sẽ dẫn đến hành vi không xác định (xem §6.3.1.4 từ tiêu chuẩn C), vì vậy thay vào đó, nó cắt ngắn thành TYPE_MAXIMUM (time_t).
  4. Giá trị thực tế của TYPE_MAXIMUM (time_t)không được đặt trong tiêu chuẩn (thậm chí sizeof(time_t)là không); vì vậy, ví dụ, hãy chọn x86-64 từ nhân Linux gần đây.

Đây là TIME_T_MAXtrong nhân Linux, được định nghĩa ( time.h) là:

(time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)

Lưu ý rằng time_t__kernel_time_ttime_tlong; mô hình dữ liệu LP64 được sử dụng, sizeof(long)8 (64 bit) cũng vậy.

Kết quả nào trong : TIME_T_MAX = 9223372036854775807.

Đó là: sleep infinitekết quả trong thời gian ngủ thực tế là 9223372036854775807 giây (10 ^ 11 năm). Và đối với các hệ thống linux 32 bit ( sizeof(long)là 4 (32 bit)): 2147483647 giây (68 năm; xem thêm vấn đề năm 2038 ).


Chỉnh sửa : rõ ràng nanosecondshàm được gọi không trực tiếp là tòa nhà, mà là một trình bao bọc phụ thuộc vào hệ điều hành (cũng được định nghĩa trong gnulib ).

Có thêm một bước kết quả là: đối với một số các hệ thống mà HAVE_BUG_BIG_NANOSLEEPtruegiấc ngủ sẽ bỏ bớt 24 ngày và sau đó được gọi trong một vòng lặp. Đây là trường hợp đối với một số (hoặc tất cả?) Các bản phân phối Linux. Lưu ý rằng trình bao bọc này có thể không được sử dụng nếu thử nghiệm cấu hình- time thành công ( nguồn ).

Cụ thể, đó sẽ là 24 * 24 * 60 * 60 = 2073600 seconds(cộng 999999999 nano giây); nhưng điều này được gọi trong một vòng lặp để tôn trọng tổng thời gian ngủ được chỉ định. Do đó, kết luận trước đó vẫn còn hiệu lực.


Tóm lại, thời gian ngủ kết quả không phải là vô hạn nhưng đủ cao cho tất cả các mục đích thực tế , ngay cả khi thời gian trôi đi thực tế không mang theo; điều đó phụ thuộc vào hệ điều hành và kiến ​​trúc.

Để trả lời câu hỏi ban đầu, điều này rõ ràng là đủ tốt nhưng nếu vì một lý do nào đó (một hệ thống rất hạn chế về tài nguyên), bạn thực sự muốn tránh một bộ đếm thời gian đếm ngược vô dụng, tôi đoán cách thay thế chính xác nhất là sử dụng catphương pháp được mô tả trong các câu trả lời khác .


1
Trong các lõi tiếp theo, sleep infinitybây giờ sẽ thực sự ngủ mãi mà không cần lặp lại: list.gnu.org/archive/html/orms-gnulib/2020-02/msg00081.html
Vladimir Panteleev

8

sleep infinitytrông thanh lịch nhất, nhưng đôi khi nó không hoạt động vì một số lý do. Trong trường hợp đó, bạn có thể thử lệnh chặn khác như cat, read, tail -f /dev/null, grep a, vv


1
tail -f /dev/nullcũng là một giải pháp hiệu quả cho tôi trên nền tảng SaaS
schmunk

2
tail -f /dev/nullcũng có ưu điểm là không tiêu thụ stdin. Tôi đã sử dụng nó cho lý do đó.
Sudo Bash

Những người xem xét tùy chọn này nên đọc câu trả lời này để tìm hiểu về sự phân nhánh của tùy chọn này.
Bóng tối

6

Còn việc gửi SIGSTOP cho chính nó thì sao?

Điều này sẽ tạm dừng quá trình cho đến khi nhận được SIGCONT. Đó là trong trường hợp của bạn: không bao giờ.

kill -STOP "$$";
# grace time for signal delivery
sleep 60;

6
Tín hiệu không đồng bộ. Vì vậy, những điều sau đây có thể xảy ra: a) shell call kill kill báo hiệu STOP sang shell
không phải người dùng

1
@temple Cái nhìn sâu sắc, không nghĩ về bản chất không đồng bộ của tín hiệu. Cảm ơn!
michuelnik

4

Hãy để tôi giải thích tại sao sleep infinityhoạt động mặc dù nó không được ghi lại. Câu trả lời của jp48 cũng hữu ích.

Điều quan trọng nhất: Bằng cách chỉ định infhoặc infinity(cả hai trường hợp không nhạy cảm), bạn có thể ngủ trong thời gian dài nhất cho phép thực hiện (nghĩa là giá trị nhỏ hơn HUGE_VALTYPE_MAXIMUM(time_t)).

Bây giờ hãy đi sâu vào chi tiết. Mã nguồn của sleeplệnh có thể được đọc từ coreutils / src / sleep.c . Về cơ bản, chức năng thực hiện điều này:

double s; //seconds
xstrtod (argv[i], &p, &s, cl_strtod); //`p` is not essential (just used for error check).
xnanosleep (s);

Hiểu xstrtod (argv[i], &p, &s, cl_strtod)

xstrtod()

Theo gnulib / lib / xstrtod.c , lệnh gọi xstrtod()chuyển đổi chuỗi argv[i]thành giá trị dấu phẩy động và lưu trữ nó *s, sử dụng hàm chuyển đổi cl_strtod().

cl_strtod()

Như có thể thấy từ coreutils / lib / cl-strtod.c , cl_strtod()chuyển đổi một chuỗi thành giá trị dấu phẩy động, sử dụng strtod().

strtod()

Theo đó man 3 strtod, strtod()chuyển đổi một chuỗi thành một giá trị của loại double. Trang này nói

Dạng dự kiến ​​của chuỗi (phần ban đầu của chuỗi) là ... hoặc (iii) vô cực hoặc ...

và một vô cực được định nghĩa là

Một vô cực là "INF" hoặc "INFINITY", không tính đến trường hợp.

Mặc dù tài liệu nói

Nếu giá trị đúng sẽ gây ra tràn, cộng hoặc trừ HUGE_VAL( HUGE_VALF, HUGE_VALL) được trả về

, không rõ làm thế nào một vô cực được điều trị. Vì vậy, hãy xem mã nguồn gnulib / lib / strtod.c . Những gì chúng tôi muốn đọc là

else if (c_tolower (*s) == 'i'
         && c_tolower (s[1]) == 'n'
         && c_tolower (s[2]) == 'f')
  {
    s += 3;
    if (c_tolower (*s) == 'i'
        && c_tolower (s[1]) == 'n'
        && c_tolower (s[2]) == 'i'
        && c_tolower (s[3]) == 't'
        && c_tolower (s[4]) == 'y')
      s += 5;
    num = HUGE_VAL;
    errno = saved_errno;
  }

Do đó, INFINFINITY(cả hai trường hợp không nhạy cảm) được coi là HUGE_VAL.

HUGE_VAL gia đình

Hãy sử dụng N1570 làm tiêu chuẩn C. HUGE_VAL, HUGE_VALFHUGE_VALLmacro được định nghĩa trong §7.12-3

Macro
    HUGE_VAL
mở rộng thành biểu thức hằng số kép dương, không nhất thiết phải biểu diễn dưới dạng float. Các macro
    HUGE_VALF
    HUGE_VALL
tương ứng là float và tương tự kép dài của HUGE_VAL.

HUGE_VAL, HUGE_VALFHUGE_VALLcó thể là vô số tích cực trong một triển khai hỗ trợ cho các phần tử.

và trong §7.12.1-5

Nếu một kết quả nổi tràn và làm tròn mặc định có hiệu lực, thì hàm trả về giá trị của vĩ mô HUGE_VAL, HUGE_VALFhoặc HUGE_VALLtheo kiểu trả về

Hiểu xnanosleep (s)

Bây giờ chúng tôi hiểu tất cả các bản chất của xstrtod(). Từ những giải thích ở trên, rõ ràng là xnanosleep(s)chúng ta đã thấy ý nghĩa thực sự đầu tiên xnanosleep(HUGE_VALL).

xnanosleep()

Theo mã nguồn gnulib / lib / xnanos ngủ.c , xnanosleep(s)về cơ bản thực hiện điều này:

struct timespec ts_sleep = dtotimespec (s);
nanosleep (&ts_sleep, NULL);

dtotimespec()

Hàm này chuyển đổi một đối số của kiểu doublethành một đối tượng của kiểu struct timespec. Vì nó rất đơn giản, hãy để tôi trích dẫn mã nguồn gnulib / lib / dtotimespec.c . Tất cả các ý kiến ​​được thêm vào bởi tôi.

struct timespec
dtotimespec (double sec)
{
  if (! (TYPE_MINIMUM (time_t) < sec)) //underflow case
    return make_timespec (TYPE_MINIMUM (time_t), 0);
  else if (! (sec < 1.0 + TYPE_MAXIMUM (time_t))) //overflow case
    return make_timespec (TYPE_MAXIMUM (time_t), TIMESPEC_HZ - 1);
  else //normal case (looks complex but does nothing technical)
    {
      time_t s = sec;
      double frac = TIMESPEC_HZ * (sec - s);
      long ns = frac;
      ns += ns < frac;
      s += ns / TIMESPEC_HZ;
      ns %= TIMESPEC_HZ;

      if (ns < 0)
        {
          s--;
          ns += TIMESPEC_HZ;
        }

      return make_timespec (s, ns);
    }
}

Do time_tđược định nghĩa là một loại tích phân (xem §7.27.1-3), nên chúng ta giả sử giá trị tối đa của loại time_tnhỏ hơn HUGE_VAL(loại double), có nghĩa là chúng ta nhập trường hợp tràn. (Trên thực tế, giả định này là không cần thiết vì, trong mọi trường hợp, quy trình về cơ bản là giống nhau.)

make_timespec()

Bức tường cuối cùng chúng ta phải leo lên là make_timespec(). Rất may, nó đơn giản đến mức trích dẫn mã nguồn gnulib / lib / timespec.h là đủ.

_GL_TIMESPEC_INLINE struct timespec
make_timespec (time_t s, long int ns)
{
  struct timespec r;
  r.tv_sec = s;
  r.tv_nsec = ns;
  return r;
}

2

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

Cách tiếp cận này sẽ không tiêu tốn bất kỳ tài nguyên nào để duy trì quá trình sống.

while :; do sleep 1; done & kill -STOP $! && wait $!

Phá vỡ

  • while :; do sleep 1; done & Tạo một quá trình giả trong nền
  • kill -STOP $! Dừng quá trình nền
  • wait $! Đợi quá trình nền, điều này sẽ bị chặn vĩnh viễn, vì quá trình nền đã bị dừng trước khi

0

Thay vì giết trình quản lý cửa sổ, hãy thử chạy cái mới với --replacehoặc -replacenếu có.


1
Nếu tôi sử dụng --replacetôi luôn nhận được một cảnh báo như thế another window manager is already running. Điều đó không có ý nghĩa nhiều với tôi tho.
watain

-2
while :; do read; done

Không chờ đợi quá trình ngủ của trẻ.


1
Điều này ăn stdinnếu điều này vẫn xảy ra để được kết nối với tty. Nếu bạn chạy nó với < /dev/nullcác vòng lặp bận rộn. Nó có thể được sử dụng trong một số trường hợp nhất định, vì vậy tôi không downvote.
Tino

1
Đây là một ý tưởng rất tồi, nó sẽ chỉ tiêu thụ rất nhiều cpu.
Mohammed Noureldin
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.