430

I'm looking for the best way to duplicate the Linux 'watch' command on Mac OS X. I'd like to run a command every few seconds to pattern match on the contents of an output file using 'tail' and 'sed'.

What's my best option on a Mac, and can it be done without downloading software?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
joseph.hainline
  • 24,829
  • 18
  • 53
  • 70

14 Answers14

826

With Homebrew installed:

brew install watch
phuclv
  • 37,963
  • 15
  • 156
  • 475
seantomburke
  • 10,514
  • 3
  • 18
  • 23
  • 43
    Definitely the way to go if you use `brew` on mac. – Maxence Nov 12 '14 at 16:24
  • 4
    More details would be appreciated. When I search "brew watch osx", this is the top search result. **EDIT: Apparently it's a Linux command. For those who weren't sure how to use `watch`:** http://stackoverflow.com/questions/13593771/linux-repeat-command-automatically – jds Mar 29 '16 at 18:16
  • 6
    This `watch` from brew doesn't seem to read the user aliases. When executing `watch somealias`, I get a command not found. – RNickMcCandless Mar 31 '16 at 19:21
  • 2
    @NickMc the proper way to use watch is to do `watch [command]` when you use `watch somealias` it is assuming that `somealias` is a command that you can run in your terminal. – seantomburke Apr 02 '16 at 00:07
  • 1
    Given the simplicity of the accepted answer, in my view your answer is 1. an overkill 2. hide detail implementations in a sense that it makes others think `watch` command is something super hard to implement. – mfaani Feb 02 '21 at 01:16
  • 3
    This and the accepted answer are both good. If you run `man watch` after install, you'll see it has more capabilities than a simple command/script, starting with a default duration and an optional switch (-n). If avoiding dependencies is a priority the accepted answer is great, but otherwise `brew install watch` takes minimal effort and gives you the capability you might be used to if using Linux. – fazy Jul 29 '21 at 15:23
  • I used this method and trying it with `watch lsof -i tcp`. It only shows whatever fits on the screen and does not let me scroll the output in neither Terminal nor iTerm2. Google only shows apple watches since I have to put words like "brew", "watch", "osx", "scroll"... – Serj Mar 23 '22 at 18:05
  • This command affects the iTerm2's color when used like: `watch --color` – alper Apr 06 '23 at 11:38
437

You can emulate the basic functionality with the shell loop:

while :; do clear; your_command; sleep 2; done

That will loop forever, clear the screen, run your command, and wait two seconds - the basic watch your_command implementation.

You can take this a step further and create a watch.sh script that can accept your_command and sleep_duration as parameters:

#!/bin/bash
# usage: watch.sh <your_command> <sleep_duration>

while :; 
  do 
  clear
  date
  $1
  sleep $2
done
pjvandehaar
  • 1,070
  • 1
  • 10
  • 24
