Paksa pembilasan output ke file saat skrip bash masih berjalan

90

Saya memiliki skrip kecil, yang dipanggil setiap hari oleh crontab menggunakan perintah berikut:

/homedir/MyScript &> some_log.log

Masalah dengan metode ini adalah some_log.log hanya dibuat setelah MyScript selesai. Saya ingin memindahkan output dari program ke dalam file saat sedang berjalan sehingga saya dapat melakukan hal-hal seperti

tail -f some_log.log

dan melacak kemajuannya, dll.

olamundo.dll
sumber
Kami perlu memiliki deskripsi -atau jika mungkin kode- tentang apa yang sebenarnya dilakukan skrip kecil Anda ...
ChristopheD
7
Untuk unbuffer skrip python Anda bisa menggunakan "python -u". Untuk melepaskan skrip perl, lihat balasan Greg Hewgill di bawah ini. Dan seterusnya ...
Eloici
Jika Anda dapat mengedit skrip, Anda biasanya dapat membersihkan buffer keluaran secara eksplisit di dalam skrip, misalnya di python dengan sys.stdout.flush().
drevicko

Jawaban:

28

bash sendiri tidak akan pernah benar-benar menulis keluaran apa pun ke file log Anda. Sebaliknya, perintah yang dipanggil sebagai bagian dari skrip masing-masing akan menulis output dan menghapusnya kapan pun mereka mau. Jadi pertanyaan Anda sebenarnya adalah bagaimana memaksa perintah dalam skrip bash untuk mengosongkan, dan itu tergantung pada apa itu.

Chris Dodd
sumber
25
Saya benar-benar tidak mengerti jawaban ini.
Alfonso Santiago
3
Untuk gagasan yang lebih baik mengapa keluaran standar berperilaku seperti ini, lihat stackoverflow.com/a/13933741/282728 . Versi singkat — secara default, jika diarahkan ke sebuah file, stdout sepenuhnya di-buffer; itu ditulis ke file hanya setelah flush. Stderr bukan — ini ditulis setelah setiap '\ n'. Salah satu solusinya adalah dengan menggunakan perintah 'script' yang direkomendasikan oleh user3258569 di bawah ini, agar stdout dihapus setelah setiap akhir baris.
Alex
2
Menyatakan yang sudah jelas, dan sepuluh tahun kemudian, tapi ini adalah komentar, bukan jawaban, dan itu seharusnya bukan jawaban yang diterima.
RealHandy
87

Saya menemukan solusi untuk ini di sini . Menggunakan contoh OP pada dasarnya Anda menjalankan

stdbuf -oL /homedir/MyScript &> some_log.log

dan kemudian buffer dihilangkan setelah setiap baris output. Saya sering menggabungkan ini dengan nohupmenjalankan pekerjaan lama di mesin jarak jauh.

stdbuf -oL nohup /homedir/MyScript &> some_log.log

Dengan cara ini proses Anda tidak dibatalkan saat Anda keluar.

Martin Wiebusch
sumber
1
Bisakah Anda menambahkan tautan ke beberapa dokumentasi untuk stdbuf? Berdasarkan komentar ini sepertinya tidak tersedia di beberapa distro. Bisakah Anda menjelaskan?
Gugatan Dana Monica
1
stdbuf -o menyesuaikan buffering stdout. Opsi lainnya adalah -i dan -e untuk stdin dan stderr. L mengatur buffering baris. Seseorang juga bisa menentukan ukuran buffer, atau 0 tanpa buffering.
Seppo Enarvi
5
tautan itu tidak lagi tersedia.
john-jones
2
@NicHartley: stdbufadalah bagian dari GNU coreutils, dokumentasinya dapat ditemukan di gnu.org
Thor
Jika itu membantu siapa pun, gunakan export -f my_function dan kemudian stdbuf -oL bash -c "my_function -args"jika Anda perlu menjalankan fungsi alih-alih skrip
Anonim
29
script -c <PROGRAM> -f OUTPUT.txt

Kuncinya adalah -f. Kutipan dari naskah pria:

-f, --flush
     Flush output after each write.  This is nice for telecooperation: one person
     does 'mkfifo foo; script -f foo', and another can supervise real-time what is
     being done using 'cat foo'.

Jalankan di latar belakang:

