การล้างไฟล์โดยไม่รบกวนการเขียนไปป์


12

ฉันมีโปรแกรมที่ส่งออกฉันเปลี่ยนเส้นทางไปยังล็อกไฟล์:

./my_app > log

ฉันต้องการล้าง (เช่นว่างเปล่า) บันทึกเป็นครั้งคราว (ตามต้องการ) และลองทำสิ่งต่าง ๆ เช่น

cat "" > log

อย่างไรก็ตามดูเหมือนว่าไปป์ดั้งเดิมจะถูกขัดจังหวะและโปรแกรมจะไม่เปลี่ยนเส้นทางเอาต์พุตไปยังล็อกไฟล์อีกต่อไป

มีวิธีการทำเช่นนั้น?

ปรับปรุง

โปรดทราบว่าฉันไม่สามารถแก้ไขแอปพลิเคชันที่สร้างเอาต์พุต มันคายออกมาเป็น stdout และฉันต้องการบันทึกไว้ในบันทึกเพื่อที่ฉันจะได้ตรวจสอบเมื่อฉันต้องการมันและเคลียร์มันเมื่อฉันต้องการ อย่างไรก็ตามฉันไม่จำเป็นต้องรีสตาร์ทแอปพลิเคชัน


นั่นเป็นเหตุผลที่คุณมักจะใช้ภูตการบันทึกเพื่อบันทึกสิ่งต่าง ๆ ...
Kiwy

@ กีวีคุณสามารถอธิบายเกี่ยวกับวิธีการที่จะแก้ปัญหาได้หรือไม่
bangnab

ปกติแล้วคุณจะใช้ดีมอนภูตหรือปล่อยให้แอปของคุณจัดการบันทึกเพราะการเขียนสิ่งที่จะส่งออกและเปลี่ยนเส้นทางมันไม่น่าเชื่อถือ คุณควรดูsyslogdหรือlogrotate
Kiwy

2
สิ่งต่าง ๆ ทำงานถ้าคุณทำ./my_app >> log(เพื่อบังคับให้ผนวก) และcp /dev/null logตัดทอนมัน?
Mark Plotnick

1
คุณได้รับข้อความแสดงข้อผิดพลาดอะไร คุณเห็นพฤติกรรมแบบใด "ไม่เปลี่ยนเส้นทางเอาต์พุตไปยังล็อกไฟล์อีกต่อไป" ไม่เฉพาะเจาะจงมาก ยังcat "" > logไม่ได้เป็นที่ถูกต้องคำสั่งเนื่องจากมีไม่มีไฟล์ที่เรียกว่าcat ""
Mikel

คำตอบ:


13

รูปแบบอื่นของปัญหานี้เกิดขึ้นกับแอปพลิเคชั่นที่ใช้เวลานานซึ่งมีการหมุนบันทึกเป็นระยะ แม้ว่าคุณจะย้ายบันทึกดั้งเดิม (เช่นmv log.txt log.1) และแทนที่ทันทีด้วยไฟล์ชื่อเดียวกันก่อนที่การบันทึกจริงจะเกิดขึ้นหากกระบวนการเปิดไฟล์ค้างไว้ก็จะจบลงด้วยการเขียนlog.1(เนื่องจากอาจยังเป็น ไอโหนดเปิด) หรือเพื่ออะไร

วิธีทั่วไปในการจัดการกับสิ่งนี้ (ตัวบันทึกระบบทำงานได้ด้วยวิธีนี้) คือการใช้ตัวจัดการสัญญาณในกระบวนการซึ่งจะปิดและเปิดบันทึกอีกครั้ง จากนั้นเมื่อใดก็ตามที่คุณต้องการย้ายหรือล้าง (โดยการลบ) บันทึกให้ส่งสัญญาณนั้นไปยังกระบวนการทันทีหลังจากนั้น