Daniel Pittman
  • 16,733
  • 4
  • 41
  • 34
  • 3
    I believe watch only shows the first screenful of output. If you want to do something similar, change `your_command` to `your_command 2>&1|head -10` – Mark Eirich Nov 10 '13 at 15:36
  • 13
    This works pretty well, but I prefer `sleep ${2:-1}` to make the interval optional. – Daniel Lubarov Nov 20 '13 at 10:13
  • 3
    Also, it's `echo "$(date)"` equivalent to `date`? – Daniel Lubarov Nov 27 '13 at 05:02
  • 5
    It also doesn't work with pipes. Here's a fork with various improvements: http://daniel.lubarov.com/simple-watch-script-for-osx – Daniel Lubarov Nov 27 '13 at 05:11
  • Doesn't work like `watch` when the output is more than one screen. Only shows the end, while `watch` only shows the top. (Neither is ideal) – h__ Feb 05 '14 at 08:20
  • Is there any performance differences between the loop and watch? – Levent Yumerov Feb 04 '16 at 09:07
  • `chmod a+x watch.sh` to make it executable – gyo Jun 08 '17 at 18:38
  • Funny: This works, but the `brew` version of `watch` just shows a blank screen for some commands. – Throw Away Account Jun 12 '17 at 19:11
  • After I stop this command and scroll upwards in the bash I see all the output of the loop. This is not the case with the original `watch`command. Is there a way to prevent this? – asmaier Apr 23 '20 at 11:30
  • I often use [`watch` from @seantomburke's answer](https://stackoverflow.com/a/23370705/809944) but I can't get it to work when the command I want to run has a `|`. This option, while longer to write, does the job. – mokagio May 18 '20 at 05:01
  • ``` # Get the watch command back function get_watch_command() { echo $1 } # watch any function in linux; function watch_function() { command_str=$1 while :; do clear date $(get_watch_command ${command_str}) sleep ${SLEEP_TIME}s done } ``` – Arun George Jun 07 '21 at 07:44
44

Use MacPorts:

$ sudo port install watch
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
GoTLiuM
  • 781
  • 9
  • 3
  • 3
    There is no better way then using the `watch` command directly. – Oliver Aug 25 '13 at 10:05
  • 11
    Why use brew instead of MacPorts? – bmauter May 27 '15 at 14:41
  • 2
    @bmauter, to each their own: http://stackoverflow.com/questions/21374366/what-is-the-difference-usage-of-homebrew-macports-or-other-package-installation – CenterOrbit Nov 02 '15 at 04:50
  • 5
    @CenterOrbit, that's precisely why I asked. @joseph.hainline says don't use MacPorts, but doesn't state why. Is the `watch` command busted in MacPorts? – bmauter Nov 02 '15 at 19:46
  • 1
    It's not busted. That's terrible advice. MacPorts works great and has tons of packages. Use it. – Ali Gangji Feb 07 '19 at 00:44
  • 5
    I never recovered emotionally from using MacPorts. It ended up screwing up my system so thoroughly that I had to completely reformat my hard drive. Homebrew all the way--I challenge you to find something prevalent that's on ports that's not on HB. – mpowered Feb 07 '19 at 02:45
22

The shells above will do the trick, and you could even convert them to an alias (you may need to wrap in a function to handle parameters):

alias myWatch='_() { while :; do clear; $2; sleep $1; done }; _'

Examples:

myWatch 1 ls ## Self-explanatory
myWatch 5 "ls -lF $HOME" ## Every 5 seconds, list out home directory; double-quotes around command to keep its arguments together

Alternately, Homebrew can install the watch from http://procps.sourceforge.net/:

brew install watch
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
IT Gumby
  • 1,067
  • 8
  • 11
  • 1
    Why do you prefer an alias to a function if you're wrapping it in a function anyway? – philraj Jul 14 '20 at 18:29
  • 1
    No intentional preference; I just have a habit of creating aliases. It is possible those habits are born from legacy shells that didn't rely on or support functions, but I can't confirm. – IT Gumby Dec 02 '20 at 21:00
17

It may be that "watch" is not what you want. You probably want to ask for help in solving your problem, not in implementing your solution! :)

If your real goal is to trigger actions based on what's seen from the tail command, then you can do that as part of the tail itself. Instead of running "periodically", which is what watch does, you can run your code on demand.

#!/bin/sh

tail -F /var/log/somelogfile | while read line; do
  if echo "$line" | grep -q '[Ss]ome.regex'; then
    # do your stuff
  fi
done

Note that tail -F will continue to follow a log file even if it gets rotated by newsyslog or logrotate. You want to use this instead of the lower-case tail -f. Check man tail for details.

That said, if you really do want to run a command periodically, the other answers provided can be turned into a short shell script:

#!/bin/sh
if [ -z "$2" ]; then
  echo "Usage: $0 SECONDS COMMAND" >&2
  exit 1
fi

SECONDS=$1
shift 1
while sleep $SECONDS; do
  clear
  $*
done
ghoti
  • 45,319
  • 8
  • 65
  • 104
  • Unfortunately this shell script doesn't work with bash expanded parameters such as: watch 2 cat * – Code Commander Aug 13 '13 at 00:00
  • 1
    @CodeCommander - The command line `watch 2 cat *` would expand parameters before running the script, so in a directory with files "foo" and "bar", you'd run `cat foo bar` every 2 seconds. What behaviour were you expecting? – ghoti Aug 19 '13 at 14:33
  • @ghoti I'm merely pointing out that the behavior is different from the watch command on Ubuntu. The Ubuntu version apparently runs the command every two seconds (expanding the parameters each time it is run), this script has the parameters expanded before it is run, and then runs using the same parameters every two seconds. So if you want to watch files in a directory where files are being added and removed this script doesn't help. It is useful when you don't use expanded parameters though. :) – Code Commander Aug 19 '13 at 16:39
  • @CodeCommander, I think you are mistaken. Bash (like sh) expands `*` without passing it to the command you run. Try running: `mkdir /tmp/zz; touch /tmp/zz/foo; watch -n 2 ls -l /tmp/zz/*` in one window. While that's running, you can `touch /tmp/zz/bar` in another window. See if your "watch" sees the change, in the first window. I don't think it will. It doesn't for me. This has nothing to do with Ubuntu vs OSX or Linux vs Unix, it's the behaviour of `bash` itself. – ghoti Aug 19 '13 at 20:10
  • 1
    @ghoti, you are right. I was mistaken. Even with the Linux watch command you need to use quotes: `watch "ls -l /tmp/zz/*"` but if you remember to use quotes it works with your script as well. :) – Code Commander Aug 27 '13 at 16:05
12

I am going with the answer from here:

bash -c 'while [ 0 ]; do <your command>; sleep 5; done'

But you're really better off installing watch as this isn't very clean...

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Marvin Pinto
  • 30,138
  • 7
  • 37
  • 54
10

If watch doesn't want to install via

brew install watch

There is another similar/copy version that installed and worked perfectly for me

brew install visionmedia-watch

https://github.com/tj/watch

A_funs
  • 1,228
  • 2
  • 19
  • 31
9

Or, in your ~/.bashrc file:

function watch {
    while :; do clear; date; echo; $@; sleep 2; done
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jon
  • 91
  • 1
  • 1
6

To prevent flickering when your main command takes perceivable time to complete, you can capture the output and only clear screen when it's done.

function watch {while :; do a=$($@); clear; echo "$(date)\n\n$a"; sleep 1;  done}

Then use it by:

watch istats
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
tungv
  • 107
  • 1
  • 9
  • Should be `echo -e` instead of just `echo`, to render those line breaks correctly – jez Jun 22 '20 at 17:04
  • Thanks to both of you guys, this is a great answer for client machines that restrict the install of package managers like Brew. – Brian Topping Jun 29 '20 at 21:27
  • In fact you can reduce flickering further by executing your command, then executing 'date' and capturing that o/p, and *then* clearing... – Brian Agnew Oct 26 '20 at 12:32
2

Try this:

#!/bin/bash
# usage: watch [-n integer] COMMAND

case $# in
    0)
        echo "Usage $0 [-n int] COMMAND"
        ;;
    *)      
        sleep=2;
        ;;
esac    

if [ "$1" == "-n" ]; then
    sleep=$2
    shift; shift
fi


while :; 
    do 
    clear; 
    echo "$(date) every ${sleep}s $@"; echo 
    $@; 
    sleep $sleep; 
done
AstroCB
  • 12,337
  • 20
  • 57
  • 73
1

Here's a slightly changed version of this answer that:

  • checks for valid args
  • shows a date and duration title at the top
  • moves the "duration" argument to be the 1st argument, so complex commands can be easily passed as the remaining arguments.

To use it:

  • Save this to ~/bin/watch
  • execute chmod 700 ~/bin/watch in a terminal to make it executable.
  • try it by running watch 1 echo "hi there"

~/bin/watch

#!/bin/bash

function show_help()
{
  echo ""
  echo "usage: watch [sleep duration in seconds] [command]"
  echo ""
  echo "e.g. To cat a file every second, run the following"
  echo ""
  echo "     watch 1 cat /tmp/it.txt" 
  exit;
}

function show_help_if_required()
{
  if [ "$1" == "help" ]
  then
      show_help
  fi
  if [ -z "$1" ]
    then
      show_help
  fi
}

function require_numeric_value()
{
  REG_EX='^[0-9]+$'
  if ! [[ $1 =~ $REG_EX ]] ; then
    show_help
  fi
}

show_help_if_required $1
require_numeric_value $1

DURATION=$1
shift

while :; do 
  clear
  echo "Updating every $DURATION seconds. Last updated $(date)"
  bash -c "$*"
  sleep $DURATION
done
Community
  • 1
  • 1
Brad Parks
  • 66,836
  • 64
  • 257
  • 336
1

Use the Nix package manager!

Install Nix, and then do nix-env -iA nixpkgs.watch and it should be available for use after the completing the install instructions (including sourcing . "$HOME/.nix-profile/etc/profile.d/nix.sh" in your shell).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
galva
  • 1,253
  • 10
  • 17
0

The watch command that's available on Linux does not exist on macOS. If you don't want to use brew you can add this bash function to your shell profile.

# execute commands at a specified interval of seconds
function watch.command {
  # USAGE: watch.commands [seconds] [commands...]
  # EXAMPLE: watch.command 5 date
  # EXAMPLE: watch.command 5 date echo 'ls -l' echo 'ps | grep "kubectl\\\|node\\\|npm\\\|puma"'
  # EXAMPLE: watch.command 5 'date; echo; ls -l; echo; ps | grep "kubectl\\\|node\\\|npm\\\|puma"' echo date 'echo; ls -1'
  local cmds=()
  for arg in "${@:2}"; do
    echo $arg | sed 's/; /;/g' | tr \; \\n | while read cmd; do
      cmds+=($cmd)
    done
  done
  while true; do
    clear
    for cmd in $cmds; do
      eval $cmd
    done
    sleep $1
  done
}

https://gist.github.com/Gerst20051/99c1cf570a2d0d59f09339a806732fd3

Andrew
  • 3,733
  • 1
  • 35
  • 36
0

No blinky blinky

newwatch(){while :; do "$@" > /tmp/newwatch; clear; cat /tmp/newwatch; sleep 1; done}
Jeremiah
  • 61
  • 1
  • 2