Theo dõi một tập tin cho đến khi tìm thấy một chuỗi


60

Tôi đang sử dụng tail -f để theo dõi một tệp nhật ký đang được tích cực ghi vào. Khi một chuỗi nhất định được ghi vào tệp nhật ký, tôi muốn thoát khỏi giám sát và tiếp tục với phần còn lại của tập lệnh.

Hiện tại tôi đang sử dụng:

tail -f logfile.log | grep -m 1 "Server Started"

Khi chuỗi được tìm thấy, grep thoát như mong đợi, nhưng tôi cần tìm cách làm cho lệnh tail thoát quá để tập lệnh có thể tiếp tục.


Tôi tự hỏi hệ điều hành mà poster ban đầu đang chạy. Trên hệ thống Linux RHEL5, tôi đã rất ngạc nhiên khi thấy lệnh đuôi chỉ đơn giản là chết khi lệnh grep đã tìm thấy kết quả khớp và thoát.
ZaSter

4
@ZaSter: tailChết chỉ ở dòng tiếp theo. Hãy thử điều này: date > log; tail -f log | grep -m 1 triggervà sau đó trong một shell khác: echo trigger >> logvà bạn sẽ thấy đầu ra triggertrong shell đầu tiên, nhưng không chấm dứt lệnh. Sau đó thử: date >> logtrong shell thứ hai và lệnh trong shell đầu tiên sẽ chấm dứt. Nhưng đôi khi điều này là quá muộn; chúng tôi muốn chấm dứt ngay khi dòng kích hoạt xuất hiện, không phải khi dòng sau khi dòng kích hoạt hoàn tất.
Alfe

Đó là một lời giải thích và ví dụ tuyệt vời, @Alfe.
ZaSter

1
giải pháp mạnh mẽ một dòng thanh lịch là sử dụng tail+ grep -qnhư câu trả lời của 00prometheus
Trevor Boyd Smith

Câu trả lời:


41

Một lớp lót POSIX đơn giản

Đây là một lót đơn giản. Nó không cần các thủ thuật bash cụ thể hoặc không phải POSIX, hoặc thậm chí là một đường ống có tên. Tất cả những gì bạn thực sự cần là tách rời việc chấm dứt tailtừ grep. Bằng cách đó, một khi grepkết thúc, kịch bản có thể tiếp tục ngay cả khi tailchưa kết thúc. Vì vậy, phương pháp đơn giản này sẽ đưa bạn đến đó:

( tail -f -n0 logfile.log & ) | grep -q "Server Started"

grepsẽ chặn cho đến khi tìm thấy chuỗi, sau đó nó sẽ thoát. Bằng cách tailchạy từ lớp vỏ riêng của nó, chúng ta có thể đặt nó ở chế độ nền để nó chạy độc lập. Trong khi đó, shell chính có thể tự do tiếp tục thực thi tập lệnh ngay khi grepthoát. tailsẽ nán lại trong lớp vỏ phụ của nó cho đến khi dòng tiếp theo được ghi vào logfile, rồi thoát (có thể ngay cả sau khi tập lệnh chính đã kết thúc). Điểm chính là đường ống không còn chờ tailkết thúc, vì vậy đường ống thoát ra ngay khi grepthoát ra.

Một số điều chỉnh nhỏ:

  • Tùy chọn -n0 để taillàm cho nó bắt đầu đọc từ dòng logfile cuối cùng hiện tại, trong trường hợp chuỗi tồn tại trước đó trong logfile.
  • Bạn có thể muốn cho tail-F chứ không phải -f. Nó không phải là POSIX, nhưng nó cho phép tailhoạt động ngay cả khi nhật ký được xoay trong khi chờ.
  • Tùy chọn -q thay vì -m1 thực hiện grepthoát sau lần xuất hiện đầu tiên, nhưng không in ra dòng kích hoạt. Ngoài ra, đó là POSIX, mà -m1 thì không.

3
Sự chấp thuận này sẽ để lại tailchạy trong nền mãi mãi. Làm thế nào bạn có thể chụp được tailPID trong lớp vỏ phụ được nền và phơi nó ra lớp vỏ chính? Tôi chỉ có thể đưa ra cách giải quyết tùy chọn phụ bằng cách hủy tất cả các tailquy trình đính kèm phiên , sử dụng pkill -s 0 tail.
Rick van der Zwet

1
Trong hầu hết các trường hợp sử dụng, nó không phải là một vấn đề. Lý do bạn đang làm điều này ở nơi đầu tiên là vì bạn đang mong đợi nhiều dòng được ghi vào tệp nhật ký. tailsẽ chấm dứt ngay khi nó cố gắng ghi vào một đường ống bị hỏng. Đường ống sẽ vỡ ngay sau khi grephoàn thành, vì vậy một khi grepđã hoàn thành, tailsẽ chấm dứt sau khi tệp nhật ký có thêm một dòng trong đó.
00prometheus