นี่เป็นตัวอย่างง่ายๆสำหรับการทุบตี - ยกโทษให้กับทักษะกระสุน cruddy ของฉัน (แต่ถ้าคุณจะแก้ไขสิ่งนี้เพื่อการปฏิบัติที่ดีที่สุด ฯลฯ โปรดตรวจสอบให้แน่ใจว่าคุณเข้าใจการทำงานก่อนและทดสอบการแก้ไขก่อนที่จะแก้ไข):

#!/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          

เริ่มต้นโดยการฟอร์กเป็นพื้นหลัง:

> ./test.sh &
12356

แจ้งให้ทราบว่ารายงานของ PID log.txtไปยังสถานีและจากนั้นก็เริ่มเข้าสู่ระบบเพื่อ คุณมีเวลา 2 นาทีในการเล่น รอสักครู่แล้วลอง:

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

แค่ธรรมดาก็kill -2 12356อาจใช้ได้สำหรับคุณที่นี่เช่นกัน สัญญาณที่ 2 คือ SIGINT (มันเป็นสิ่งที่ Ctrl-C ทำดังนั้นคุณสามารถลองทำสิ่งนี้ในเบื้องหน้าและย้ายหรือลบ logfile จากเทอร์มินัลอื่น) ซึ่งtrapควรดักจับ เพื่อตรวจสอบ;

> 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

ตอนนี้เรามาดูกันว่ามันยังเขียนlog.txtถึงแม้ว่าเราจะย้ายมัน:

> 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

แจ้งให้ทราบว่ามันไปทางขวาที่มันค้างไว้ หากคุณไม่ต้องการที่จะเก็บบันทึกเพียงแค่ล้างบันทึกโดยการลบมัน

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

ตรวจสอบ:

> 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

ยังคงเกิดขึ้น.

คุณไม่สามารถทำสิ่งนี้ได้ในเชลล์สคริปต์สำหรับกระบวนการย่อยที่ดำเนินการ แต่น่าเสียดายเพราะหากอยู่ในเบื้องหน้าตัวจัดการสัญญาณของ bash trapจะถูกระงับและถ้าคุณแยกมันออกเป็นพื้นหลังคุณจะไม่สามารถมอบหมายมันอีกครั้ง เอาท์พุต นั่นคือสิ่งที่คุณต้องนำไปใช้ในแอปพลิเคชันของคุณ

อย่างไรก็ตาม ...

หากคุณไม่สามารถแก้ไขแอปพลิเคชัน (เช่นเนื่องจากคุณไม่ได้เขียน) ฉันมียูทิลิตี้ CLI ที่คุณสามารถใช้เป็นตัวกลาง คุณสามารถนำเวอร์ชันนี้มาใช้ในสคริปต์ซึ่งทำหน้าที่เป็นไพพ์ไปยังบันทึก:

#!/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  

เรียกสิ่งนี้pipetrap.shว่า ตอนนี้เราต้องการโปรแกรมแยกต่างหากเพื่อทดสอบโดยจำลองแอปพลิเคชันที่คุณต้องการบันทึก:

#!/bin/bash

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

นั่นจะเป็นtest.sh:

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

นี่เป็นกระบวนการที่แยกกันสองกระบวนการพร้อม PID แยกกัน หากต้องการล้างtest.shเอาต์พุตของซึ่งกำลังถูกส่งผ่านpipetrap.sh:

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

ตรวจสอบ:

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

15858, test.shยังคงทำงานอยู่และกำลังส่งออกบันทึกอยู่ ในกรณีนี้ไม่จำเป็นต้องแก้ไขแอปพลิเคชัน


ขอบคุณสำหรับคำอธิบายที่ดี อย่างไรก็ตามในกรณีของฉันฉันไม่สามารถแก้ไขแอปพลิเคชันเพื่อใช้โซลูชันของคุณได้
bangnab

2
หากคุณไม่สามารถใช้ตัวจัดการสัญญาณในแอปพลิเคชันของคุณ (เพราะคุณไม่สามารถปรับเปลี่ยนช่วงเวลาได้) คุณสามารถใช้เทคนิคนี้เพื่อ
ไพพ์

