Làm trống một tập tin mà không làm gián đoạn ghi đường ống vào nó


12

Tôi có một chương trình mà đầu ra tôi chuyển hướng đến một tệp nhật ký:

./my_app > log

Thỉnh thoảng tôi sẽ xóa (tức là trống) nhật ký (theo yêu cầu) và thử nhiều thứ như

cat "" > log

Tuy nhiên, dường như đường ống ban đầu sau đó bị gián đoạn và chương trình không chuyển hướng đầu ra của nó sang tệp nhật ký nữa.

Có cách nào để làm điều đó?

Cập nhật

Lưu ý rằng tôi không thể sửa đổi ứng dụng sản xuất đầu ra. Nó chỉ nhổ nó ra thiết bị xuất chuẩn và tôi muốn lưu nó vào nhật ký để tôi có thể kiểm tra nó khi tôi cần, và xóa nó khi tôi muốn. Tuy nhiên tôi không cần phải khởi động lại ứng dụng.


đó là lý do tại sao bạn thường sử dụng trình nền đăng nhập để ghi nhật ký ...
Kiwy

@Kiwy bạn có thể giải thích về cách giải quyết vấn đề không?
bangnab

tốt, bạn thường sử dụng một trình nền nhật ký hoặc để ứng dụng của bạn xử lý nhật ký, bởi vì việc viết những thứ để xuất ra và chuyển hướng nó không đáng tin cậy. bạn nên xem syslogdhoặclogrotate
Kiwy

2
Làm mọi thứ hoạt động nếu bạn làm ./my_app >> log(để buộc bổ sung) và cp /dev/null logcắt ngắn nó?
Đánh dấu Plotnick

1
thông báo lỗi gì bạn nhận được? Những hành vi nào bạn nhìn thấy? "Không chuyển hướng đầu ra của nó sang tệp nhật ký nữa" không cụ thể lắm. Ngoài ra, cat "" > logkhông phải là một catlệnh hợp lệ vì không có tệp nào được gọi "".
Mikel

Câu trả lời:


13

Một dạng khác của vấn đề này xảy ra với các ứng dụng chạy dài có nhật ký được xoay theo định kỳ. Ngay cả khi bạn di chuyển nhật ký gốc (ví dụ mv log.txt log.1:) và thay thế nó ngay lập tức bằng một tệp cùng tên trước khi bất kỳ ghi nhật ký thực tế nào xảy ra, nếu quá trình đang giữ tệp mở, nó sẽ kết thúc bằng văn bảnlog.1 (vì đó vẫn có thể là mở inode) hoặc không có gì.

Một cách phổ biến để giải quyết vấn đề này (bộ ghi nhật ký hệ thống hoạt động theo cách này) là triển khai bộ xử lý tín hiệu trong quy trình sẽ đóng và mở lại nhật ký của nó. Sau đó, khi bạn muốn di chuyển hoặc xóa (bằng cách xóa) nhật ký, hãy gửi tín hiệu đó đến quy trình ngay sau đó.

Đây là một minh chứng đơn giản cho bash - tha thứ cho các kỹ năng vỏ sò của tôi (nhưng nếu bạn sẽ chỉnh sửa điều này để thực hành tốt nhất, v.v., hãy đảm bảo bạn hiểu chức năng trước và kiểm tra bản sửa đổi của bạn trước khi chỉnh sửa):

#!/bin/bash

trap sighandler INT

function sighandler () {
    touch log.txt
    exec &> log.txt
}

echo $BASHPID
exec &> log.txt

count=0;
while [ $count -lt 60 ]; do
    echo "$BASHPID Count is now $count"
    sleep 2
    ((count++))
done          

Bắt đầu điều này bằng cách rẽ vào nền:

> ./test.sh &
12356

Lưu ý rằng nó báo cáo PID của nó tới thiết bị đầu cuối và sau đó bắt đầu đăng nhập log.txt. Bây giờ bạn có 2 phút để chơi xung quanh. Đợi vài giây và thử:

> mv log.txt log.1 && kill -s 2 12356