Khi tôi sử dụng giải pháp này, tôi đã không làm nền tail -f.
Trevor Boyd Smith

2
@Trevor Boyd Smith, vâng, nó hoạt động trong hầu hết các tình huống, nhưng vấn đề của OP là grep sẽ không hoàn thành cho đến khi thoát khỏi đuôi và đuôi sẽ không thoát cho đến khi một dòng khác xuất hiện trong tệp nhật ký sau khi grep đã thoát (khi đuôi cố gắng cung cấp cho các đường ống đã bị phá vỡ bởi grep kết thúc). Vì vậy, trừ khi bạn có đuôi nền, tập lệnh của bạn sẽ không tiếp tục thực thi cho đến khi một dòng bổ sung xuất hiện trong tệp nhật ký, thay vì chính xác trên dòng mà grep bắt được.
00prometheus

"Đường mòn sẽ không thoát cho đến khi một dòng khác xuất hiện sau [mẫu chuỗi yêu cầu]": Điều đó rất tinh tế và tôi hoàn toàn bỏ lỡ điều đó. Tôi đã không chú ý bởi vì mẫu tôi đang tìm kiếm ở giữa và tất cả đã được in ra nhanh chóng. (Một lần nữa hành vi bạn mô tả là rất tinh tế)
Trevor Boyd Smith

59

Câu trả lời được chấp nhận không hoạt động đối với tôi, cộng với nó khó hiểu và nó thay đổi tệp nhật ký.

Tôi đang sử dụng một cái gì đó như thế này:

tail -f logfile.log | while read LOGLINE
do
   [[ "${LOGLINE}" == *"Server Started"* ]] && pkill -P $$ tail
done

Nếu dòng nhật ký khớp với mẫu, giết chết tailbắt đầu bởi tập lệnh này.

Lưu ý: nếu bạn cũng muốn xem đầu ra trên màn hình, | tee /dev/ttyhoặc lặp lại dòng trước khi kiểm tra trong vòng lặp while.


2
Điều này hoạt động, nhưng pkillkhông được chỉ định bởi POSIX và không có sẵn ở mọi nơi.
Richard Hansen

2
Bạn không cần một vòng lặp while. sử dụng đồng hồ với tùy chọn -g và bạn có thể sử dụng lệnh pkill khó chịu.
l1zard 17/2/2015

@ l1zard Bạn có thể làm thịt nó ra không? Làm thế nào bạn sẽ xem phần đuôi của một tệp nhật ký cho đến khi một dòng cụ thể xuất hiện? (Ít quan trọng hơn, nhưng tôi cũng tò mò khi xem -g đã được thêm vào; Tôi có một máy chủ Debian mới hơn với tùy chọn đó và một máy chủ cũ khác dựa trên RHEL mà không có nó).
Rob Whelan

Nó không hoàn toàn rõ ràng với tôi tại sao đuôi thậm chí còn cần thiết ở đây. Theo tôi hiểu chính xác, người dùng muốn thực thi một lệnh cụ thể khi một từ khóa nhất định trong tệp nhật ký xuất hiện. Lệnh được đưa ra dưới đây bằng cách sử dụng đồng hồ thực hiện nhiệm vụ này.
l1zard

Không hoàn toàn - nó kiểm tra khi một chuỗi đã cho được thêm vào tệp nhật ký. Tôi sử dụng điều này để kiểm tra khi Tomcat hoặc JBoss được khởi động hoàn toàn; họ viết "Máy chủ bắt đầu" (hoặc tương tự) mỗi lần điều đó xảy ra.
Rob Whelan

16

Nếu bạn đang sử dụng Bash (ít nhất, nhưng có vẻ như nó không được xác định bởi POSIX, vì vậy nó có thể bị thiếu trong một số shell), bạn có thể sử dụng cú pháp

grep -m 1 "Server Started" <(tail -f logfile.log)

Nó hoạt động khá giống như các giải pháp FIFO đã được đề cập, nhưng đơn giản hơn nhiều để viết.


1
Điều này hoạt động, nhưng đuôi vẫn chạy cho đến khi bạn gửi SIGTERM(Ctrl + C, lệnh thoát hoặc giết nó)
mems

3
@mems, bất kỳ dòng bổ sung trong tệp nhật ký sẽ làm. Họ tailsẽ đọc nó, cố gắng xuất nó và sau đó nhận được một SIGPIPE sẽ chấm dứt nó. Vì vậy, về nguyên tắc bạn đúng; nó tailcó thể chạy vô thời hạn nếu không có gì được ghi vào tệp nhật ký một lần nữa. Trong thực tế, đây có thể là một giải pháp rất gọn gàng cho nhiều người.
Alfe 17/2/2015

