Pertanyaan Bagaimana cara menjalankan perintah setiap kali file berubah?


Saya ingin cara cepat dan sederhana untuk menjalankan perintah setiap kali file berubah. Saya menginginkan sesuatu yang sangat sederhana, sesuatu yang saya akan biarkan berjalan di terminal dan menutupnya setiap kali saya selesai bekerja dengan file itu.

Saat ini, saya menggunakan ini:

while read; do ./myfile.py ; done

Dan kemudian saya harus pergi ke terminal itu dan tekan Memasukkan, setiap kali saya menyimpan file itu di editor saya. Yang saya inginkan adalah sesuatu seperti ini:

while sleep_until_file_has_changed myfile.py ; do ./myfile.py ; done

Atau solusi lain semudah itu.

BTW: Saya menggunakan Vim, dan saya tahu saya dapat menambahkan autocommand untuk menjalankan sesuatu di BufWrite, tetapi ini bukan jenis solusi yang saya inginkan sekarang.

Memperbarui: Saya ingin sesuatu yang sederhana, dapat dibuang jika memungkinkan. Terlebih lagi, saya ingin sesuatu berjalan di terminal karena saya ingin melihat output program (saya ingin melihat pesan kesalahan).

Tentang jawabannya: Terima kasih atas semua jawaban Anda! Semuanya sangat bagus, dan masing-masing mengambil pendekatan yang sangat berbeda dari yang lain. Karena saya hanya perlu menerima satu, saya menerima yang benar-benar saya gunakan (itu sederhana, cepat dan mudah diingat), meskipun saya tahu itu bukan yang paling elegan.


357
2017-08-27 20:02


asal


Kemungkinan duplikat lintas situs: stackoverflow.com/questions/2972765/… (meskipun ini topiknya)) - Ciro Santilli 新疆改造中心 六四事件 法轮功
Saya telah direferensikan sebelum duplikat lintas situs dan ditolak: S;) - Francisco Tapia
Solusinya oleh Jonathan Hartley dibangun di atas solusi lain di sini dan memperbaiki masalah besar yang dijawab oleh jawaban atas: tidak ada modifikasi dan tidak efisien. Harap ubah jawaban yang diterima untuknya, yang juga sedang dikelola di github github.com/tartley/rerun2 (atau ke solusi lain tanpa cacat tersebut) - nealmcb


Jawaban:


Sederhana, menggunakan inotifywait (instal distribusi Anda inotify-tools paket):

while inotifywait -e close_write myfile.py; do ./myfile.py; done

atau

inotifywait -q -m -e close_write myfile.py |
while read -r filename event; do
  ./myfile.py         # or "./$filename"
done

Cuplikan pertama lebih sederhana, tetapi memiliki kelebihan yang signifikan: itu akan kehilangan perubahan yang dilakukan sementara inotifywait tidak berjalan (khususnya sementara myfile sedang berlari). Cuplikan kedua tidak memiliki cacat ini. Namun, berhati-hatilah karena menganggap bahwa nama file tidak mengandung spasi putih. Jika itu masalah, gunakan --format pilihan untuk mengubah output agar tidak menyertakan nama file:

inotifywait -q -m -e close_write --format %e myfile.py |
while read events; do
  ./myfile.py
done

Either way, ada batasan: jika beberapa program menggantikan myfile.py dengan file yang berbeda, daripada menulis ke yang ada myfile, inotifywait akan mati. Banyak editor bekerja seperti itu.

Untuk mengatasi keterbatasan ini, gunakan inotifywait di direktori:

inotifywait -e close_write,moved_to,create -m . |
while read -r directory events filename; do
  if [ "$filename" = "myfile.py" ]; then
    ./myfile.py
  fi
done

Atau, gunakan alat lain yang menggunakan fungsi dasar yang sama, seperti incron (memungkinkan Anda mendaftarkan acara ketika suatu file diubah) atau fswatch (alat yang juga bekerja pada banyak varian Unix lainnya, menggunakan masing-masing varian varian Linux inotify).


341
2017-08-27 20:54