nohup script -c <PROGRAM> -f OUTPUT.txt
pengguna3258569
sumber
Wow! Solusi yang berhasil busybox! (cangkang saya membeku setelah itu, tapi apa pun)
Victor Sergienko
9

Anda dapat menggunakan teeuntuk menulis ke file tanpa perlu membersihkan.

/homedir/MyScript 2>&1 | tee some_log.log > /dev/null
crenate
sumber
2
Ini masih menyangga output, setidaknya di lingkungan Ubuntu 18.04 saya. Isinya akhirnya bisa ditulis ke file dengan cara apa pun, tetapi saya pikir OP meminta metode di mana mereka dapat memantau kemajuan lebih akurat sebelum file selesai ditulis, dan metode ini tidak memungkinkan untuk itu lebih dari pengalihan keluaran tidak.
mltsy
3

Ini bukan fungsi dari bash, karena yang dilakukan semua shell adalah membuka file yang dipermasalahkan dan kemudian meneruskan deskriptor file sebagai output standar skrip. Yang perlu Anda lakukan adalah memastikan keluaran dihapus dari skrip Anda lebih sering daripada saat ini.

Di Perl misalnya, ini dapat dilakukan dengan menyetel:

$| = 1;

Lihat perlvar untuk informasi lebih lanjut tentang ini.

Greg Hewgill
sumber
2

Buffering output bergantung pada bagaimana program Anda /homedir/MyScriptdiimplementasikan. Jika Anda menemukan bahwa output mendapatkan buffer, Anda harus memaksanya dalam implementasi Anda. Misalnya, gunakan sys.stdout.flush () jika itu program python atau gunakan fflush (stdout) jika itu program C.

Midas
sumber
2

Apakah ini membantu?

tail -f access.log | stdbuf -oL cut -d ' ' -f1 | uniq 

Ini akan segera menampilkan entri unik dari access.log menggunakan utilitas stdbuf .

Ondra Žižka
sumber
Satu-satunya masalah adalah stdbuf tampaknya merupakan utilitas lama yang tidak tersedia di distro baru.
Ondra Žižka
..nor di busybox saya :(
Campa
Sebenarnya saya telah stdbuftersedia di Ubuntu saat ini, tidak yakin dari mana saya mendapatkannya.
Ondra Žižka
Saya memiliki stdbuf di Centos 7.5
Maks
1
Di Ubuntu 18.04, stdbufadalah bagian dari coreutilus(ditemukan dengan apt-file search /usr/bin/stdbuf).
Rmano
1

Masalahnya baru saja terlihat di sini adalah Anda harus menunggu program yang dijalankan dari skrip Anda menyelesaikan tugasnya.
Jika di skrip Anda, Anda menjalankan program di latar belakang Anda dapat mencoba sesuatu yang lebih.

Secara umum panggilan ke sync sebelum Anda keluar memungkinkan untuk membersihkan buffer sistem file dan dapat sedikit membantu.

Jika dalam skrip Anda memulai beberapa program di background ( &), Anda dapat menunggu hingga program selesai sebelum Anda keluar dari skrip. Untuk mengetahui bagaimana fungsinya bisa anda simak dibawah ini

#!/bin/bash
#... some stuffs ...
program_1 &          # here you start a program 1 in background
PID_PROGRAM_1=${!}   # here you remember its PID
#... some other stuffs ... 
program_2 &          # here you start a program 2 in background
wait ${!}            # You wait it finish not really useful here
#... some other stuffs ... 
daemon_1 &           # We will not wait it will finish
program_3 &          # here you start a program 1 in background
PID_PROGRAM_3=${!}   # here you remember its PID
#... last other stuffs ... 
sync
wait $PID_PROGRAM_1
wait $PID_PROGRAM_3  # program 2 is just ended
# ...

Karena waitbekerja dengan pekerjaan serta dengan PIDangka, solusi malas harus diletakkan di akhir skrip

for job in `jobs -p`
do
   wait $job 
done

Situasi yang lebih sulit adalah jika Anda menjalankan sesuatu yang menjalankan sesuatu yang lain di latar belakang karena Anda harus mencari dan menunggu (jika memang demikian) akhir dari semua proses anak : misalnya jika Anda menjalankan daemon mungkin tidak demikian untuk menunggu selesai :-).

