Bagaimana saya bisa menggunakan bash's jika menguji dan menemukan perintah bersama?

25

Saya memiliki direktori dengan log kerusakan, dan saya ingin menggunakan pernyataan kondisional dalam skrip bash berdasarkan perintah find.

File log disimpan dalam format ini:

/var/log/crashes/app-2012-08-28.log
/var/log/crashes/otherapp-2012-08-28.log

Saya ingin pernyataan if hanya mengembalikan true jika ada crash log untuk aplikasi tertentu yang telah dimodifikasi dalam 5 menit terakhir. The findperintah yang saya akan gunakan adalah:

find /var/log/crashes -name app-\*\.log -mmin -5

Saya tidak yakin bagaimana memasukkannya ke dalam ifpernyataan dengan benar. Saya pikir ini mungkin berhasil:

if [ test `find /var/log/crashes -name app-\*\.log -mmin -5` ] then
 service myapp restart
fi

Ada beberapa area di mana saya tidak jelas:

  • Saya telah melihat bendera if tapi saya tidak yakin yang mana, jika ada, yang harus saya gunakan.
  • Apakah saya memerlukan testarahan atau haruskah saya hanya memproses terhadap hasil perintah find secara langsung, atau mungkin menggunakan find... | wc -luntuk mendapatkan jumlah baris sebagai gantinya?
  • Tidak 100% diperlukan untuk menjawab pertanyaan ini, tetapi testapakah untuk pengujian terhadap kode kembali yang memerintahkan pengembalian? Dan mereka agak tidak terlihat - di luar stdout/ stderr? Saya membaca manhalaman tersebut tetapi saya masih belum jelas kapan harus digunakan testdan bagaimana cara men-debug-nya.
cwd
sumber
Jawaban nyata untuk kasus umum adalah menggunakan find ... -exec. Juga lihat contoh perintah di bawah Mengapa mengulangi hasil praktik buruk?
Wildcard
@Wildcard - sayangnya itu tidak menyelesaikan kasus umum: itu tidak bekerja jika ada lebih dari satu pertandingan dan tindakan hanya perlu dijalankan sekali, dan itu tidak berfungsi jika Anda memerlukan tindakan untuk dijalankan ketika ada tidak ada yang cocok. Yang pertama dapat diselesaikan dengan menggunakan ... -exec command ';' -quit, tetapi saya tidak percaya ada solusi untuk yang terakhir selain mengurai hasilnya. Juga, dalam kedua kasus, masalah utama dengan parsing hasil find(yaitu ketidakmampuan untuk membedakan pembatas dari karakter dalam nama file) tidak berlaku, karena Anda tidak perlu menemukan pembatas dalam kasus ini.
Jules

Jawaban:

27