Saya telah mengenkapsulasi semua ini (dengan beberapa tipuan bash) dengan cara yang mudah digunakan sleep_until_modified.sh skrip, tersedia di: bitbucket.org/denilsonsa/small_scripts/src - Denilson Sá Maia
while sleep_until_modified.sh derivation.tex ; do latexmk -pdf derivation.tex ; done itu fantastis. Terima kasih. - Rhys Ulerich
inotifywait -e delete_self tampaknya bekerja dengan baik untukku. - Kos
Ini sederhana tetapi memiliki dua masalah penting: Acara mungkin terlewatkan (semua peristiwa dalam loop) dan inisialisasi inotifywait dilakukan setiap kali yang membuat solusi ini lebih lambat untuk folder rekursif besar. - Wernight
Untuk beberapa alasan while inotifywait -e close_write myfile.py; do ./myfile.py; done selalu keluar tanpa menjalankan perintah (bash dan zsh). Agar ini berhasil, saya perlu menambahkan || true, misalnya: while inotifywait -e close_write myfile.py || true; do ./myfile.py; done - ideasman42


entr (http://entrproject.org/) menyediakan antarmuka yang lebih ramah untuk inotify (dan juga mendukung * BSD & Mac OS X).

Itu membuatnya sangat mudah untuk menentukan beberapa file untuk ditonton (hanya dibatasi oleh ulimit -n), menghilangkan kerumitan menangani file yang sedang diganti, dan membutuhkan lebih sedikit bash syntax:

$ find . -name '*.py' | entr ./myfile.py

Saya telah menggunakannya di seluruh pohon sumber proyek saya untuk menjalankan tes unit untuk kode yang saat ini saya modifikasi, dan ini telah menjadi dorongan besar bagi alur kerja saya.

Bendera seperti -c (Bersihkan layar antara run) dan -d (keluar ketika file baru ditambahkan ke direktori yang dipantau) menambahkan lebih banyak fleksibilitas, misalnya Anda dapat melakukan:

$ while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; done

Pada awal 2018 masih dalam pengembangan aktif dan dapat ditemukan di Debian & Ubuntu (apt install entr); membangun dari repo penulis itu bebas rasa sakit dalam hal apapun.


120
2017-10-25 09:41



Tidak menangani file baru dan modifikasi mereka. - Wernight
@Wernight - mulai 7 Mei 2014 entr memiliki yang baru -d bendera; itu sedikit lebih bertele-tele, tetapi Anda bisa melakukannya while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; done untuk menangani file baru. - Paul Fenney
tersedia di aur aur.archlinux.org/packages/entr - Victor Häggqvist
yang terbaik yang saya temukan di OS X pasti. fswatch meraih terlalu banyak acara yang funky dan saya tidak ingin menghabiskan waktu untuk mencari tahu mengapa - dtc
Perlu dicatat itu entr tersedia di Homebrew, jadi brew install entr akan berfungsi seperti yang diharapkan - jmarceli


Saya menulis program Python untuk melakukan hal ini kapan berubah.

Penggunaannya sederhana:

when-changed FILE COMMAND...

Atau untuk menonton banyak file:

when-changed FILE [FILE ...] -c COMMAND

FILE bisa berupa direktori. Tonton secara rekursif dengan -r. Menggunakan %f untuk meneruskan nama file ke perintah.


100
2018-06-30 13:34



@ysangkok ya ya, di versi terbaru kode :) - joh
Sekarang tersedia dari "pip install when-changed". Masih berfungsi dengan baik. Terima kasih. - A. L. Flanagan
Untuk membersihkan layar pertama yang dapat Anda gunakan when-changed FILE 'clear; COMMAND'. - Dave James Miller
Jawaban ini jauh lebih baik karena saya bisa melakukannya di Windows juga. Dan pria ini sebenarnya menulis sebuah program untuk mendapatkan jawabannya. - Wolfpack'08
Kabar baik semuanya! when-changed sekarang cross-platform! Lihat yang terbaru Pelepasan 0.3.0 :) - joh


Bagaimana dengan skrip ini? Ini menggunakan stat perintah untuk mendapatkan waktu akses file dan menjalankan perintah setiap kali ada perubahan dalam waktu akses (kapan pun file diakses).

#!/bin/bash

