Pipo yazmayı bozmadan bir dosyayı boşaltma


12

Ben çıktı bir günlük dosyasına yönlendirme bir program var:

./my_app > log

Günlüğü zaman zaman temizlemek (yani boş) (istek üzerine) ve çeşitli şeyleri denemek istiyorum

cat "" > log

Ancak, orijinal boru her zaman kesintiye uğramış gibi görünüyor ve program artık çıktısını günlük dosyasına yönlendirmiyor.

Bunu yapmanın bir yolu var mı?

Güncelleme

Çıktı üreten uygulamayı değiştiremiyorum. Sadece stdout'a tükürür ve bir günlüğe kaydetmek istiyorum, böylece ihtiyaç duyduğumda inceleyebilir ve istediğim zaman temizleyebilirim. Ancak uygulamayı yeniden başlatmam gerekmiyor.


işte bu yüzden bir şeyleri günlüğe kaydetmek için genellikle bir günlük kayıt programı kullanıyorsunuz ...
Kiwy

@Kiwy Sorunun nasıl çözüleceği hakkında ayrıntılı bilgi verebilir misiniz?
bangnab

genellikle bir günlük arka plan programı kullanırsınız veya uygulamanızın günlüğü işlemesine izin verirsiniz, çünkü çıktı almak ve yeniden yönlendirmek için bir şeyler yazmak güvenilir değildir. bir göz syslogdlogrotate
atmalısın

2
./my_app >> log(Eklemeye zorlamak) ve kısaltmak için işler işe yarıyor cp /dev/null logmu?
Mark Plotnick

1
Hangi hata mesajını alıyorsunuz? Hangi davranışı görüyorsun? "Artık çıktısını günlük dosyasına yönlendirmiyor" çok özel değil. Ayrıca, çağrılan dosya olmadığından cat "" > loggeçerli bir catkomut değildir "".
Mikel

Yanıtlar:


13

Bu sorunun başka bir biçimi, günlükleri periyodik olarak döndürülen uzun çalışan uygulamalarda oluşur. Özgün günlüğü taşıdığınızda (örn. mv log.txt log.1) Ve herhangi bir gerçek günlüğe kaydetme gerçekleşmeden hemen önce aynı addaki bir dosyayla değiştirseniz bile, işlem dosyayı açık tutuyorsa, log.1( açık inode) veya hiçbir şeye.

Bununla başa çıkmanın yaygın bir yolu (sistem kaydedicisinin kendisi bu şekilde çalışır) işlemde günlüklerini kapatacak ve yeniden açacak bir sinyal işleyici uygulamaktır. Ardından, günlüğü taşımak veya temizlemek istediğinizde (silerek), hemen sonra bu sinyali işleme gönderin.

İşte bash için basit bir gösteri - kıvrımlı kabuk becerilerimi affedin (ancak bunu en iyi uygulamalar vb. İçin düzenleyecekseniz, lütfen önce işlevselliği anladığınızdan ve düzenlemeden önce revizyonunuzu test ettiğinizden emin olun ):

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

Arka plana girerek başlayın:

> ./test.sh &
12356

PID'sini terminale rapor ettiğine ve ardından oturum açmaya başladığına dikkat edin log.txt. Artık oynamak için 2 dakikanız var. Birkaç saniye bekleyin ve deneyin:

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