[dan testadalah sinonim (kecuali [membutuhkan ]), sehingga Anda tidak ingin menggunakan [ test:

[ -x /bin/cat ] && echo 'cat is executable'
test -x /bin/cat && echo 'cat is executable'

testmengembalikan status keluar nol jika kondisinya benar, jika tidak nol. Ini sebenarnya dapat diganti oleh program apa pun untuk memeriksa status keluarnya, di mana 0 menunjukkan keberhasilan dan bukan nol menunjukkan kegagalan:

# echoes "command succeeded" because echo rarely fails
if /bin/echo hi; then echo 'command succeeded'; else echo 'command failed'; fi

# echoes "command failed" because rmdir requires an argument
if /bin/rmdir; then echo 'command succeeded'; else echo 'command failed'; fi

Namun, semua contoh di atas hanya menguji status keluar program, dan mengabaikan output program.

Sebab find, Anda perlu menguji apakah ada output yang dihasilkan. -ntes untuk string yang tidak kosong:

if [[ -n $(find /var/log/crashes -name "app-*.log" -mmin -5) ]]
then
    service myapp restart
fi

Daftar lengkap argumen pengujian tersedia dengan memohon help testdi baris bashperintah.

Jika Anda menggunakan bash(dan tidak sh), Anda dapat menggunakan [[ condition ]], yang berperilaku lebih mudah ditebak ketika ada ruang atau kasus khusus lainnya dalam kondisi Anda. Kalau tidak, umumnya sama dengan menggunakan [ condition ]. Saya telah menggunakan [[ condition ]]dalam contoh ini, seperti yang saya lakukan sebisa mungkin.

Saya juga berubah `command`menjadi $(command), yang juga umumnya berperilaku sama, tetapi lebih baik dengan perintah bersarang.

mrb
sumber
echobisa gagal: coba echo 'oops' > /dev/full.
derobert
Jawaban ini berdetak di sekitar akar masalah tetapi dengan anggun menghindari menyebutkan apa itu.
bahamat
9

findakan keluar dengan sukses jika tidak ada kesalahan, jadi Anda tidak dapat mengandalkan status keluarnya untuk mengetahui apakah ia menemukan file. Tetapi, seperti yang Anda katakan, Anda dapat menghitung berapa banyak file yang ditemukan dan menguji nomor itu.

Itu akan menjadi seperti ini:

if [ $(find /var/log/crashes -name 'app-*.log' -mmin -5 | wc -l) -gt 0 ]; then
    ...
fi

test(alias [) tidak memeriksa kode kesalahan dari perintah, ini memiliki sintaks khusus untuk melakukan tes, dan kemudian keluar dengan kode kesalahan 0 jika tes berhasil, atau 1 jika tidak. Ini adalah ifsalah satu yang memeriksa kode kesalahan dari perintah yang Anda berikan kepadanya, dan mengeksekusi tubuhnya berdasarkan itu.

Lihat man test(atau help test, jika Anda menggunakan bash), dan help if(ditto).

Dalam hal ini, wc -lakan menampilkan angka. Kami menggunakan testopsi -gtuntuk menguji apakah angka itu lebih besar dari 0. Jika ya, test(atau [) akan kembali dengan kode keluar 0. ifakan menafsirkan kode keluar itu sebagai sukses, dan itu akan menjalankan kode di dalam tubuhnya.

angus
sumber
1

Ini akan menjadi

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5)" ]; then

atau

if test -n "$(find /var/log/crashes -name app-\*\.log -mmin -5)"; then

Perintah testdan [ … ]persis sinonim. Satu-satunya perbedaan adalah nama mereka, dan fakta yang [membutuhkan penutupan ]sebagai argumen terakhirnya. Seperti biasa, gunakan tanda kutip ganda di sekitar substitusi perintah, jika tidak output dari findperintah akan dipecah menjadi kata-kata, dan di sini Anda akan mendapatkan kesalahan sintaks jika ada lebih dari satu file yang cocok (dan ketika tidak ada argumen, [ -n ]itu benar , sedangkan yang Anda inginkan [ -n "" ]salah).

Dalam ksh, bash dan zsh tetapi tidak dalam abu, Anda juga dapat menggunakan [[ … ]]yang memiliki aturan parsing yang berbeda: [adalah perintah biasa, sedangkan [[ … ]]parsing construct yang berbeda. Anda tidak perlu tanda kutip ganda di dalam [[ … ]](meskipun mereka tidak terluka). Anda masih memerlukan perintah ;setelah.

if [[ -n $(find /var/log/crashes -name app-\*\.log -mmin -5) ]]; then

Ini berpotensi menjadi tidak efisien: jika ada banyak file di dalam /var/log/crashes, find akan menjelajahi semuanya. Anda harus menghentikan pencarian segera setelah menemukan kecocokan, atau segera setelahnya. Dengan GNU find (Linux yang tidak tertanam, Cygwin), gunakan yang -quitutama.

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5 -print -quit)" ]; then

Dengan sistem lain, pipa findke headsetidaknya berhenti segera setelah pertandingan pertama (menemukan akan mati karena pipa yang rusak).

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5 -print | head -n 1)" ]; then

(Anda dapat menggunakan head -c 1jika headperintah Anda mendukungnya.)


Atau, gunakan zsh.

crash_files=(/var/log/crashes/**/app-*.log(mm-5[1]))
if (($#crash_files)); then
Gilles 'SANGAT berhenti menjadi jahat'
sumber
1

Ini seharusnya bekerja

if find /var/log/crashes -name 'app-\*\.log' -mmin -5 | read
then
  service myapp restart
fi
Steven Penny
sumber
0
find /var/log/crashes -name app-\*\.log -mmin -5 -exec service myapp restart ';' -quit

adalah solusi tepat di sini.

-exec service myapp restart ';'menyebabkan finduntuk memanggil perintah yang ingin Anda jalankan secara langsung, daripada membutuhkan shell untuk menafsirkan apa pun.

-quitmenyebabkan findkeluar setelah memproses perintah, sehingga mencegah perintah dieksekusi lagi jika ada beberapa file yang sesuai dengan kriteria.

Jules
sumber