14

Có một vài cách tailđể thoát ra:

Cách tiếp cận kém: Buộc tailphải viết một dòng khác

Bạn có thể buộc tailphải viết một dòng đầu ra khác ngay sau khi grepđã tìm thấy một kết quả khớp và thoát. Điều này sẽ gây ra tailđể có được một SIGPIPE, khiến nó thoát ra. Một cách để làm điều này là sửa đổi tệp đang được theo dõi tailsau khi grepthoát.

Dưới đây là một số mã ví dụ:

tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }

Trong ví dụ này, catsẽ không thoát cho đến khi grepđóng thiết bị xuất chuẩn, do đó tailkhông có khả năng ghi vào đường ống trước khi grepcó cơ hội đóng stdin của nó. catđược sử dụng để tuyên truyền đầu ra tiêu chuẩn của grepkhông thay đổi.

Cách tiếp cận này tương đối đơn giản, nhưng có một số nhược điểm:

  • Nếu grepđóng stdout trước khi đóng stdin, sẽ luôn có một điều kiện cuộc đua: grepđóng stdout, kích hoạt catđể thoát, kích hoạt echo, kích hoạt tailđể tạo ra một dòng. Nếu dòng này được gửi đến greptrước đó grepđã có cơ hội đóng stdin, tailsẽ không nhận được SIGPIPEcho đến khi nó viết một dòng khác.
  • Nó yêu cầu truy cập ghi vào tệp nhật ký.
  • Bạn phải ổn với việc sửa đổi tệp nhật ký.
  • Bạn có thể làm hỏng tệp nhật ký nếu bạn tình cờ ghi cùng lúc với một quá trình khác (việc ghi có thể bị xen kẽ, khiến một dòng mới xuất hiện ở giữa thông điệp tường trình).
  • Cách tiếp cận này là cụ thể đối với tinh thần tailsẽ không làm việc với các chương trình khác.
  • Giai đoạn đường ống thứ ba làm cho nó khó khăn để có được quyền truy cập vào mã trở lại của giai đoạn đường ống thứ hai (trừ khi bạn đang sử dụng một phần mở rộng POSIX như bash's PIPESTATUSmảng). Đây không phải là vấn đề lớn trong trường hợp này vì grepsẽ luôn trả về 0, nhưng nói chung, giai đoạn giữa có thể được thay thế bằng một lệnh khác có mã trả về mà bạn quan tâm (ví dụ: một cái gì đó trả về 0 khi phát hiện "máy chủ khởi động", 1 khi "máy chủ không khởi động được" được phát hiện).

Các phương pháp tiếp theo tránh những hạn chế này.

Cách tiếp cận tốt hơn: Tránh đường ống

Bạn có thể sử dụng một FIFO để tránh đường ống hoàn toàn, cho phép thực hiện tiếp tục một lần greptrở lại. Ví dụ:

fifo=/tmp/tmpfifo.$$
mkfifo "${fifo}" || exit 1
tail -f logfile.log >${fifo} &
tailpid=$! # optional
grep -m 1 "Server Started" "${fifo}"
kill "${tailpid}" # optional
rm "${fifo}"

Các dòng được đánh dấu bằng nhận xét # optionalcó thể được gỡ bỏ và chương trình sẽ vẫn hoạt động; tailsẽ chỉ nán lại cho đến khi nó đọc một dòng đầu vào khác hoặc bị giết bởi một quá trình khác.

Ưu điểm của phương pháp này là:

  • bạn không cần phải sửa đổi tệp nhật ký
  • Cách tiếp cận hoạt động cho các tiện ích khác bên cạnh tail
  • nó không bị tình trạng chủng tộc
  • bạn có thể dễ dàng nhận được giá trị trả về của grep(hoặc bất kỳ lệnh thay thế nào bạn đang sử dụng)

Nhược điểm của phương pháp này là phức tạp, đặc biệt là quản lý FIFO: Bạn sẽ cần tạo một tên tệp tạm thời một cách an toàn và bạn sẽ cần đảm bảo rằng FIFO tạm thời bị xóa ngay cả khi người dùng nhấn Ctrl-C ở giữa kịch bản. Điều này có thể được thực hiện bằng cách sử dụng một cái bẫy.

Phương pháp thay thế: Gửi tin nhắn để giết tail

Bạn có thể lấy tailgiai đoạn đường ống để thoát bằng cách gửi tín hiệu như thế SIGTERM. Thách thức là đáng tin cậy khi biết hai điều ở cùng một vị trí trong mã: tailPID và liệu grepđã thoát.

Với một đường ống như thế tail -f ... | grep ..., thật dễ dàng để sửa đổi giai đoạn đường ống đầu tiên để lưu tailPID của một biến bằng cách đặt nền tailvà đọc $!. Cũng dễ dàng sửa đổi giai đoạn đường ống thứ hai để chạy killkhi grepthoát. Vấn đề là hai giai đoạn của đường ống chạy trong "môi trường thực thi" riêng biệt (theo thuật ngữ của tiêu chuẩn POSIX) nên giai đoạn đường ống thứ hai không thể đọc bất kỳ biến nào được đặt bởi giai đoạn đường ống đầu tiên. Không sử dụng các biến shell, giai đoạn thứ hai bằng cách nào đó phải tìm ra tailPID của nó để nó có thể tắt tailkhi greptrả về hoặc giai đoạn đầu tiên phải được thông báo bằng cách nào đó khi greptrả về.

Giai đoạn thứ hai có thể sử dụng pgrepđể có được tailPID, nhưng điều đó sẽ không đáng tin cậy (bạn có thể khớp quy trình sai) và không di động ( pgrepkhông được quy định bởi tiêu chuẩn POSIX).

Giai đoạn đầu tiên có thể gửi PID đến giai đoạn thứ hai thông qua đường ống bằng cách nhập echovào PID, nhưng chuỗi này sẽ bị lẫn với tailđầu ra của. Việc khử nhiễu cả hai có thể yêu cầu một sơ đồ thoát phức tạp, tùy thuộc vào đầu ra của tail.

Bạn có thể sử dụng một FIFO để có giai đoạn đường ống thứ hai thông báo cho giai đoạn đường ống đầu tiên khi grepthoát. Sau đó, giai đoạn đầu tiên có thể giết chết tail. Dưới đây là một số mã ví dụ:

fifo=/tmp/notifyfifo.$$
mkfifo "${fifo}" || exit 1
{
    # run tail in the background so that the shell can
    # kill tail when notified that grep has exited
    tail -f logfile.log &
    # remember tail's PID
    tailpid=$!
    # wait for notification that grep has exited
    read foo <${fifo}
    # grep has exited, time to go
    kill "${tailpid}"
} | {
    grep -m 1 "Server Started"
    # notify the first pipeline stage that grep is done
    echo >${fifo}
}
# clean up
rm "${fifo}"

Cách tiếp cận này có tất cả ưu và nhược điểm của cách tiếp cận trước, ngoại trừ nó phức tạp hơn.

Một cảnh báo về bộ đệm

POSIX cho phép các luồng stdin và stdout được đệm hoàn toàn, điều đó có nghĩa taillà đầu ra của nó có thể không được xử lý greptrong một thời gian dài tùy ý. Không nên có bất kỳ vấn đề nào trên các hệ thống GNU: GNU grepsử dụng read(), giúp tránh tất cả các bộ đệm và GNU tail -fthực hiện các cuộc gọi thông thường fflush()khi ghi vào thiết bị xuất chuẩn. Các hệ thống không phải GNU có thể phải làm một cái gì đó đặc biệt để vô hiệu hóa hoặc thường xuyên xóa bộ đệm.


Giải pháp của bạn (giống như những người khác, tôi sẽ không đổ lỗi cho bạn) sẽ bỏ lỡ những điều đã được ghi vào tệp nhật ký trước khi việc giám sát của bạn bắt đầu. Di tail -fchúc chỉ xuất ra mười dòng cuối cùng, và sau đó là tất cả những điều sau đây. Để cải thiện điều này, bạn có thể thêm tùy chọn -n 10000vào đuôi để 10000 dòng cuối cùng cũng được đưa ra.
Alfe 17/2/2015

Một ý tưởng khác: giải pháp fifo của bạn có thể được làm thẳng, tôi nghĩ, bằng cách chuyển đầu ra của tail -ffifo và greping trên nó : mkfifo f; tail -f log > f & tailpid=$! ; grep -m 1 trigger f; kill $tailpid; rm f.
Alfe 17/2/2015

@ Alfe: Tôi có thể sai, nhưng tôi tin rằng việc tail -f logghi vào FIFO sẽ khiến một số hệ thống (ví dụ: GNU / Linux) sử dụng bộ đệm dựa trên khối thay vì bộ đệm dựa trên dòng, có nghĩa là grepcó thể không nhìn thấy dòng phù hợp khi nó xuất hiện trong nhật ký. Hệ thống có thể cung cấp một tiện ích để thay đổi bộ đệm, chẳng hạn như stdbuftừ lõi GNU. Một tiện ích như vậy sẽ không di động, tuy nhiên.
Richard Hansen

1
@ Alfe: Trên thực tế, có vẻ như POSIX không nói gì về bộ đệm ngoại trừ khi tương tác với thiết bị đầu cuối, vì vậy từ góc độ tiêu chuẩn, tôi nghĩ rằng giải pháp đơn giản của bạn cũng tốt như giải pháp phức tạp của tôi. Tuy nhiên, tôi không chắc chắn 100% về cách thực hiện khác nhau trong từng trường hợp.
Richard Hansen

Trên thực tế, bây giờ tôi đi đến một grep -q -m 1 trigger <(tail -f log)đề xuất thậm chí đơn giản hơn ở nơi khác và sống với thực tế là tailchạy một dòng dài hơn trong nền hơn mức cần thiết.
Alfe

9

Hãy để tôi mở rộng câu trả lời @ 00prometheus (đây là câu trả lời hay nhất).

Có lẽ bạn nên sử dụng thời gian chờ thay vì chờ đợi vô thời hạn.

Hàm bash bên dưới sẽ chặn cho đến khi cụm từ tìm kiếm nhất định xuất hiện hoặc đạt đến thời gian chờ đã cho.

Trạng thái thoát sẽ là 0 nếu chuỗi được tìm thấy trong thời gian chờ.

wait_str() {
  local file="$1"; shift
  local search_term="$1"; shift
  local wait_time="${1:-5m}"; shift # 5 minutes as default timeout

  (timeout $wait_time tail -F -n0 "$file" &) | grep -q "$search_term" && return 0

  echo "Timeout of $wait_time reached. Unable to find '$search_term' in '$file'"
  return 1
}

Có lẽ tệp nhật ký chưa tồn tại ngay sau khi khởi chạy máy chủ của bạn. Trong trường hợp đó, bạn nên đợi nó xuất hiện trước khi tìm kiếm chuỗi:

wait_server() {
  echo "Waiting for server..."
  local server_log="$1"; shift
  local wait_time="$1"; shift

  wait_file "$server_log" 10 || { echo "Server log file missing: '$server_log'"; return 1; }

  wait_str "$server_log" "Server Started" "$wait_time"
}

wait_file() {
  local file="$1"; shift
  local wait_seconds="${1:-10}"; shift # 10 seconds as default timeout

  until test $((wait_seconds--)) -eq 0 -o -f "$file" ; do sleep 1; done

  ((++wait_seconds))
}

Đây là cách bạn có thể sử dụng nó:

wait_server "/var/log/server.log" 5m && \
echo -e "\n-------------------------- Server READY --------------------------\n"

Vậy, timeoutlệnh ở đâu?
ayanamist

Trên thực tế, sử dụng timeoutlà cách đáng tin cậy duy nhất để không bị treo vô thời hạn chờ đợi một máy chủ không thể khởi động và đã thoát.
gluk47

1
Câu trả lời này là tốt nhất. Chỉ cần sao chép chức năng và gọi nó, nó rất dễ dàng và có thể tái sử dụng
Hristo Vrigazov

6

Vì vậy, sau khi thực hiện một số thử nghiệm, tôi đã tìm thấy một cách nhanh chóng để thực hiện công việc này. Nó xuất hiện đuôi -f sẽ thoát khi grep thoát, nhưng có một nhược điểm. Nó dường như chỉ được kích hoạt nếu tập tin được mở và đóng. Tôi đã hoàn thành việc này bằng cách nối thêm chuỗi trống vào tệp khi grep tìm thấy kết quả khớp.

tail -f logfile |grep -m 1 "Server Started" | xargs echo "" >> logfile \;

Tôi không chắc tại sao mở / đóng tệp kích hoạt đuôi để nhận ra rằng đường ống bị đóng, vì vậy tôi sẽ không dựa vào hành vi này. nhưng nó dường như làm việc cho bây giờ.

Lý do nó đóng, nhìn vào cờ -F, so với cờ -f.


1
Điều này hoạt động vì việc gắn vào logfile gây tailra một dòng khác, nhưng sau đó grepđã thoát (có lẽ - có một điều kiện cuộc đua ở đó). Nếu grepđã hết thời gian tailviết một dòng khác, tailsẽ nhận được a SIGPIPE. Điều đó gây ra tailđể thoát ngay lập tức.
Richard Hansen

1
Nhược điểm của phương pháp này: (1) có một điều kiện chủng tộc (không phải lúc nào cũng có thể thoát ngay lập tức) (2) nó yêu cầu quyền truy cập ghi vào tệp nhật ký (3) bạn phải đồng ý với việc sửa đổi tệp nhật ký (4) bạn có thể làm hỏng tệp nhật ký (5) nó chỉ hoạt động cho tail(6) bạn không thể dễ dàng điều chỉnh nó để hành xử khác nhau tùy thuộc vào kết hợp chuỗi differnt ("máy chủ bắt đầu" so với "máy chủ bắt đầu thất bại") vì bạn không thể dễ dàng nhận được mã trả về của giai đoạn giữa của đường ống. Có một cách tiếp cận khác để tránh tất cả những vấn đề này - xem câu trả lời của tôi.
Richard Hansen

6

Hiện tại, như đã cho, tất cả các tail -fgiải pháp ở đây đều có nguy cơ chọn một dòng "Máy chủ bắt đầu" đã được ghi lại trước đó (có thể là một vấn đề trong trường hợp cụ thể của bạn, tùy thuộc vào số lượng dòng được ghi và xoay tệp nhật ký / cắt ngắn).

Thay vì quá phức tạp hóa mọi thứ, chỉ cần sử dụng một thông minh hơn tail, như bmike cho thấy với một đoạn trích perl. Giải pháp đơn giản nhất là giải pháp này retailcó tích hợp hỗ trợ regex với các mẫu điều kiện bắt đầudừng :

retail -f -u "Server Started" server.log > /dev/null

Điều này sẽ theo tệp như bình thường tail -fcho đến khi phiên bản mới đầu tiên của chuỗi đó xuất hiện, sau đó thoát. ( -uTùy chọn không kích hoạt trên các dòng hiện có trong 10 dòng cuối cùng của tệp khi ở chế độ "theo dõi" thông thường.)


Nếu bạn sử dụng GNU tail(từ coreutils ), tùy chọn đơn giản tiếp theo là sử dụng --pidvà một FIFO (ống có tên):

mkfifo ${FIFO:=serverlog.fifo.$$}
grep -q -m 1 "Server Started" ${FIFO}  &
tail -n 0 -f server.log  --pid $! >> ${FIFO}
rm ${FIFO}

Một FIFO được sử dụng vì các quy trình phải được bắt đầu một cách riêng biệt để có được và vượt qua một PID. Một FIFO vẫn gặp phải vấn đề tương tự về việc viết lách kịp thời tailđể nhận SIGPIPE , sử dụng --pidtùy chọn để tailthoát khi thông báo grepđã chấm dứt (thường được sử dụng để theo dõi quá trình người viết thay vì người đọc , nhưng tailkhông ' t thực sự quan tâm). Tùy chọn -n 0được sử dụng tailđể các dòng cũ không kích hoạt khớp.


Cuối cùng, bạn có thể sử dụng một đuôi trạng thái , điều này sẽ lưu trữ phần bù tập tin hiện tại để các lần gọi tiếp theo chỉ hiển thị các dòng mới (nó cũng xử lý xoay tệp). Ví dụ này sử dụng FWTK retail* cũ :

retail "${LOGFILE:=server.log}" > /dev/null   # skip over current content
while true; do
    [ "${LOGFILE}" -nt ".${LOGFILE}.off" ] && 
       retail "${LOGFILE}" | grep -q "Server Started" && break
    sleep 2
done

* Lưu ý, cùng tên, chương trình khác với tùy chọn trước đó.

Thay vì có vòng lặp CPU, hãy so sánh dấu thời gian của tệp với tệp trạng thái ( .${LOGFILE}.off) và chế độ ngủ. Sử dụng " -T" để chỉ định vị trí của tệp trạng thái nếu được yêu cầu, ở trên giả định thư mục hiện tại. Vui lòng bỏ qua điều kiện đó hoặc trên Linux, bạn có thể sử dụng hiệu quả hơn inotifywaitthay thế:

retail "${LOGFILE:=server.log}" > /dev/null
while true; do
    inotifywait -qq "${LOGFILE}" && 
       retail "${LOGFILE}" | grep -q "Server Started" && break
done

Tôi có thể kết hợp retailvới thời gian chờ, như: "Nếu 120 giây trôi qua và bán lẻ vẫn không đọc được dòng, sau đó đưa ra mã lỗi và thoát khỏi bán lẻ"?
kiltek 15/03/18

@kiltek sử dụng GNU timeout(coreutils) để khởi chạy retailvà chỉ cần kiểm tra mã thoát 124 khi hết thời gian chờ ( timeoutsẽ giết bất kỳ lệnh nào bạn sử dụng để bắt đầu sau thời gian bạn đặt)
mr.spuratic 15/03/18

4

Điều này sẽ có một chút khó khăn vì bạn sẽ phải điều khiển quá trình và báo hiệu. Nhiều bùn hơn sẽ là một giải pháp hai tập lệnh sử dụng theo dõi PID. Tốt hơn nên sử dụng các ống được đặt tên như thế này.

Bạn đang sử dụng kịch bản shell nào?

Để nhanh chóng và bẩn thỉu, một giải pháp tập lệnh - Tôi sẽ tạo một tập lệnh perl bằng File: Tail

use File::Tail;
$file=File::Tail->new(name=>$name, maxinterval=>300, adjustafter=>7);
while (defined($line=$file->read)) {
    last if $line =~ /Server started/;
}

Vì vậy, thay vì in bên trong vòng lặp while, bạn có thể lọc chuỗi khớp và thoát ra khỏi vòng lặp while để cho tập lệnh của bạn tiếp tục.

Một trong hai điều này chỉ cần học một chút để thực hiện kiểm soát luồng xem bạn đang tìm kiếm.


sử dụng bash. perl-fu của tôi không mạnh lắm, nhưng tôi sẽ thử.
Alex Hofsteede

Sử dụng đường ống - họ thích bash và bash yêu họ. (và phần mềm sao lưu của bạn sẽ tôn trọng bạn khi nó chạm vào một trong các đường ống của bạn)
bmike

maxinterval=>300có nghĩa là nó sẽ kiểm tra tệp cứ sau năm phút. Vì tôi biết rằng dòng của tôi sẽ xuất hiện trong tệp trong giây lát, tôi đang sử dụng bỏ phiếu mạnh mẽ hơn nhiều:maxinterval=>0.2, adjustafter=>10000
Stephen Ostermiller

2

chờ tập tin xuất hiện

while [ ! -f /path/to/the.file ] 
do sleep 2; done

chờ chuỗi để xuất hiện trong tập tin

while ! grep "the line you're searching for" /path/to/the.file  
do sleep 10; done

https://superuser.com/a/743693/129669


2
Việc bỏ phiếu này có hai nhược điểm chính: 1. Nó lãng phí thời gian tính toán bằng cách đi qua nhật ký nhiều lần. Hãy xem xét một /path/to/the.filecái có dung lượng lớn 1,4 GB; thì rõ ràng đây là một vấn đề. 2. Nó chờ lâu hơn mức cần thiết khi mục nhật ký đã xuất hiện, trong trường hợp xấu nhất là 10 giây.
Alfe 17/2/2015

2

Tôi không thể tưởng tượng một giải pháp sạch hơn giải pháp này:

#!/usr/bin/env bash
# file : untail.sh
# usage: untail.sh logfile.log "Server Started"
(echo $BASHPID; tail -f $1) | while read LINE ; do
    if [ -z $TPID ]; then
        TPID=$LINE # the first line is used to store the previous subshell PID
    else
        echo "$LINE"; [[ "$LINE" == *"${*:2}"* ]] && kill -3 $TPID && break
    fi
done

ok, có lẽ tên có thể được cải thiện ...

Ưu điểm:

  • nó không sử dụng bất kỳ tiện ích đặc biệt nào
  • nó không ghi vào đĩa
  • nó duyên dáng thoát khỏi đuôi và đóng đường ống
  • nó khá ngắn và dễ hiểu

2

Bạn không cần đuôi để làm điều đó. Tôi nghĩ rằng lệnh đồng hồ là những gì bạn đang tìm kiếm. Lệnh watch theo dõi đầu ra của một tập tin và có thể được kết thúc bằng tùy chọn -g khi đầu ra thay đổi.

watch -g grep -m 1 "Server Started" logfile.log && Yournextaction

1
Bởi vì điều này cứ hai giây lại chạy một lần nên nó không thoát ngay lập tức khi dòng xuất hiện trong tệp nhật ký. Ngoài ra, nó không hoạt động tốt nếu tệp nhật ký rất lớn.
Richard Hansen


1

Alex tôi nghĩ rằng điều này sẽ giúp bạn rất nhiều.

tail -f logfile |grep -m 1 "Server Started" | xargs echo "" >> /dev/null ;

lệnh này sẽ không bao giờ đưa ra một mục trên logfile nhưng sẽ âm thầm ...


1
Điều này sẽ không hoạt động - bạn phải nối thêm vào logfilenếu không nó có thể là một thời gian dài tùy ý trước khi đưa tailra một dòng khác và phát hiện grepđã chết (thông qua SIGPIPE).
Richard Hansen

1

Đây là một giải pháp tốt hơn nhiều mà không yêu cầu bạn phải ghi vào logfile, điều này rất nguy hiểm hoặc thậm chí là không thể trong một số trường hợp.

sh -c 'tail -n +0 -f /tmp/foo | { sed "/EOF/ q" && kill $$ ;}'

Hiện tại nó chỉ có một tác dụng phụ, tailquá trình sẽ vẫn ở chế độ nền cho đến khi dòng tiếp theo được ghi vào nhật ký.


tail -n +0 -fbắt đầu từ đầu tập tin tail -n 0 -fbắt đầu từ cuối tập tin
Stephen Ostermiller

1
Một tác dụng phụ khác tôi nhận được:myscript.sh: line 14: 7845 Terminated sh -c 'tail...
Stephen Ostermiller

Tôi tin rằng "danh sách tiếp theo" phải là "dòng tiếp theo" trong câu trả lời này.
Stephen Ostermiller

Điều này hoạt động, nhưng một tailquá trình vẫn chạy trong nền.
cbaldan

1

Các giải pháp khác ở đây có một số vấn đề:

  • nếu quá trình đăng nhập bị hỏng hoặc đi xuống trong vòng lặp, chúng sẽ chạy vô thời hạn
  • chỉnh sửa nhật ký chỉ nên xem
  • không cần thiết phải viết một tập tin bổ sung
  • không cho phép logic bổ sung

Đây là những gì tôi đã đưa ra bằng cách sử dụng tomcat làm ví dụ (loại bỏ băm nếu bạn muốn xem nhật ký trong khi bắt đầu):

function startTomcat {
    loggingProcessStartCommand="${CATALINA_HOME}/bin/startup.sh"
    loggingProcessOwner="root"
    loggingProcessCommandLinePattern="${JAVA_HOME}"
    logSearchString="org.apache.catalina.startup.Catalina.start Server startup"
    logFile="${CATALINA_BASE}/log/catalina.out"

    lineNumber="$(( $(wc -l "${logFile}" | awk '{print $1}') + 1 ))"
    ${loggingProcessStartCommand}
    while [[ -z "$(sed -n "${lineNumber}p" "${logFile}" | grep "${logSearchString}")" ]]; do
        [[ -z "$(ps -ef | grep "^${loggingProcessOwner} .* ${loggingProcessCommandLinePattern}" | grep -v grep)" ]] && { echo "[ERROR] Tomcat failed to start"; return 1; }
        [[ $(wc -l "${logFile}" | awk '{print $1}') -lt ${lineNumber} ]] && continue
        #sed -n "${lineNumber}p" "${logFile}"
        let lineNumber++
    done
    #sed -n "${lineNumber}p" "${logFile}"
    echo "[INFO] Tomcat has started"
}

1

Các taillệnh có thể được backgrounded và pid của nó vang vọng đến grepsubshell. Trong phần con, grepmột trình xử lý bẫy trên EXIT có thể giết taillệnh.

( (sleep 1; exec tail -f logfile.log) & echo $! ; wait ) | 
     (trap 'kill "$pid"' EXIT; pid="$(head -1)"; grep -m 1 "Server Started")

1

Đọc tất cả. tldr: tách rời phần đuôi của grep.

Hai hình thức thuận tiện nhất là

( tail -f logfile.log & ) | grep -q "Server Started"

và nếu bạn có bash

grep -m 1 "Server Started" <(tail -f logfile.log)

Nhưng nếu cái đuôi đó ngồi ở phía sau làm phiền bạn, có cách nào đẹp hơn fifo hoặc bất kỳ câu trả lời nào khác ở đây. Yêu cầu bash.

coproc grep -m 1 "Server Started"
tail -F /tmp/x --pid $COPROC_PID >&${COPROC[1]}

Hoặc nếu nó không phải là đuôi đang tạo ra những thứ,

coproc command that outputs
grep -m 1 "Sever Started" ${COPROC[0]}
kill $COPROC_PID

0

Cố gắng sử dụng inotify (inotifywait)

Bạn thiết lập inotifywait cho bất kỳ thay đổi tập tin, sau đó kiểm tra tệp với grep, nếu không tìm thấy chỉ cần chạy lại inotifywait, nếu tìm thấy thoát khỏi vòng lặp ... Smth như thế


Bằng cách này, toàn bộ tệp sẽ phải được kiểm tra lại mỗi khi có gì đó được ghi vào nó. Không hoạt động tốt cho các tệp nhật ký.
grawity

1
Một cách khác là tạo hai tập lệnh: 1. tail -f logfile.log | grep -m 1 "Máy chủ đã bắt đầu"> / tmp / Found 2. Firstscript.sh & MYPID = $!; inotifywait -e MODIFY / tmp / được tìm thấy; giết -KILL - $ MYPID
Evengard

Tôi muốn bạn chỉnh sửa câu trả lời của mình để hiển thị chụp PID và sau đó sử dụng inotifywait - một giải pháp tao nhã có thể dễ dàng nắm bắt đối với ai đó đã sử dụng grep nhưng cần một công cụ tinh vi hơn.
bmike

Một PID của những gì bạn muốn chụp? Tôi có thể cố gắng thực hiện nếu bạn giải thích thêm một chút những gì bạn muốn
Evengard

0

Bạn muốn rời khỏi ngay khi dòng được viết, nhưng bạn cũng muốn rời đi sau khi hết thời gian:

if (timeout 15s tail -F -n0 "stdout.log" &) | grep -q "The string that says the startup is successful" ; then
    echo "Application started with success."
else
    echo "Startup failed."
    tail stderr.log stdout.log
    exit 1
fi

-2

Còn cái này thì sao:

trong khi sự thật; làm nếu [ ! -z $ (grep "myRegEx" myLog.log)]; rồi phá vỡ; fi; làm xong

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.