### Set initial time of file
LTIME=`stat -c %Z /path/to/the/file.txt`

while true    
do
   ATIME=`stat -c %Z /path/to/the/file.txt`

   if [[ "$ATIME" != "$LTIME" ]]
   then    
       echo "RUN COMMAND"
       LTIME=$ATIME
   fi
   sleep 5
done

46
2017-08-20 17:12



Tidak akan stat-seperti waktu yang diubah menjadi lebih baik "setiap kali sebuah file berubah" menjawab? - Xen2050
Apakah menjalankan stat berkali-kali per detik menyebabkan banyak membaca ke disk? atau apakah panggilan sistem fstat secara otomatis membuat cache tanggapan ini entah bagaimana? Saya mencoba menulis semacam 'grunt watch' untuk mengkompilasi kode c saya setiap kali saya melakukan perubahan - Oskenso Kashi
Ini bagus jika Anda tahu nama file yang harus ditonton sebelumnya. Lebih baik akan meneruskan nama file ke skrip. Lebih baik tetap jika Anda dapat melewati banyak nama file (mis. "Mywatch * .py"). Lebih baik lagi jika itu bisa beroperasi secara rekursif pada file di subdir juga, yang beberapa solusi lain lakukan. - Jonathan Hartley
Jika ada yang bertanya-tanya tentang pembacaan yang berat, saya menguji skrip ini di Ubuntu 17.04 dengan tidur 0,05 dan vmstat -d untuk mengawasi akses disk. Sepertinya linux melakukan pekerjaan yang fantastis dalam melakukan cache hal semacam ini: D - Oskenso Kashi
Ada kesalahan ketik di "COMMAND", saya sedang berusaha memperbaikinya, tetapi S.O. mengatakan "Edit tidak boleh kurang dari 6 karakter" - user337085


Solusi menggunakan Vim:

:au BufWritePost myfile.py :silent !./myfile.py

Tapi saya tidak ingin solusi ini karena agak menjengkelkan untuk mengetik, agak sulit untuk mengingat apa yang harus diketik, tepatnya, dan itu agak sulit untuk membatalkan pengaruhnya (harus dijalankan :au! BufWritePost myfile.py). Selain itu, solusi ini memblokir Vim hingga perintah selesai dieksekusi.

Saya telah menambahkan solusi ini di sini hanya untuk kelengkapan, karena dapat membantu orang lain.

Untuk menampilkan output program (dan benar-benar mengganggu alur editan Anda, karena output akan menulis di editor Anda selama beberapa detik, sampai Anda menekan Enter), hapus :silent perintah.


28
2017-08-27 20:12



Ini bisa sangat bagus ketika dikombinasikan dengan entr (lihat di bawah) - cukup sentuh saja file dummy yang sedang dilihat, dan biarkan entr melakukan sisanya di latar belakang ... atau tmux send-keys jika Anda berada dalam lingkungan seperti itu :) - Paul Fenney
bagus! Anda dapat membuat makro untuk Anda .vimrc mengajukan - ErichBSchulz


Jika Anda kebetulan punya npm terpasang, nodemon mungkin adalah cara termudah untuk memulai, terutama pada OS X, yang tampaknya tidak memiliki alat inotify. Mendukung menjalankan perintah ketika folder berubah.


24
2018-06-09 23:51



Namun, hanya melihat file .js dan .coffee. - zelk
Versi saat ini tampaknya mendukung perintah apa pun, misalnya: nodemon -x "bundle exec rspec" spec/models/model_spec.rb -w app/models -w spec/models - kek
Saya harap saya memiliki lebih banyak info, tetapi osx memiliki metode untuk melacak perubahan, fsevents - ConstantineK
Pada OS X Anda juga bisa menggunakan Luncurkan Daemon dengan WatchPaths kunci seperti yang ditunjukkan di tautan saya. - Adam Johns


