Làm cách nào để đợi tệp trong tập lệnh shell?


14

Tôi đang cố gắng viết một tập lệnh shell sẽ đợi một tập tin xuất hiện trong /tmpthư mục được gọi sleep.txtvà một khi nó được tìm thấy, chương trình sẽ dừng lại, nếu không tôi muốn chương trình ở trạng thái ngủ (bị treo) cho đến khi tập tin được định vị . Bây giờ, tôi giả sử rằng tôi sẽ sử dụng lệnh kiểm tra. Vì vậy, một cái gì đó như

(if [ -f "/tmp/sleep.txt" ]; 
then stop 
   else sleep.)

Tôi hoàn toàn mới để viết kịch bản shell và bất kỳ trợ giúp nào đều được đánh giá cao!


Nhìn vào $MAILPATH.
mikeerv 18/2/2015

Câu trả lời:


22

Trong Linux, bạn có thể sử dụng hệ thống con kernel inotify để chờ hiệu quả của sự xuất hiện của một tệp trong một thư mục:

while read i; do if [ "$i" = sleep.txt ]; then break; fi; done \
   < <(inotifywait  -e create,open --format '%f' --quiet /tmp --monitor)
# script execution continues ...

(giả sử Bash cho <()cú pháp chuyển hướng đầu ra)

Ưu điểm của phương pháp này so với bỏ phiếu trong khoảng thời gian cố định như trong

while [ ! -f /tmp/sleep.txt ]; do sleep 1; done
# script execution continues ...

Là hạt nhân ngủ nhiều hơn. Với một đặc tả sự kiện inotify như create,opentập lệnh chỉ được lên lịch để thực thi khi một tập tin bên dưới /tmpđược tạo hoặc mở. Với việc bỏ phiếu trong khoảng thời gian cố định, bạn lãng phí chu kỳ CPU cho mỗi lần tăng.

Tôi đã bao gồm opensự kiện để đăng ký touch /tmp/sleep.txtkhi tập tin đã tồn tại.


Cảm ơn, điều này thật tuyệt vời! Tại sao bạn cần vòng lặp while nếu bạn biết tên của tệp? Tại sao không thể như vậy được inotifywait -e create,open --format '%f' --quiet /tmp/sleep.txt --monitor > /dev/null ; # continues once /tmp/sleep.txt is created/opened?
NHDaly

Ồ, jk. Sau khi cài đặt công cụ tôi hiểu bây giờ. inotifywaitbản thân nó là một vòng lặp while và nó xuất ra một dòng mỗi khi tệp được mở / tạo. Vì vậy, bạn cần vòng lặp while để phá vỡ khi nó thay đổi.
NHDaly

ngoại trừ việc điều này tạo ra một điều kiện cuộc đua - không giống như phiên bản thử nghiệm -e / ngủ. Không có cách nào để đảm bảo rằng tệp sẽ không được tạo ngay trước khi vòng lặp được nhập - trong trường hợp đó sự kiện sẽ bị bỏ lỡ. Bạn cần thêm một cái gì đó như (ngủ 1; [[-e /tmp/s ngủ.txt]] && touch /tmp/s ngủ.txt;) & trước vòng lặp. Tất nhiên điều này có thể tạo ra vấn đề, tùy thuộc vào logic kinh doanh thực tế
n-alexander

@ n-alexander Vâng, câu trả lời giả định rằng bạn thực thi đoạn mã trước khi bạn bắt đầu quá trình sẽ tạo ra sleep.txttại một số thời điểm. Như vậy, không có điều kiện chủng tộc. Câu hỏi không chứa bất cứ điều gì gợi ý về một kịch bản bạn có trong đầu.
maxschlepzig

@maxschlepzig thì ngược lại. Trừ khi đặt hàng được thi hành, nó không thể được giả định. Khái niệm cơ bản.
n-alexander

10

Chỉ cần đặt bài kiểm tra của bạn trong whilevòng lặp:

while [ ! -f /tmp/sleep.txt ]; do sleep 1; done
# next command