Chỉ đơn giản kill -2 12356có thể làm việc cho bạn ở đây quá. Tín hiệu 2 là SIGINT (đó cũng là những gì Ctrl-C làm, vì vậy bạn có thể thử điều này ở nền trước và di chuyển hoặc xóa logfile khỏi thiết bị đầu cuối khác), cái mà trapnên bẫy. Để kiểm tra;

> cat log.1
12356 Count is now 0
12356 Count is now 1
12356 Count is now 2
12356 Count is now 3
12356 Count is now 4
12356 Count is now 5
12356 Count is now 6
12356 Count is now 7
12356 Count is now 8
12356 Count is now 9
12356 Count is now 10
12356 Count is now 11
12356 Count is now 12
12356 Count is now 13
12356 Count is now 14

Bây giờ hãy xem liệu nó có còn được viết cho log.txtdù chúng tôi đã chuyển nó:

> cat log.txt
12356 Count is now 15
12356 Count is now 16
12356 Count is now 17
12356 Count is now 18
12356 Count is now 19
12356 Count is now 20
12356 Count is now 21

Chú ý nó tiếp tục đi đúng nơi nó rời đi. Nếu bạn không muốn giữ bản ghi chỉ cần xóa nhật ký bằng cách xóa nó

> rm -f log.txt && kill -s 2 12356

Kiểm tra:

> cat log.txt
12356 Count is now 29
12356 Count is now 30
12356 Count is now 31
12356 Count is now 32
12356 Count is now 33
12356 Count is now 34
12356 Count is now 35
12356 Count is now 36

Vẫn đang đi.

Thật không may, bạn không thể thực hiện điều này trong tập lệnh shell cho một quy trình con được thực thi, vì nếu ở phía trước, các trình xử lý tín hiệu của bash trapbị treo và nếu bạn rẽ nhánh vào nền, bạn không thể gán lại cho nó đầu ra. Tức là, đây là điều bạn phải thực hiện trong ứng dụng của mình.

Tuy nhiên...

Nếu bạn không thể sửa đổi ứng dụng (ví dụ: vì bạn không viết nó), tôi có một tiện ích CLI mà bạn có thể sử dụng làm trung gian. Bạn cũng có thể triển khai một phiên bản đơn giản của điều này trong một tập lệnh đóng vai trò là một đường dẫn đến nhật ký:

#!/bin/bash

trap sighandler INT

function sighandler () {
    touch log.txt
    exec 1> log.txt
}

echo "$0 $BASHPID"
exec 1> log.txt

count=0;
while read; do
    echo $REPLY
done  

Hãy gọi nó là pipetrap.sh. Bây giờ chúng tôi cần một chương trình riêng để kiểm tra, bắt chước ứng dụng bạn muốn đăng nhập:

#!/bin/bash

count=0
while [ $count -lt 60 ]; do
    echo "$BASHPID Count is now $count"
    sleep 2
    ((count++))
done           

Đó sẽ là test.sh:

> (./test.sh | ./pipetrap.sh) &
./pipetrap.sh 15859

Đây là hai quá trình riêng biệt với các PID riêng biệt. Để xóa test.shđầu ra, đang được chuyển qua pipetrap.sh:

> rm -f log.txt && kill -s 2 15859

Kiểm tra:

>cat log.txt
15858 Count is now 6
15858 Count is now 7
15858 Count is now 8

15858, test.shvẫn đang chạy và đầu ra của nó đang được ghi lại. Trong trường hợp này, không cần sửa đổi cho ứng dụng.


Cảm ơn những lời giải thích tốt đẹp. Tuy nhiên trong trường hợp của tôi, tôi không thể sửa đổi ứng dụng để thực hiện giải pháp của bạn.
bangnab

2
Nếu bạn không thể triển khai trình xử lý tín hiệu trong ứng dụng của mình (vì bạn không thể sửa đổi chu kỳ đó), bạn có thể sử dụng kỹ thuật này để dẫn nhật ký qua bẫy tín hiệu - xem nội dung sau "Tuy nhiên ..."
goldilocks

Ok tôi sẽ thử và cho bạn biết nó đã đi như thế nào.
bangnab