Berikut shell shell Bourne shell sederhana yang:

  1. Membawa dua argumen: file yang akan dimonitor dan perintah (dengan argumen, jika perlu)
  2. Salinan file yang Anda pantau ke direktori / tmp
  3. Periksa setiap dua detik untuk melihat apakah file yang Anda pantau lebih baru daripada salinan
  4. Jika lebih baru itu menimpa salinan dengan yang lebih baru asli dan mengeksekusi perintah
  5. Bersihkan diri setelah Anda menekan Ctr-C

    #!/bin/sh  
    f=$1  
    shift  
    cmd=$*  
    tmpf="`mktemp /tmp/onchange.XXXXX`"  
    cp "$f" "$tmpf"  
    trap "rm $tmpf; exit 1" 2  
    while : ; do  
        if [ "$f" -nt "$tmpf" ]; then  
            cp "$f" "$tmpf"  
            $cmd  
        fi  
        sleep 2  
    done  
    

Ini berfungsi pada FreeBSD. Satu-satunya masalah portabilitas yang dapat saya pikirkan adalah jika beberapa Unix yang lain tidak memiliki perintah mktemp (1), tetapi dalam kasus itu Anda hanya dapat mengodekan nama file temp.


12
2017-08-27 21:23



Polling adalah satu-satunya cara portabel, tetapi kebanyakan sistem memiliki mekanisme pemberitahuan perubahan file (inotify pada Linux, kqueue pada FreeBSD, ...). Anda memiliki masalah mengutip yang parah ketika Anda melakukannya $cmd, tapi untungnya itu mudah diperbaiki: parit cmd variabel dan eksekusi "$@". Skrip Anda tidak cocok untuk memantau file besar, tetapi itu bisa diperbaiki dengan mengganti cp oleh touch -r (Anda hanya butuh tanggal, bukan isinya). Portabilitas-bijaksana, yang -nttes membutuhkan bash, ksh atau zsh. - Gilles


rerun2 (di github) adalah script Bash 10-baris formulir:

#!/usr/bin/env bash

function execute() {
    clear
    echo "$@"
    eval "$@"
}

execute "$@"

inotifywait --quiet --recursive --monitor --event modify --format "%w%f" . \
| while read change; do
    execute "$@"
done

Simpan versi github sebagai 'tayangan ulang' di PATH Anda, dan gunakan menggunakan:

rerun COMMAND

Menjalankan COMMAND setiap kali ada peristiwa modifikasi filesystem dalam direktori Anda saat ini (rekursif.)

Hal-hal yang mungkin disukai olehnya:

  • Menggunakan inotify, jadi lebih responsif daripada polling. Luar biasa untuk menjalankan pengujian unit sub-milidetik, atau merender file graphviz dot, setiap kali Anda menekan 'simpan'.
  • Karena begitu cepat, Anda tidak perlu repot-repot memberitahukannya untuk mengabaikan subdir besar (seperti node_modules) hanya karena alasan kinerja.
  • Ini ekstra super responsif, karena itu hanya memanggil inotify menunggu satu kali, saat startup, bukannya menjalankannya, dan menimbulkan hit mahal membangun jam tangan, pada setiap iterasi.
  • Hanya 12 baris Bash
  • Karena itu Bash, ia menafsirkan perintah yang Anda lewati persis seolah-olah Anda mengetiknya pada prompt Bash. (Mungkin ini kurang keren jika Anda menggunakan shell lain.)
  • Itu tidak kehilangan peristiwa yang terjadi ketika COMMAND mengeksekusi, tidak seperti kebanyakan solusi inotify lainnya di halaman ini.
  • Pada acara pertama, itu memasuki 'periode mati' selama 0,15 detik, di mana acara lainnya diabaikan, sebelum COMMAND dijalankan tepat satu kali. Ini adalah agar kesibukan peristiwa yang disebabkan oleh gerakan membuat-tulis-bergerak yang dilakukan oleh Vi atau Emacs ketika menyimpan buffer tidak menyebabkan beberapa eksekusi yang melelahkan dari sebuah test suite yang mungkin berjalan lambat. Peristiwa apa pun yang terjadi saat COMMAND sedang dieksekusi tidak diabaikan - mereka akan menyebabkan periode mati kedua dan eksekusi berikutnya.

