سؤال كيف يمكن تنفيذ أمر كلما تغير الملف؟


أريد طريقة سريعة وبسيطة لتنفيذ أمر كلما تغير الملف. أريد شيئًا بسيطًا جدًا ، شيء سأتركه قيد التشغيل على جهاز طرفي وإغلاقه عند الانتهاء من العمل مع هذا الملف.

حاليا ، أنا أستخدم هذا:

while read; do ./myfile.py ; done

ثم أحتاج إلى الذهاب إلى ذلك المطراف والصحافة أدخل، كلما قمت بحفظ هذا الملف على المحرر الخاص بي. ما أريده هو شيء من هذا القبيل:

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

أو أي حل آخر بهذه السهولة.

راجع للشغل: أستخدم Vim ، وأعرف أنه يمكنني إضافة autocommand لتشغيل شيء ما على BufWrite ، لكن هذا ليس نوع الحل الذي أريده الآن.

تحديث: أريد شيئًا بسيطًا ، غير قابل للتجاهل إن أمكن. ما هو أكثر من ذلك ، أريد تشغيل شيء في جهاز طرفي لأنني أريد رؤية إخراج البرنامج (أريد أن أرى رسائل خطأ).

حول الإجابات: شكرا لإجاباتك! كلهم جيدون جدا ، وكل واحد منهم يأخذ نهجا مختلفا عن الآخرين. بما أنني أحتاج إلى قبول واحدة فقط ، فأنا أقبل تلك التي استخدمتها بالفعل (كانت بسيطة وسريعة وسهلة التذكر) ، على الرغم من أنني أعلم أنها ليست الأكثر أناقة.


375
2017-08-27 20:02


الأصل


ممكن عبر موقع مكرر من: stackoverflow.com/questions/2972765/... (على الرغم من أنه هنا في موضوع =)) - Ciro Santilli 新疆改造中心 六四事件 法轮功
لقد أشرت قبل تكرار الموقع المتقاطع وتم رفضه: S؛) - Francisco Tapia
يعتمد الحل من قبل جوناثان هارتلي على حلول أخرى هنا ويحل المشاكل الكبيرة التي تمتلكها الإجابات ذات الأصوات العليا: فقد بعض التعديلات وعدم الكفاءة. يرجى تغيير الإجابة المقبولة له ، والتي يتم الحفاظ عليها أيضا على جيثب في github.com/tartley/rerun2 (أو إلى حل آخر دون تلك العيوب) - nealmcb


الأجوبة:


بسيطة ، باستخدام inotifywait (قم بتثبيت التوزيع الخاص بك inotify-tools صفقة):

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

أو

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

يعتبر المقتطف الأول أبسط ، ولكنه يحتوي على جانب سلبي مهم: سيفتقد التغييرات التي يتم إجراؤها أثناء inotifywait لا يعمل (على وجه الخصوص في حين myfile يركض). المقتطف الثاني ليس به هذا العيب. ومع ذلك ، يجب الحذر من أنه يفترض أن اسم الملف لا يحتوي على مسافات بيضاء. إذا كانت هذه مشكلة ، فاستخدم --format الخيار لتغيير الإخراج لعدم تضمين اسم الملف:

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

في كلتا الحالتين ، هناك قيود: إذا كان بعض البرنامج يحل محل myfile.py مع ملف مختلف ، بدلا من الكتابة إلى القائمة myfile، inotifywait سيموت. يعمل العديد من المحررين بهذه الطريقة.

للتغلب على هذا القيد ، استخدم inotifywait على الدليل:

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

بدلاً من ذلك ، استخدم أداة أخرى تستخدم نفس الوظيفة الأساسية ، مثل incron (يتيح لك تسجيل الأحداث عند تعديل الملف) أو fswatch (أداة تعمل أيضًا على العديد من المتغيرات الأخرى لـ Unix ، باستخدام تناظر كل متغير لنظام Linux الذي يعمل على inotify).


