├── LICENSE ├── README.md ├── tg └── trigger /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 David Peter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # trigger 2 | 3 | ``` bash 4 | Usage: trigger COMMAND [FILE...] 5 | ``` 6 | 7 | trigger runs the given *COMMAND* every time one of the *FILE*s is changed. 8 | If no *FILE* argument is given, trigger watches everything in the current 9 | directory, recursively. 10 | 11 | In the *COMMAND* string, `#1`, `#2`, .. can be used as synonyms for the 12 | *FILE*names. The helper `tg COMMAND FILE ...` is a shortcut for 13 | `trigger 'COMMAND #1' FILE ...`. 14 | 15 | ![Example usage](http://i.imgur.com/xlpR376.gif) 16 | 17 | ## Examples 18 | 19 | ### Make 20 | 21 | Run `make` every time one of the files in the current directory changes: 22 | 23 | ``` 24 | trigger make 25 | ``` 26 | 27 | ### Python 28 | 29 | Run `python main.py` every time either `main.py` or `config.py` changes: 30 | 31 | ``` bash 32 | trigger 'python #1' main.py config.py 33 | ``` 34 | 35 | By using the `tg` helper command, this can be shortened to: 36 | 37 | ``` bash 38 | tg python main.py config.py 39 | ``` 40 | 41 | ### Markdown to PDF 42 | 43 | Convert a Markdown document to PDF every time it is changed. Combine this with 44 | an auto-reloading PDF-viewer (e.g. okular) to get a live preview: 45 | 46 | ``` bash 47 | tg 'pandoc -t latex -o README.pdf' README.md 48 | ``` 49 | 50 | ### Less to CSS 51 | 52 | Convert a LESS file to CSS: 53 | 54 | ``` bash 55 | trigger 'lessc #1 > main.css' main.less 56 | ``` 57 | 58 | ### Interrupt mode 59 | 60 | If `trigger` is called with the `-i` (or `--interrupt`) option, it will kill 61 | running subprocesses whenever a file changes: 62 | ``` bash 63 | trigger -i longRunningCommand input.dat 64 | ``` 65 | 66 | 67 | ## Requirements 68 | 69 | trigger is just a simple wrapper around `inotifywait`. It should be available 70 | for most Linux distributions (the package is typically called `inotify-tools`). 71 | 72 | 73 | ## Installation 74 | 75 | Keeping in mind that, in principle, you should not copy-and-paste into your 76 | shell, something like this should work: 77 | 78 | ``` bash 79 | git clone https://github.com/sharkdp/trigger ~/.trigger 80 | 81 | echo 'export PATH="$PATH:$HOME/.trigger"' >> .bashrc 82 | ``` 83 | 84 | ## Related projects 85 | 86 | Also have a look at these projects, to see if they fit your needs better: 87 | 88 | - [http://entrproject.org/](http://entrproject.org/) - more features, slightly more complicated syntax 89 | - [https://github.com/joh/when-changed](https://github.com/joh/when-changed) - different syntax, requires python and watchdog 90 | - [https://facebook.github.io/watchman/](https://facebook.github.io/watchman/) - much more powerful but more complicated syntax. 91 | - [http://inotify.aiken.cz/?section=incron&page=about](http://inotify.aiken.cz/?section=incron&page=about) - more difficult to setup up 92 | -------------------------------------------------------------------------------- /tg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Usage: 4 | # 5 | # tg COMMAND FILE1 [FILE2..] 6 | # 7 | # A simple wrapper on top of 'trigger'. 8 | # 9 | # Calling 'tg python main.py' is equivalent to 10 | # 11 | # trigger 'python #1' main.py 12 | # 13 | 14 | if [[ $# -lt 2 ]]; then 15 | echo "Usage: tg COMMAND FILE1 [FILE2..]" 16 | exit 1 17 | fi 18 | 19 | command="$1" 20 | shift 21 | trigger "$command #1" "$@" 22 | -------------------------------------------------------------------------------- /trigger: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Usage: 4 | # 5 | # trigger [OPTIONS] COMMAND [FILE...] 6 | # 7 | # Runs the given COMMAND every time one of the FILEs is changed. 8 | # 9 | # In the COMMAND string, #1, #2, ..., #9 can be used as synonyms for FILE1, 10 | # FILE2, ..., FILE9. 11 | # 12 | # OPTIONS 13 | # 14 | # -i, --interrupt 15 | # If this mode is enabled, a running COMMAND will be killed if a file 16 | # changes. 17 | # 18 | # EXAMPLE 19 | # 20 | # trigger 'python #1' main.py config.py 21 | # 22 | 23 | # This flag determines Whether or not 'trigger' is running in interrupt mode. 24 | # If this mode is enabled, trigger will kill the subprocess when a file changes 25 | # (in case it is still running). 26 | interruptMode=false 27 | 28 | # Check command line arguments 29 | if [[ "$1" == "-i" || "$1" == "--interrupt" ]]; then 30 | interruptMode=true 31 | 32 | # The process ID of the subprocess 33 | lastPID="none" 34 | shift 35 | fi 36 | 37 | if [[ $# -eq 0 ]]; then 38 | echo "Usage: trigger COMMAND [FILE...]" 39 | exit 1 40 | fi 41 | 42 | # Output colors 43 | reset='\x1b[0m' 44 | blue='\x1b[34;01m' 45 | green='\x1b[32;01m' 46 | red='\x1b[31;01m' 47 | 48 | command="$1" 49 | 50 | if [[ $# -gt 1 ]]; then 51 | # Replace occurences of #1, #2, .., #9 in the given command with the 52 | # corresponding file names 53 | 54 | shift 55 | for n in {1..9}; do 56 | # get the n-th filename 57 | ARG="${!n}" 58 | 59 | # replace #n with the n-th filename 60 | command="${command//\#${n}/$ARG}" 61 | done 62 | 63 | watchall=false 64 | else 65 | watchall=true 66 | fi 67 | 68 | runChild() { 69 | if [[ $# -eq 1 ]]; then 70 | local cfile="$1" 71 | echo -e "${blue}>>>${reset} File '$cfile' has been changed" 72 | fi 73 | 74 | # Run the command and measure the elapsed time 75 | local starttime=$SECONDS 76 | 77 | eval "$command" 78 | local status="$?" 79 | 80 | local elapsed=$((SECONDS - starttime)) 81 | 82 | # Status output 83 | local status_info="" 84 | local color="$green" 85 | 86 | if [[ $status -ne 0 ]]; then 87 | status_info="with exit status $status " 88 | color=$red 89 | fi 90 | 91 | echo -e "${color}>>>${reset} finished ${status_info}after ${elapsed} second(s)" 92 | echo 93 | } 94 | 95 | run() { 96 | if [[ $interruptMode = true ]]; then 97 | numPIDS=$(ps --no-headers -o pid --ppid=$$ | wc -w) 98 | if [[ $numPIDS != 1 ]]; then 99 | # We have at least one subprocess (that will be killed) 100 | echo -e "${red}>>>${reset} Interrupting currently running process\n" 101 | 102 | kill $lastPID 103 | wait $lastPID 2> /dev/null 104 | fi 105 | 106 | # Start the subprocess asynchronously 107 | runChild "$@" & 108 | lastPID=$! 109 | else 110 | # Start the subprocess synchronously 111 | runChild "$@" 112 | fi 113 | } 114 | 115 | # Run the command once 116 | echo -e "${blue}>>>${reset} Initial run of '${command}'" 117 | run 118 | 119 | # Run the command repeatedly, every time one of the files changes 120 | 121 | if [[ $watchall = true ]]; then 122 | # Watch all files in the current directory 123 | 124 | while cfile=$(inotifywait --quiet --format '%w%f' --event close_write,move_self --exclude '\.git' -r .); do 125 | run "$cfile" 126 | done 127 | else 128 | while cfile=$(inotifywait --quiet --format '%w' --event close_write,move_self "$@"); do 129 | [[ ! -e "$cfile" ]] && sleep 0.1 130 | if [[ ! -e "$cfile" ]]; then 131 | echo -n "File '$cfile' was deleted, waiting for it to reappear .." 132 | while [[ ! -e "$cfile" ]]; do 133 | sleep 0.1 134 | echo -n "." 135 | done 136 | echo 137 | fi 138 | run "$cfile" 139 | done 140 | fi 141 | --------------------------------------------------------------------------------