catatan:

  • tunggu $ {!} berarti "tunggu sampai proses latar belakang terakhir selesai" di mana $!PID dari proses latar belakang terakhir. Jadi untuk meletakkan wait ${!}setelahnya program_2 &sama dengan mengeksekusi secara langsung program_2tanpa mengirimkannya di latar belakang dengan&

  • Dari bantuan wait:

    Syntax    
        wait [n ...]
    Key  
        n A process ID or a job specification
    
Hastur
sumber
1

Terima kasih @user3258569, skrip mungkin satu-satunya hal yang berhasil busybox!

Namun, cangkangnya membeku untukku setelah itu. Mencari penyebabnya, saya menemukan peringatan merah besar ini "jangan gunakan di shell non-interaktif" di halaman manual skrip :

scriptterutama dirancang untuk sesi terminal interaktif. Ketika stdin bukan terminal (misalnya echo foo | script:), maka sesi dapat hang, karena shell interaktif dalam sesi skrip kehilangan EOF dan scripttidak memiliki petunjuk kapan harus menutup sesi. Lihat bagian CATATAN untuk informasi lebih lanjut.

Benar. script -c "make_hay" -f /dev/null | grep "needle"membekukan cangkangnya untukku.

Berlawanan dengan peringatan itu, saya pikir echo "make_hay" | scriptAKAN lulus EOF, jadi saya mencoba

echo "make_hay; exit" | script -f /dev/null | grep 'needle'

dan berhasil!

Perhatikan peringatan di halaman manual. Ini mungkin tidak berhasil untuk Anda.

Victor Sergienko
sumber
0

alternatif untuk stdbuf adalah awk '{print} END {fflush()}' saya berharap ada bash builtin untuk melakukan ini. Biasanya itu tidak perlu, tetapi dengan versi yang lebih lama mungkin ada bug sinkronisasi bash pada deskriptor file.

Brian Chrisman
sumber
-2

Saya tidak tahu apakah itu akan berhasil, tetapi bagaimana dengan menelepon sync?

forkandwait
sumber
1
syncadalah operasi sistem file tingkat rendah dan tidak terkait dengan keluaran yang di-buffer di tingkat aplikasi.
Greg Hewgill
2
syncmenulis buffer sistem file kotor ke penyimpanan fisik, jika perlu. Ini internal OS; aplikasi yang berjalan di atas OS selalu melihat tampilan yang koheren dari sistem file apakah blok disk telah ditulis ke penyimpanan fisik atau tidak. Untuk pertanyaan awal, aplikasi (skrip) mungkin melakukan buffering output dalam buffer internal ke aplikasi, dan OS bahkan tidak akan tahu (belum) bahwa output sebenarnya ditujukan untuk ditulis ke stdout. Jadi operasi jenis "sinkronisasi" hipotetis tidak akan bisa "menjangkau" skrip dan menarik datanya.
Greg Hewgill
-2

Saya mengalami masalah ini dengan proses latar belakang di Mac OS X menggunakan StartupItems. Inilah cara saya mengatasinya:

Jika saya membuatnya, sudo ps auxsaya dapat melihat bahwa mytooldiluncurkan.

Saya menemukan bahwa (karena buffering) ketika Mac OS X dimatikan mytooltidak pernah mentransfer output ke sedperintah. Namun, jika saya mengeksekusi sudo killall mytool, maka mytooltransfer hasilnya ke sedperintah. Oleh karena itu, saya menambahkan stopkasus ke StartupItemsyang dieksekusi saat Mac OS X dimatikan:

start)
    if [ -x /sw/sbin/mytool ]; then
      # run the daemon
      ConsoleMessage "Starting mytool"
      (mytool | sed .... >> myfile.txt) & 
    fi
    ;;
stop)
    ConsoleMessage "Killing mytool"
    killall mytool
    ;;
Warga kehormatan
sumber
Ini bukanlah jawaban yang baik bagi Freeman karena sangat spesifik untuk lingkungan Anda. OP ingin memantau keluaran bukan membunuhnya.
Gray
-3

suka atau tidak, begitulah cara kerja pengalihan.

Dalam kasus Anda, output (artinya skrip Anda telah selesai) dari skrip Anda dialihkan ke file itu.

Apa yang ingin Anda lakukan adalah menambahkan pengalihan tersebut di skrip Anda.


sumber