Vì vậy, như bạn đã viết nó, đây là logic: trong khi tệp không tồn tại, tập lệnh sẽ ngủ. Nếu không, nó đã được thực hiện. Chính xác?
Mo Gainz 17/2/2015

@MoGainz Đúng. Số sau sleeplà số giây chờ trong mỗi lần lặp - bạn có thể thay đổi tùy theo nhu cầu của mình.
jimmij 17/2/2015

7

Có một vài vấn đề với một số inotifywaitcách tiếp cận dựa trên cơ sở được đưa ra cho đến nay:

  • họ không tìm thấy sleep.txttệp đã được tạo trước tiên dưới dạng tên tạm thời và sau đó được đổi tên thành sleep.txt. Một nhu cầu phù hợp cho moved_tocác sự kiện ngoàicreate
  • Tên tệp có thể chứa các ký tự dòng mới, in tên của các tệp mới được phân tách bằng dòng là không đủ để xác định nếu một sleep.txtđã được tạo. Điều gì nếu một foo\nsleep.txt\nbartập tin đã được tạo ra ví dụ?
  • Điều gì xảy ra nếu tập tin được tạo trước đó inotifywait đã được khởi động và cài đặt đồng hồ? Sau đó inotifywaitsẽ đợi mãi cho một tập tin đã ở đây. Bạn cần chắc chắn rằng tập tin chưa có ở đó sau khi đồng hồ đã được cài đặt.
  • một số giải pháp vẫn inotifywaitchạy (ít nhất là cho đến khi tệp khác được tạo) sau khi tìm thấy tệp.

Để giải quyết những vấn đề đó, bạn có thể làm:

sh -c 'echo "$$" &&
        LC_ALL=C exec inotifywait -me create,moved_to --format=/%f/ . 2>&1' | {
  IFS= read pid &&
    while IFS= read -r line && [ "$line" != "Watches established." ]; do
      : wait for watches to be established
    done
  [ -e sleep.txt ] || [ -L sleep.txt ] || grep -qxF /sleep.txt/ && kill "$pid"
}

Lưu ý rằng chúng tôi đang theo dõi việc tạo sleep.txttrong thư mục hiện tại, .(vì vậy bạn sẽ làmcd /tmp || exit trước đây trong ví dụ của mình). Thư mục hiện tại không bao giờ thay đổi, vì vậy khi đường ống đó trả về thành công, đó là sleep.txtthư mục hiện tại đã được tạo.

Tất nhiên, bạn có thể thay thế .bằng /tmpở trên, nhưng trong khi inotifywaitđang chạy, /tmpcó thể đã được đổi tên nhiều lần (không chắc /tmp, nhưng có thể xem xét trong trường hợp chung) hoặc hệ thống tệp mới được gắn trên nó, vì vậy khi đường ống trở lại, nó có thể không là một /tmp/sleep.txtcái đã được tạo ra nhưng /new-name-for-the-original-tmp/sleep.txtthay vào đó. Một /tmpthư mục mới cũng có thể đã được tạo trong khoảng thời gian đó và thư mục đó sẽ không được xem, do đó, một thư mục sleep.txtđược tạo sẽ không bị phát hiện.


1

Câu trả lời được chấp nhận thực sự hoạt động (cảm ơn maxschlepzig) nhưng để lại giám sát inotifywait trong nền cho đến khi tập lệnh của bạn thoát. Câu trả lời duy nhất phù hợp với chính xác các yêu cầu của bạn (tức là chờ cho ngủ nghỉ hiển thị bên trong / tmp) dường như là của Stephane, nếu thư mục được theo dõi bởi inotifywait được thay đổi từ dấu chấm (.) Thành '/ tmp'.

Tuy nhiên, nếu bạn sẵn sàng sử dụng một thư mục tạm thời CHỈ để đặt cờ ngủ.txt của bạn và có thể đặt cược rằng không ai khác sẽ đặt bất kỳ tệp nào trong thư mục đó, chỉ cần yêu cầu inotifywait xem thư mục này để tạo tệp là đủ:

Bước 1: tạo thư mục bạn sẽ theo dõi:

directoryToPutSleepFile=$(mktemp -d)

Bước 2: đảm bảo thư mục thực sự ở đó