357
2017-08-27 20:54



لقد قمت بتغليف كل هذا (مع عدد غير قليل من حيل باش) في لعبة بسيطة الاستخدام sleep_until_modified.sh النصي ، متاح في: bitbucket.org/denilsonsa/small_scripts/src - Denilson Sá Maia
while sleep_until_modified.sh derivation.tex ; do latexmk -pdf derivation.tex ; done رائع. شكرا لكم. - Rhys Ulerich
inotifywait -e delete_self يبدو أنه ينفعني. - Kos
الأمر بسيط ولكنه يحتوي على مسألتين مهمتين: قد يتم تفويت الأحداث (جميع الأحداث في الحلقة) ويتم إجراء inotifywait في كل مرة مما يجعل هذا الحل أبطأ للمجلدات العودية الكبيرة. - Wernight
لسبب ما while inotifywait -e close_write myfile.py; do ./myfile.py; done دائما يخرج بدون تشغيل الأمر (bash و zsh). لهذا العمل كنت بحاجة إلى إضافة || trueعلى سبيل المثال: while inotifywait -e close_write myfile.py || true; do ./myfile.py; done - ideasman42


ENTR (http://entrproject.org/) يوفر واجهة أكثر ودية لتوطين (ويدعم أيضا * BSD و Mac OS X).

يجعل من السهل جدا تحديد ملفات متعددة للمشاهدة (محدودة فقط من قبل ulimit -n) ، يأخذ المتاعب من التعامل مع الملفات التي يتم استبدالها ، ويتطلب أقل بناء جملة باش:

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

لقد قمت باستخدامه على شجرة مصدر المشروع بالكامل لتشغيل اختبارات الوحدة للرمز الذي أقوم بتعديله حاليًا ، وقد كان ذلك بمثابة دفعة كبيرة لسير العمل الخاص بي بالفعل.

أعلام مثل -c (مسح الشاشة بين أشواط) و -d (الخروج عند إضافة ملف جديد إلى دليل مراقب) قم بإضافة المزيد من المرونة ، على سبيل المثال ، يمكنك القيام بذلك:

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

اعتبارًا من أوائل عام 2018 ، لا تزال في مرحلة التطوير النشط ويمكن العثور عليها في دبيان وأوبونتو (apt install entr)؛ بناء من repo المؤلف كان خال من الألم في أي حال.


127
2017-10-25 09:41



لا يعالج الملفات الجديدة وتعديلاتها. - Wernight
Wernight - اعتبارا من 7 مايو 2014 entr لديه الجديد -d العلم؛ هو أكثر قليلا طويلا ، ولكن يمكنك القيام به while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; done للتعامل مع الملفات الجديدة. - Paul Fenney
entr متاحة أيضا في ديبيان repos على الأقل من ديبيان جيسي / 8.2 على ... - Peter V. Mørch
أفضل واحد وجدت على نظام التشغيل العاشر بالتأكيد. fswatch الاستيلاء على العديد من الأحداث غير تقليدي وأنا لا أريد قضاء بعض الوقت لمعرفة السبب - dtc
من الجدير بالذكر ذلك ENTR متاح في البيرة ، لذلك brew install entr سوف يعمل كما هو متوقع - jmarceli


كتبت برنامج بايثون ليقوم بهذا بالضبط تغيرت عندما-.

الاستخدام بسيط:

when-changed FILE COMMAND...

أو لمشاهدة ملفات متعددة:

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

FILE يمكن أن يكون دليلا. مشاهدة متكررة مع -r. استعمال %f لتمرير اسم الملف إلى الأمر.


102
2018-06-30 13:34



ysangkok نعم يفعل ، في أحدث إصدار من التعليمات البرمجية :) - joh
متاحة الآن من "تثبيت نقطة عند تغيير". لا يزال يعمل بشكل جيد. شكر. - A. L. Flanagan
لمسح الشاشة أولاً ، يمكنك استخدامها when-changed FILE 'clear; COMMAND'. - Dave James Miller
هذه الإجابة أفضل بكثير لأني أستطيع القيام بها على Windows أيضًا. وهذا الشخص كتب فعلا برنامجا للحصول على الجواب. - Wolfpack'08
اخبارسعيدة يا جماعة! when-changed هو الآن عبر منصة! تحقق من أحدث 0.3.0 الافراج :) - joh