Cuối cùng tôi cũng có một ứng dụng CLI được viết bằng C cho việc này (xin lỗi, nó mất nhiều thời gian hơn dự định ban đầu): cognitivingissonance.ca/cogware/pipelog
goldilocks

6

TL; DR

Mở tệp nhật ký của bạn ở chế độ chắp thêm :

cmd >> log

Sau đó, bạn có thể cắt nó một cách an toàn với:

: > log

Chi tiết

Với trình bao giống như Bourne, có 3 cách chính để mở tệp. Trong chỉ ghi ( >), đọc + write ( <>) hoặc chắp thêm (và chỉ ghi,>> ).

Trong hai phần đầu, kernel ghi nhớ vị trí hiện tại của bạn (ý bạn là, mô tả tệp đang mở , được chia sẻ bởi tất cả các mô tả tệp đã sao chép hoặc kế thừa nó bằng cách chuyển từ vị trí bạn đã mở tệp trên) tập tin.

Khi bạn làm:

cmd > log

logđược mở trong chế độ chỉ ghi bởi trình bao cho thiết bị xuất chuẩn của cmd.

cmd(quá trình ban đầu của nó được sinh ra bởi shell và tất cả các con có thể) khi viết vào thiết bị xuất chuẩn của chúng, hãy viết ở vị trí con trỏ hiện tại được giữ bởi mô tả tệp mở mà chúng chia sẻ trên tệp đó.

Chẳng hạn, nếu cmdban đầu ghi zzz, vị trí sẽ ở byte bù 4 vào tệp và lần sau cmdhoặc con của nó ghi vào tệp, đó là nơi dữ liệu sẽ được ghi bất kể tệp đã tăng hay giảm trong khoảng thời gian .

Nếu tập tin bị thu hẹp, ví dụ nếu nó đã bị cắt bằng

: > log

cmdviết xx, những cái đó xxsẽ được viết ở offset 4, và 3 ký tự đầu tiên sẽ được thay thế bằng các ký tự NUL.

$ exec 3> log # open file on fd 3.
$ printf zzz >&3
$ od -c log
0000000   z   z   z
0000003
$ printf aaaa >> log # other open file description -> different cursor
$ od -c log
0000000   z   z   z   a   a   a   a
0000007
$ printf bb >&3 # still write at the original position
$ od -c log
0000000   z   z   z   b   b   a   a
0000007
$ : > log
$ wc log
0 0 0 log
$ printf x >&3
$ od -c log
0000000  \0  \0  \0  \0  \0   x
0000006

Điều đó có nghĩa là bạn không thể cắt một tệp đã được mở ở chế độ chỉ ghi (và tương tự đối với đọc + ghi ) như khi bạn thực hiện, các quy trình có mô tả tệp mở trên tệp, sẽ để lại các ký tự NUL ở đầu tệp (những tệp, ngoại trừ trên OS / X, thường không chiếm dung lượng trên đĩa, chúng trở thành các tệp thưa thớt).

Thay vào đó (và bạn sẽ nhận thấy hầu hết các ứng dụng làm điều đó khi chúng ghi vào tệp nhật ký), bạn nên mở tệp ở chế độ chắp thêm :

cmd >> log

hoặc là

: > log && cmd >> log

nếu bạn muốn bắt đầu trên một tập tin trống.

Trong chế độ chắp thêm, tất cả các ghi được thực hiện ở cuối tệp, bất kể nơi ghi cuối cùng là:

$ exec 4>> log
$ printf aa >&4
$ printf x >> log
$ printf bb >&4
$ od -c log
0000000   a   a   x   b   b
0000005
$ : > log
$ printf cc >&4
$ od -c log
0000000   c   c
0000002

Điều đó cũng an toàn hơn nếu hai quá trình mở (theo cách đó) tệp bị nhầm (ví dụ: nếu bạn đã bắt đầu hai phiên bản của cùng một trình nền), đầu ra của chúng sẽ không ghi đè lên nhau.