Sadece düz kill -2 12356de burada sizin için çalışabilir. Sinyal 2 SIGINT (aynı zamanda Ctrl-C'nin yaptığı şeydir, bu yüzden bunu ön planda deneyebilir ve günlük dosyasını başka bir terminalden taşıyabilir veya kaldırabilirsiniz), bu da traptuzağa düşmelidir. Kontrol etmek;

> 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

Şimdi log.txthareket ettirmiş olsak da hala yazıp yazmadığını görelim :

> 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

Kaldığı yerden devam ettiğine dikkat edin. Kaydı saklamak istemiyorsanız kaydı silerek temizleyin.

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

Kontrol:

> 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

Halen devam ediyor.

Ne yazık ki, yürütülen bir alt işlem için bir kabuk komut dosyasında yapamazsınız, çünkü ön planda ise, bash'ın kendi sinyal işleyicileri trapaskıya alınır ve arka plana çatallarsanız, yeniden atayamazsınız. çıktı. Yani, bu başvurunuzda uygulamak zorunda olduğunuz bir şeydir.

Ancak...

Uygulamayı değiştiremezseniz (örneğin, yazmadığınız için), aracı olarak kullanabileceğiniz bir CLI yardımcı programım var. Bunun basit bir sürümünü günlüğe bir kanal görevi gören bir komut dosyasında da uygulayabilirsiniz:

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

Buna diyelim pipetrap.sh. Şimdi, test etmek istediğiniz uygulamayı taklit ederek test etmek için ayrı bir programa ihtiyacımız var:

#!/bin/bash

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

Bu olacak test.sh:

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

Bunlar ayrı PID'lere sahip iki ayrı işlemdir. test.shHuni oluşturan çıktısını temizlemek için pipetrap.sh:

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

Kontrol:

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

15858,, test.shhala çalışıyor ve çıktıları günlüğe kaydediliyor. Bu durumda, uygulamada herhangi bir değişiklik yapılması gerekmez.


Güzel açıklamalar için teşekkürler. Ancak benim durumumda, çözümünüzü uygulamak için uygulamayı değiştiremiyorum.
bangnab

2
Eğer değilse olamaz sonra şeyler görmek - (bunu dönemi değiştiremezsiniz çünkü) uygulamanızda bir sinyal işleyicisi uygulamak, bir sinyal kapanı vasıtasıyla boruya günlüğünü bu tekniği kullanabilirsiniz "Ancak ..."
Goldilocks'ı

Tamam, bir deneyeceğim ve nasıl gittiğini size bildireceğim.
bangnab

Sonunda bunun için C ile yazılmış bir CLI uygulaması var (üzgünüm başlangıçta amaçlanandan biraz daha uzun sürdü): cognitivedissonance.ca/cogware/pipelog
goldilocks

6

TL; DR

Günlük dosyanızı ekleme modunda açın:

cmd >> log

Ardından, güvenle aşağıdakileri kesebilirsiniz:

: > log

ayrıntılar

Bourne benzeri bir kabukla, bir dosyanın yazmaya açık olmasının 3 ana yolu vardır. In salt ( >), okuma + yazma ( <>) veya ekleme (ve salt, >>) modu.

İlk ikisinde, çekirdek, sizin açtığınız pozisyonu hatırlar (sizin, yani, açtığınız dosya tanımlayıcıları tarafından paylaşılan açık dosya açıklaması , dosyayı açtığınız yerden çatallayarak) dosya.

Yaptığınızda:

cmd > log

logstdout için kabuk tarafından salt yazma modunda açıktır cmd.

cmd(kabuk ve olası tüm çocuklar tarafından ortaya çıkan ilk işlem) stdout'larına yazarken , o dosyada paylaştıkları açık dosya açıklaması tarafından tutulan geçerli imleç konumuna yazın.

Örneğin, cmdbaşlangıçta yazarsa zzz, konum bayt ofset 4'te dosyaya yerleştirilir ve bir dahaki sefere cmdveya alt dosyaları dosyaya yazılır; bu, dosyanın aralıkta büyüdüğüne veya küçülmesine bakılmaksızın verilerin yazılacağı yerdir .

Dosya daraltılmışsa, örneğin bir dosya ile kesilmişse

: > log

ve cmdyazıyor xx, bu xxoffset de yazılır 4ve ilk 3 karakter boş karakterlerin yerini alacaktır.

$ 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

Bu, yalnızca yazma modunda açık olan (ve okuma + yazma için aynı olan ) bir dosyayı kesmiş gibi kesemeyeceğiniz anlamına gelir, dosya açıklayıcıları dosyada açık olan işlemler, dosyanın başında NUL karakterleri bırakacaktır dosyası (OS / X hariç olanlar genellikle diskte yer kaplamazlar, seyrek dosyalar olurlar).

Bunun yerine (ve çoğu uygulamanın günlük dosyalarına yazarken bunu yaptığını fark edersiniz), dosyayı ekleme modunda açmalısınız :

cmd >> log

veya

: > log && cmd >> log

boş bir dosyaya başlamak istiyorsanız.

Ekleme modunda, son yazma işleminin nerede olduğuna bakılmaksızın tüm yazma işlemleri dosyanın sonunda yapılır:

$ 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

Bu, iki işlemin dosyayı yanlışlıkla (örneğin aynı arka plan programının iki örneğini başlattıysanız) açtığı gibi daha güvenlidir, çıktıları birbirinin üzerine yazmaz.

Linux'un son sürümlerinde, şu konuma bakarak geçerli konumu ve dosya tanımlayıcısının ekleme modunda açık olup olmadığını kontrol edebilirsiniz /proc/<pid>/fdinfo/<fd>:

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

Veya:

$ 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

Bu bayraklar , sistem çağrısına iletilen O ..._ bayraklarına karşılık gelir open.

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

( O_APPEND0x400 veya sekizli 02000'dir)

Kabuk en Yani >>dosyayı açar O_WRONLY|O_APPENDiken (ve 0.100.000 burada bu soruya alakalı değildir O_LARGEFILE olduğu) >olduğu O_WRONLYsadece (ve <>olduğu O_RDWRiçin).

Şunu yaparsanız:

sudo lsof -nP +f g | grep ,AP

açık dosyaları aramak için O_APPEND, sisteminizde yazmak üzere açık olan çoğu günlük dosyasını bulacaksınız.


Neden :(iki nokta üst üste) kullanıyorsunuz : > ?
mvorisek

1
@Mvorisek, hiçbir çıktı üretir komutun çıktısını yönlendirmek için var: :. Komut olmadan, davranış kabuklar arasında değişir.
Stéphane Chazelas

1

Doğru anlıyorsam, teemakul bir yaklaşım gibi görünüyor:

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

Hızlı çözüm olarak rotasyonlu bir günlük kullanabilirsiniz (örneğin günlük rotasyon):

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

ve günlüğe kaydetmeyi yönlendir ./my_app >> log$date.log


Talep üzerine dönebilmeyi istiyorum. Bu, otomatik bir test sırasında üretilen bir günlüktür ve testi çalıştırmadan önce temizlemek istiyorum.
bangnab

0

Bu, syslog ile uzun süredir çözülmüş bir sorundur (tüm varyantlarında), ancak özel sorununuzu minimum çaba ile çözecek iki araç vardır.

İlk, daha taşınabilir ama daha az çok yönlü çözüm logger (herhangi bir yönetici araç kutusu için olması gerekir). Standart girdiyi syslog dosyasına kopyalayan basit bir yardımcı programdır. (paranın geçmesi ve dosya döndürmenin logrotate ve syslog sorunu haline getirilmesi)

İkinci, daha zarif ancak daha az taşınabilir çözüm, standart syslog soketlerinden gelen günlük mesajlarını kabul etmenin yanı sıra, çıktı kaydedici üzerinden filtrelenen programları da çalıştırabilen syslog-ng'dir. (Bu özelliği henüz kullanmadım, ancak yapmak istediğiniz şey için mükemmel görünüyor.)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.