ماذا عن هذا البرنامج النصي؟ ويستخدم stat الأمر للحصول على وقت الوصول لملف وتشغيل أمر عند حدوث تغيير في وقت الوصول (عند الوصول إلى الملف).

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



لن stat-الوقت المعدل يكون أفضل "عند تغيير ملف" الإجابة؟ - Xen2050
هل تشغيل stat عدة مرات في الثانية يسبب العديد من القراءات على القرص؟ أو هل يقوم نظام Fstat تلقائيًا بإجراء ذاكرة التخزين المؤقت هذه الاستجابات بطريقة ما؟ أحاول أن أكتب نوعًا من "ساعة الناخر" لتجميع رمز c الخاص بي كلما قمت بإجراء تغييرات - Oskenso Kashi
هذا أمر جيد إذا كنت تعرف اسم الملف ليتم مشاهدته مقدمًا. سيكون أفضل لتمرير اسم الملف إلى البرنامج النصي. والأفضل من ذلك أن تتمكن من تمرير العديد من أسماء الملفات (على سبيل المثال. "mywatch * .py"). والأفضل من ذلك هو أن تتمكن من العمل بشكل متكرر على الملفات الموجودة في مجلدات فرعية أيضًا ، وهو ما تفعله بعض الحلول الأخرى. - Jonathan Hartley
فقط في حالة أي شخص يتساءل عن القراءات الثقيلة ، اختبرت هذا البرنامج النصي في أوبونتو 17.04 بنوم 0.05 ثانية و vmstat -d لمشاهدة الوصول إلى القرص. يبدو أن لينكس يقوم بعمل رائع في تخزين هذا النوع من الأشياء: د - Oskenso Kashi
هناك خطأ مطبعي في "COMMAND" ، كنت أحاول إصلاحه ، لكن S.O. "يجب ألا يكون التعديل أقل من 6 أحرف" - user337085


الحل باستخدام فيم:

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

لكنني لا أريد هذا الحل لأنه مزعج نوعًا ما للكتابة ، من الصعب بعض الشيء تذكر ما نكتبه بالضبط ، ومن الصعب بعض الشيء التراجع عن تأثيراته (تحتاج إلى تشغيل :au! BufWritePost myfile.py). بالإضافة إلى ذلك ، يمنع هذا الحل Vim حتى ينتهي الأمر من التنفيذ.

لقد أضفت هذا الحل هنا لمجرد اكتماله ، لأنه قد يساعد الآخرين.

لعرض خرج البرنامج (وتعطيل تدفق التحرير بالكامل ، حيث سيكتب الإخراج على المحرر لبضع ثوان ، حتى تضغط على Enter) ، قم بإزالة :silent أمر.


28
2017-08-27 20:12



هذا يمكن أن يكون لطيفا جدا عندما يقترن entr (انظر أدناه) - ما عليك سوى جعل vim touch ملفًا وهميًا يشاهده entr ، والسماح لـ entr بالقيام بالباقي في الخلفية ... أو tmux send-keys إذا كنت في مثل هذه البيئة :) - Paul Fenney
لطيف! يمكنك ان تجعل ماكرو لجهودكم .vimrc ملف - ErichBSchulz


إذا كان لديك npm المثبتة، nodemon ربما يكون أسهل طريقة للبدء ، لا سيما على نظام التشغيل OS X ، الذي يبدو أنه لا يملك أدوات التوطين. وهو يدعم تشغيل أمر عند تغيير أحد المجلدات.