Hal-hal yang mungkin tidak disukai orang lain:

  • Menggunakan inotify, jadi tidak akan berfungsi di luar Linuxland.
  • Karena menggunakan inotify, ia akan muntah ketika mencoba untuk menonton direktori yang berisi lebih banyak file daripada jumlah maksimum jam tangan inotify pengguna. Secara default, ini tampaknya diatur menjadi sekitar 5.000 hingga 8.000 pada mesin yang berbeda yang saya gunakan, tetapi mudah untuk ditingkatkan. Lihat https://unix.stackexchange.com/questions/13751/kernel-inotify-watch-limit-cara
  • Gagal untuk mengeksekusi perintah yang mengandung alias Bash. Saya berani bersumpah bahwa ini digunakan untuk bekerja. Pada prinsipnya, karena ini adalah Bash, tidak mengeksekusi COMMAND dalam subkulit, saya berharap ini bekerja. Saya ingin mendengar Jika ada yang tahu mengapa tidak. Banyak solusi lain di halaman ini yang tidak bisa menjalankan perintah semacam itu.
  • Secara pribadi saya berharap saya bisa menekan kunci di terminal itu berjalan untuk secara manual menyebabkan eksekusi ekstra COMMAND. Bisakah saya menambahkan ini, bagaimanapun caranya? Sebuah loop 'sambil membaca -n1' yang juga menjalankan panggilan secara bersamaan?
  • Saat ini saya telah mengkodekannya untuk membersihkan terminal dan mencetak COMMAND yang dieksekusi pada setiap iterasi. Beberapa orang mungkin ingin menambahkan bendera command-line untuk mengubah hal-hal seperti ini, dll. Namun ini akan meningkatkan ukuran dan kompleksitas banyak-lipat.

Ini adalah penyempurnaan dari jawaban @ cychoi.


12
2017-09-09 21:49



Saya percaya Anda harus menggunakannya "$@" dari pada $@, agar berfungsi dengan baik dengan argumen yang berisi spasi. Tetapi pada saat yang sama Anda gunakan eval, yang memaksa pengguna mengulang untuk ekstra hati-hati saat mengutip. - Denilson Sá Maia
Terima kasih Denilson. Bisakah Anda memberikan contoh di mana kutipan perlu dilakukan dengan hati-hati? Saya telah menggunakannya 24 jam terakhir dan belum melihat masalah dengan ruang sejauh ini, juga hati-hati mengutip apa pun - baru dipanggil sebagai rerun 'command'. Apakah Anda hanya mengatakan bahwa jika saya menggunakan "$ @", maka pengguna dapat memohon sebagai rerun command (tanpa tanda kutip?) Itu tidak berguna bagi saya: Saya biasanya tidak ingin Bash melakukannya apa saja pemrosesan perintah sebelum meneruskannya untuk memutarkan lagi. misalnya jika perintah mengandung "echo $ myvar", maka saya akan ingin melihat nilai-nilai baru dari myvar dalam setiap iterasi. - Jonathan Hartley
Sesuatu seperti rerun foo "Some File" mungkin hancur. Tapi karena kamu menggunakannya eval, dapat ditulis ulang sebagai rerun 'foo "Some File". Perhatikan bahwa terkadang perluasan jalur mungkin memperkenalkan spasi: rerun touch *.foo kemungkinan akan rusak, dan menggunakan rerun 'touch *.foo' memiliki semantik yang sedikit berbeda (perluasan jalur hanya terjadi sekali, atau beberapa kali). - Denilson Sá Maia
Terima kasih untuk bantuannya. Ya: rerun ls "some file" istirahat karena ruang. rerun touch *.foo* berfungsi dengan baik biasanya, tetapi gagal jika namafile yang cocok * .foo mengandung spasi. Terima kasih telah membantu saya melihat caranya rerun 'touch *.foo' memiliki semantik yang berbeda, tapi saya menduga versi dengan tanda kutip tunggal adalah semantik yang saya inginkan: Saya ingin setiap iterasi tayangan ulang bertindak seolah-olah saya mengetikkan perintah lagi - maka saya ingin  *.foo untuk diperluas pada setiap iterasi. Saya akan mencoba saran Anda untuk memeriksa efeknya ... - Jonathan Hartley
Diskusi lebih lanjut tentang PR ini (github.com/tartley/rerun2/pull/1) dan lain-lain. - Jonathan Hartley