until [ -d $directoryToPutSleepFile ]; do sleep 0.1; done

Bước 3: đợi cho đến khi BẤT K file tệp nào xuất hiện bên trong $directoryToPutSleepFile

inotifywait -e create --format '%f' --quiet $directoryToPutSleepFile

Tập tin bạn sẽ đặt $directoryToPutSleepFilecó thể được đặt tên là ngủ.txt awake.txt, bất cứ điều gì. Thời điểm bất kỳ tệp nào được tạo bên trong $directoryToPutSleepFiletập lệnh của bạn sẽ tiếp tục qua inotifywaitcâu lệnh.


1
Nhưng điều đó có thể bỏ lỡ việc tạo tập tin, nếu một cái khác được tạo ra trước đó, khiến sleep.txtcho nó được tạo ra giữa hai yêu cầu inotify chờ đợi.
Stéphane Chazelas

xem câu trả lời của tôi cho một cách khác để giải quyết nó.
Stéphane Chazelas

Vui lòng thử mã của tôi. inotifywait chỉ được gọi một lần. Khi ngủ.txt được tạo (hoặc đọc / ghi nếu đã có), inotifywait output "ngủ.txt" và việc thực thi tiếp tục sau câu lệnh 'xong'.
nikolaos

Nó được gọi nhiều lần cho đến khi một tệp được gọi sleep.txtđược tạo. Ví dụ touch /tmp/foo /tmp/sleep.txt, hãy thử một trường hợp inotifywaitsẽ báo cáo foovà thoát, và vào thời điểm bắt đầu inotifywait tiếp theo, thì ngủ đông sẽ có từ lâu vì vậy inotifywait sẽ không phát hiện ra việc tạo ra nó.
Stéphane Chazelas

Bạn đã đúng về nhiều yêu cầu: một lời gọi cho mỗi tệp được tạo trong / tmp. Tôi cập nhật câu trả lời của tôi. Cám ơn bạn đã góp ý.
nikolaos

0

nói chung là khá khó để làm bất cứ điều gì ngoại trừ vòng kiểm tra / giấc ngủ đơn giản đáng tin cậy. Vấn đề chính là thử nghiệm sẽ chạy đua với việc tạo tệp - trừ khi thử nghiệm được lặp lại, sự kiện tạo có thể bị bỏ lỡ. Đây là đặt cược tốt nhất của bạn trong hầu hết các trường hợp bạn bắt đầu sử dụng Shell để bắt đầu:

#!/bin/bash
while [[ ! -e /tmp/file ]] ; do
    sleep 1
done

Nếu bạn thấy rằng điều này là không đủ vì vấn đề hiệu năng, thì sử dụng Shell có thể không phải là một ý tưởng tuyệt vời ngay từ đầu.


2
Đây chỉ là một bản sao của câu trả lời của jimmij .
maxschlepzig

0

Dựa trên câu trả lời được chấp nhận của maxschlepzig (và một ý tưởng từ câu trả lời được chấp nhận tại /superuser/270529/monitoring-a-file-until-a-opes-is-found ) Tôi đề xuất những cải tiến sau ( theo quan điểm của tôi) câu trả lời cũng có thể làm việc với thời gian chờ:

# Enable pipefail, so if the left side of the pipe fails it does not get silently ignored
set -o pipefail
( timeout 120 inotifywait -e create,open --format '%f' --quiet /tmp --monitor & ) | while read i; do if [ "$i" == 'sleep.txt' ]; then break; fi; done
EXIT_STATUS=$?
if [ "${EXIT_STATUS}" == '124' ]; then
  echo "Timeout happened"
fi

Trong trường hợp tệp không được tạo / mở trong khoảng thời gian chờ đã cho, thì trạng thái thoát là 124 (theo tài liệu hết thời gian chờ (trang man)). Trong trường hợp nó được tạo / mở, thì trạng thái thoát là 0 (thành công).

Có, inotifywait chạy trong shell phụ theo cách này và shell phụ đó sẽ chỉ kết thúc chạy khi hết thời gian chờ hoặc khi kịch bản chính thoát ra (tùy theo điều kiện nào đến trước).

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.