24
2018-06-09 23:51



ومع ذلك ، فإنه يشاهد فقط ملفات .js و .coffee. - zelk
يبدو أن الإصدار الحالي يدعم أي أمر ، على سبيل المثال: nodemon -x "bundle exec rspec" spec/models/model_spec.rb -w app/models -w spec/models - kek
كنت أتمنى لو كان لدي المزيد من المعلومات ، ولكن لدى osx طريقة لتتبع التغييرات ، fsevents - ConstantineK
على OS X يمكنك أيضا استخدام إطلاق Daemons مع WatchPaths مفتاح كما هو موضح في الرابط الخاص بي. - Adam Johns


rerun2 (على جيثب) عبارة عن نص باش مكون من 10 أسطر من النموذج:

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

احفظ نسخة github كـ "أعد تشغيل" على PATH ، واستخدمها باستخدام:

rerun COMMAND

يتم تشغيل الأمر COMMAND في كل مرة يوجد فيها حدث تعديل نظام الملفات داخل الدليل الحالي (متكرر.)

الأشياء التي قد يعجب المرء بها:

  • ويستخدم inotify ، لذلك هو أكثر استجابة من الاقتراع. رائع لتشغيل اختبارات وحدة فرعية من ميلي ثانية واحدة ، أو تقديم ملفات نقطية graphviz ، في كل مرة تضغط فيها "حفظ".
  • نظرًا لأنه سريع جدًا ، لن تضطر إلى إزعاجك لإخباره بتجاهل الأجزاء الكبيرة (مثل node_modules) لأسباب تتعلق بالأداء فقط.
  • انها فائقة الاستجابة للغاية ، لأنه فقط يدعو inotifywait مرة واحدة ، عند بدء التشغيل ، بدلا من تشغيله ، وتحمل الثمن الباهظ لتأسيس الساعات ، على كل التكرار.
  • انها فقط 12 سطور من باش
  • نظرًا لأنه Bash ، فإنه يفسر الأوامر التي تمررها تمامًا كما لو كنت قد كتبتها في مطالبة Bash. (من المفترض أن يكون هذا أقل برودة إذا كنت تستخدم غلافًا آخر.)
  • لا تفقد الأحداث التي تحدث أثناء تنفيذ COMMAND ، بخلاف معظم الحلول inotify الأخرى على هذه الصفحة.
  • في الحدث الأول ، يدخل "فترة ميتة" لـ 0.15 ثانية ، يتم خلالها تجاهل أحداث أخرى ، قبل تشغيل COMMAND مرة واحدة بالضبط. هذا بحيث أن سلسلة الأحداث التي تسببها الرقصات التي تخلق الحركة-الكتابة والتي لا يقوم بها Vi أو Emacs عند حفظ المخزن المؤقت تسبب العديد من عمليات الإعدام الشاقة لمجموعة اختبار بطيئة التشغيل. لا يتم تجاهل أي أحداث تحدث بعد ذلك أثناء تنفيذ الأمر COMMAND - سيؤدي ذلك إلى فترة خامسة ثانية والتنفيذ اللاحق.