Trên các phiên bản gần đây của Linux, bạn có thể kiểm tra vị trí hiện tại và liệu mô tả tệp đã được mở trong chế độ chắp thêm hay chưa bằng cách xem /proc/<pid>/fdinfo/<fd>:

$ cat /proc/self/fdinfo/4
pos:        2
flags:      0102001

Hoặc với:

$ lsof +f G -p "$$" -ad 4
COMMAND  PID USER   FD   TYPE  FILE-FLAG DEVICE SIZE/OFF     NODE NAME
zsh     4870 root    4w   REG 0x8401;0x0 252,18        2 59431479 /home/chazelas/log
~# lsof +f g -p "$$" -ad 4
COMMAND  PID USER   FD   TYPE FILE-FLAG DEVICE SIZE/OFF     NODE NAME
zsh     4870 root    4w   REG   W,AP,LG 252,18        2 59431479 /home/chazelas/log

Các cờ đó tương ứng với các cờ O ..._ được truyền cho lệnh opengọi hệ thống.

$ gcc -E - <<< $'#include <fcntl.h>\nO_APPEND O_WRONLY' | tail -n1
02000 01

( O_APPENDlà 0x400 hoặc bát phân 02000)

Vì vậy, các vỏ của >>mở file với O_WRONLY|O_APPEND(và 0.100.000 đây là O_LARGEFILE mà không liên quan đến câu hỏi này) trong khi >O_WRONLYduy nhất (và <>O_RDWRduy nhất).

Nếu bạn làm một:

sudo lsof -nP +f g | grep ,AP

để tìm kiếm các tệp đang mở O_APPEND, bạn sẽ tìm thấy hầu hết các tệp nhật ký hiện đang mở để ghi trên hệ thống của mình.


Tại sao bạn sử dụng :(dấu hai chấm) trong : > ?
mvorisek

1
@Mvorisek, đó là để chuyển hướng đầu ra của lệnh không tạo ra đầu ra : :. Không có lệnh, hành vi khác nhau giữa các shell.
Stéphane Chazelas

1

Nếu tôi hiểu chính xác, teecó vẻ như là một cách tiếp cận hợp lý:

$ ./myapp-that-echoes-the-date-every-second | tee log > /dev/null &
[1] 20519
$ head log
Thu Apr  3 11:29:34 EDT 2014
Thu Apr  3 11:29:35 EDT 2014
Thu Apr  3 11:29:36 EDT 2014
$ > log
$ head log
Thu Apr  3 11:29:40 EDT 2014
Thu Apr  3 11:29:41 EDT 2014
Thu Apr  3 11:29:42 EDT 2014

1

Là giải pháp nhanh, bạn có thể sử dụng nhật ký với vòng quay (ví dụ xoay vòng hàng ngày):

date=`date +%Y%m%d`
LOGFILE=/home/log$date.log

và chuyển hướng đăng nhập vào nó ./my_app >> log$date.log


Tôi muốn có thể xoay theo yêu cầu. Đây thực sự là một bản ghi được tạo ra trong một thử nghiệm tự động và tôi muốn xóa nó trước khi chạy thử nghiệm.
bangnab

0

Đây là một vấn đề đã được giải quyết từ lâu với syslog (trong tất cả các biến thể của nó) nhưng có hai công cụ sẽ giải quyết vấn đề cụ thể của bạn với nỗ lực tối thiểu.

Giải pháp đầu tiên, di động hơn nhưng kém linh hoạt hơn là logger (phải có cho bất kỳ hộp công cụ quản trị viên nào). Nó là một tiện ích đơn giản sao chép đầu vào tiêu chuẩn vào syslog. (vượt qua khóa và làm cho tập tin xoay vòng vấn đề của logrotate và syslog)

Giải pháp thứ hai thanh lịch hơn nhưng ít di động hơn là syslog-ng, ngoài việc chấp nhận thông điệp tường trình từ các ổ cắm syslog tiêu chuẩn có thể thực thi các chương trình mà đầu ra được lọc thông qua logger. (Tôi chưa sử dụng tính năng này, nhưng nó có vẻ hoàn hảo cho những gì bạn muốn làm.)

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.