ตกลงฉันจะลองและแจ้งให้คุณทราบว่ามันไปอย่างไร
bangnab

ในที่สุดฉันก็มีแอป CLI ที่เขียนใน C สำหรับเรื่องนี้ (ขออภัยที่ใช้เวลานานกว่าที่ตั้งใจไว้เล็กน้อย): cognitivedissonance.ca/cogware/pipelog
goldilocks

6

TL; DR

เปิดไฟล์บันทึกของคุณในโหมดผนวก :

cmd >> log

จากนั้นคุณสามารถตัดทอนได้อย่างปลอดภัยด้วย:

: > log

รายละเอียด

ด้วยเชลล์คล้ายบอร์นมี 3 วิธีหลักที่ไฟล์สามารถเปิดสำหรับการเขียนได้ ในโหมดเขียนอย่างเดียว ( >) อ่าน + เขียน ( <>) หรือผนวก (และเขียนอย่างเดียว>>) โหมด

ในสองคนแรกเคอร์เนลจะจดจำตำแหน่งปัจจุบันของคุณ (โดยคุณฉันหมายถึงคำอธิบายไฟล์แบบเปิดที่ใช้ร่วมกันโดยตัวอธิบายไฟล์ทั้งหมดที่มีการทำซ้ำหรือสืบทอดโดยการฟอร์กจากไฟล์ที่คุณเปิดไฟล์) ไฟล์.

เมื่อคุณทำ:

cmd > log

logมีการเปิดในการเขียนเท่านั้นโหมดโดยเปลือกสำหรับ stdout cmdของ

cmd(กระบวนการเริ่มต้นเกิดขึ้นโดยเชลล์และลูก ๆ ที่เป็นไปได้ทั้งหมด) เมื่อเขียนไปยัง stdout ของพวกเขาเขียนที่ตำแหน่งเคอร์เซอร์ปัจจุบันที่ถือโดยคำอธิบายไฟล์เปิดที่พวกเขาแบ่งปันในไฟล์นั้น

ตัวอย่างเช่นหากcmdเริ่มต้นเขียนzzzตำแหน่งจะอยู่ที่ไบต์ออฟเซ็ต 4 ลงในไฟล์และในครั้งถัดไปcmdหรือลูก ๆ ของมันเขียนไปยังไฟล์นั่นคือที่ที่ข้อมูลจะถูกเขียนโดยไม่คำนึงว่าไฟล์นั้นเติบโตหรือหดตัวในช่วงเวลาใด .

หากไฟล์มีการหดตัวอย่างเช่นหากไฟล์ถูกตัดทอนด้วย

: > log

และcmdเขียนข้อมูลxxเหล่านั้นxxจะถูกเขียนที่ออฟเซ็ต4และอักขระ 3 ตัวแรกจะถูกแทนที่ด้วยอักขระ 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

นั่นหมายความว่าคุณไม่สามารถตัดไฟล์ที่เปิดในโหมดเขียนอย่างเดียว (และเป็นแบบเดียวกันสำหรับอ่าน + เขียน ) ราวกับว่าคุณทำกระบวนการที่เปิดไฟล์ descriptors ไว้บนไฟล์จะทำให้อักขระ NUL อยู่ที่จุดเริ่มต้นของ ไฟล์ (ยกเว้นใน OS / X มักจะไม่ใช้พื้นที่บนดิสก์ แต่จะเป็นไฟล์แบบกระจาย)

(และคุณจะสังเกตเห็นว่าแอปพลิเคชันส่วนใหญ่ทำเช่นนั้นเมื่อพวกเขาเขียนไปยังล็อกไฟล์) คุณควรเปิดไฟล์ในโหมดต่อท้าย :

cmd >> log

หรือ

: > log && cmd >> log

หากคุณต้องการเริ่มไฟล์เปล่า

ในโหมดต่อท้ายการเขียนทั้งหมดจะทำที่ส่วนท้ายของไฟล์โดยไม่คำนึงถึงตำแหน่งที่การเขียนล่าสุด:

$ 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

นั่นเป็นวิธีที่ปลอดภัยกว่าเช่นกันหากกระบวนการสองกระบวนการเปิด (โดยวิธีนี้) ไฟล์โดยไม่ได้ตั้งใจ (เช่นถ้าคุณเริ่มต้นอินสแตนซ์ที่สองของ daemon เดียวกัน) เอาต์พุตของพวกเขาจะไม่เขียนทับกัน

บน Linux เวอร์ชันล่าสุดคุณสามารถตรวจสอบตำแหน่งปัจจุบันและระบุว่า file descriptor เปิดในโหมดต่อท้ายหรือไม่โดยดูที่/proc/<pid>/fdinfo/<fd>:

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

หรือด้วย:

$ 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

แฟล็กเหล่านั้นสอดคล้องกับแฟล็กO ..._ ที่ส่งผ่านไปยังการopenเรียกระบบ

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

( O_APPENDคือ 0x400 หรือ octal 02000)

ดังนั้นเชลล์ของ>>เปิดไฟล์ด้วยO_WRONLY|O_APPEND(และ 0100000 นี่คือ O_LARGEFILE ซึ่งไม่เกี่ยวข้องกับคำถามนี้) ในขณะที่>เป็นO_WRONLYเพียง (และ<>เป็นO_RDWRเท่านั้น)

ถ้าคุณทำ:

sudo lsof -nP +f g | grep ,AP

เพื่อค้นหาไฟล์ที่เปิดด้วยO_APPENDคุณจะพบไฟล์บันทึกส่วนใหญ่ที่เปิดอยู่สำหรับการเขียนบนระบบของคุณ


ทำไมคุณใช้:(ลำไส้ใหญ่) ใน: > ?
mvorisek

1
@Mvorisek :ที่จะเปลี่ยนเส้นทางการส่งออกของคำสั่งที่ผลิตออกไม่มี: หากไม่มีคำสั่งพฤติกรรมจะแตกต่างกันระหว่างเชลล์
Stéphane Chazelas

1

หากฉันเข้าใจถูกต้องteeดูเหมือนว่าเป็นวิธีที่สมเหตุสมผล:

$ ./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

วิธีแก้ปัญหาอย่างรวดเร็วคุณสามารถใช้บันทึกที่มีการหมุน (เช่นการหมุนรายวัน):

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

และเปลี่ยนเส้นทางการเข้าสู่ระบบ ./my_app >> log$date.log


ฉันต้องการหมุนตามต้องการ นี่เป็นบันทึกที่เกิดขึ้นในระหว่างการทดสอบอัตโนมัติและฉันต้องการจะล้างมันก่อนที่จะทำการทดสอบ
บางจาก

0

นี่เป็นปัญหาที่แก้ไขมานานแล้วด้วย syslog (ในทุกรุ่น) แต่มีเครื่องมือสองอย่างที่จะแก้ปัญหาเฉพาะของคุณได้โดยใช้ความพยายามน้อยที่สุด

โซลูชั่นแรกแบบพกพา แต่มีประโยชน์น้อยกว่าคือตัวบันทึก (ต้องมีสำหรับกล่องเครื่องมือผู้ดูแลระบบ) มันเป็นโปรแกรมที่ง่ายที่คัดลอกอินพุตมาตรฐานไปยัง syslog (ผ่านเจ้าชู้และทำให้การหมุนไฟล์เป็นปัญหาของ logrotate และ syslog)

โซลูชันที่สองที่หรูหรากว่า แต่พกพาได้น้อยกว่าคือ syslog-ng ซึ่งนอกเหนือจากการยอมรับข้อความบันทึกจากซ็อกเก็ต syslog มาตรฐานสามารถเรียกใช้งานโปรแกรมที่เอาต์พุตถูกกรองผ่านตัวบันทึก (ฉันยังไม่ได้ใช้คุณสมบัตินี้ แต่มันสมบูรณ์แบบสำหรับสิ่งที่คุณต้องการจะทำ)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.