أشياء قد لا يعجبها أحد:

  • ويستخدم inotify ، لذلك لن تعمل خارج Linuxland.
  • لأنه يستخدم inotify ، فإنه barf على محاولة مشاهدة الدلائل التي تحتوي على ملفات أكثر من الحد الأقصى لعدد المستخدم inotify الساعات. بشكل افتراضي ، يبدو أن هذا الإعداد يتم تعيينه على حوالي 5،000 إلى 8،000 على أجهزة مختلفة أستخدمها ، ولكن من السهل زيادة هذا. نرى https://unix.stackexchange.com/questions/13751/kernel-inotify-watch-limit-reached
  • فشل في تنفيذ الأوامر التي تحتوي على الأسماء المستعارة باش. أستطيع أن أقسم أن هذا كان يعمل. من حيث المبدأ ، لأن هذا هو Bash ، لا ينفّذ COMMAND في subshell ، أتوقع أن يعمل هذا. أحب أن أسمع إذا كان أي شخص يعرف لماذا لا. العديد من الحلول الأخرى في هذه الصفحة لا تستطيع تنفيذ مثل هذه الأوامر أيضًا.
  • أنا شخصياً أتمنى لو تمكنت من الوصول إلى أحد المفاتيح في المحطة التي تعمل بها لتنفيذ أمر إضافي يدويًا. هل يمكنني إضافة هذا بطريقة أو بأخرى؟ حلقة متزامنة "أثناء قراءة" -n1 "والتي تستدعي أيضًا التنفيذ؟
  • الآن قمت بترميزها لمسح المحطة الطرفية وطباعة سطر الأوامر الذي تم تنفيذه في كل مرة. قد يرغب بعض الأشخاص في إضافة علامات سطر الأوامر لإيقاف تشغيل هذه الأشياء ، وما إلى ذلك. ولكن هذا من شأنه زيادة الحجم والتعقيد كثيرًا.

هذا هو صقل أنوي @ cychoi's.


13
2017-09-09 21:49



أعتقد أنك يجب أن تستخدم "$@" بدلا من $@، من أجل العمل بشكل صحيح مع الحجج التي تحتوي على مسافات. لكن في نفس الوقت تستخدم evalمما يفرض على مستخدم إعادة التشغيل أن يكون أكثر حذراً عند الاقتباس. - Denilson Sá Maia
شكرا دنيلسون. هل يمكن أن تعطينا مثالاً على أين يجب القيام بالاقتباس بعناية؟ لقد تم استخدامه في 24 ساعة الماضية ولم أر أي مشاكل مع مسافات حتى الآن ، ولا بحرص ونقلت أي شيء - استدعيت للتو باسم rerun 'command'. هل أنت فقط تقول أنه إذا استخدمت "$ @" ، فيمكن للمستخدم أن يستدعي rerun command (بدون علامات اقتباس؟) لا يبدو ذلك مفيدًا بالنسبة لي: فأنا لا أريد بصفة عامة أن تفعل Bash أي معالجة الأمر قبل تمريره لإعادة. مثلا إذا كان الأمر يحتوي على "echo $ myvar" ، فحينئذٍ أريد أن أرى القيم الجديدة لـ myvar في كل عملية تكرار. - Jonathan Hartley
شيء مثل rerun foo "Some File" قد كسر. ولكن منذ كنت تستخدم eval، يمكن إعادة كتابتها باسم rerun 'foo "Some File". لاحظ أنه في بعض الأحيان قد يؤدي توسيع المسار إلى تقديم مسافات: rerun touch *.foo من المرجح كسر ، واستخدام rerun 'touch *.foo' لديه دلالات مختلفة قليلاً (توسيع المسار يحدث مرة واحدة فقط ، أو عدة مرات). - Denilson Sá Maia
شكرا للمساعدة. نعم: rerun ls "some file" يكسر بسبب الفراغات. rerun touch *.foo* يعمل بشكل جيد ، لكنه يفشل إذا كانت أسماء الملفات التي تطابق * .foo تحتوي على مسافات. شكرا لمساعدتي في رؤية كيف rerun 'touch *.foo' لديه دلالات مختلفة ، ولكن أظن أن النسخة مع اقتباسات مفردة هي الدلالة التي أريدها: أريد كل تكرار لإعادة العمل كما لو قمت بكتابة الأمر مرة أخرى - لذلك أنا تريد  *.foo ليتم توسيعها على كل التكرار. سأحاول اقتراحاتك لفحص آثارها ... - Jonathan Hartley
مزيد من المناقشة حول هذا العلاقات العامة (github.com/tartley/rerun2/pull/1) و اخرين. - Jonathan Hartley