OS X Bash, 'watch' command - macos

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?

With Homebrew installed:
brew install watch

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

Use MacPorts:
$ sudo port install watch

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

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

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...

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

Or, in your ~/.bashrc file:
function watch {
while :; do clear; date; echo; $#; sleep 2; done
}

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

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

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

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).

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

Related

How to change the terminal title to currently running process?

I know how to change the Terminal Window title. What I am trying to find out is how to make bash not zsh write out the currently running process so if I say do
$ ls -lF
I would get something like this for the title
/home/me/curerntFolder (ls -lF)
Getting the last executed command would be too late since the command has executed already, so it won't set the title with the command that was executed.
In addition to #markp-fuso's answer, here's how I did it to make it work with Starship.
function set_win_title() {
local cmd=" ($#)"
if [[ "$cmd" == " (starship_precmd)" || "$cmd" == " ()" ]]
then
cmd=""
fi
if [[ $PWD == $HOME ]]
then
if [[ $SSH_TTY ]]
then
echo -ne "\033]0; 🏛️ # $HOSTNAME ~$cmd\a" < /dev/null
else
echo -ne "\033]0; 🏠 ~$cmd\a" < /dev/null
fi
else
BASEPWD=$(basename "$PWD")
if [[ $SSH_TTY ]]
then
echo -ne "\033]0; 🌩️ $BASEPWD # $HOSTNAME $cmd\a" < /dev/null
else
echo -ne "\033]0; 📁 $BASEPWD $cmd\a" < /dev/null
fi
fi
}
starship_precmd_user_func="set_win_title"
eval "$(starship init bash)"
trap "$(trap -p DEBUG | awk -F"'" '{print $2}');set_win_title \${BASH_COMMAND}" DEBUG
Note this differs from the Custom pre-prompt and pre-execution Commands in Bash instructions in that the trap is set after starship init. Which I have noted in a bug.
UPDATE: my previous answer (below) displays the previous command in the title bar.
Ignoring everything from my previous answer and starting from scratch:
trap 'echo -ne "\033]0;${PWD}: (${BASH_COMMAND})\007"' DEBUG
Running the following at the command prompt:
$ sleep 10
The window title bar changes to /my/current/directory: (sleep 10) while the sleep 10 is running.
Running either of these:
$ sleep 1; sleep 2; sleep 3
$ { sleep 1; sleep2; sleep 3; }
The title bar changes as each sleep command is invoked.
Running this:
$ ( sleep 1; sleep 2; sleep 3 )
The title bar does not change (the trap does not apply within a subprocess call).
One last one:
$ echo $(sleep 3; echo abc)
The title bar displays (echo $sleep 3; echo abc)).
previous answer
Adding to this answer:
store_command() {
declare -g last_command current_command
last_command=$current_command
current_command=$BASH_COMMAND
return 0
}
trap store_command DEBUG
PROMPT_COMMAND='echo -ne "\033]0;${PWD}: (${last_command})\007"'
Additional reading materials re: trap / DEBUG:
bash guide on traps
SO Q&A
You can combine setting the window title with setting the prompt.
Here's an example using bashs PROMPT_COMMAND:
tputps () {
echo -n '\['
tput "$#"
echo -n '\]'
}
prompt_builder () {
# Window title - operating system command (OSC) ESC + ]
echo -ne '\033]0;'"${USER}#${HOSTNAME}:$(dirs)"'\a' >&2
# username, green
tputps setaf 2
echo -n '\u'
# directory, orange
tputps setaf 208
echo -n ' \w'
tputps sgr0 0
}
prompt_cmd () {
PS1="$(prompt_builder) \$ "
}
export PROMPT_COMMAND=prompt_cmd
For Linux OS Adding following function in bashrc file
Following Steps
Open bashrc file
vi ~/.bashrc
Write a function in bashrc file
function set-title() {
if [[ -z "$ORIG" ]]; then
ORIG=$PS1
fi
TITLE="\[\e]2;$*\a\]"
PS1=${ORIG}${TITLE}
}
save the file
source ~/.bashrc
call function
set-title "tab1"
The easiest way to change the title of the terminal I could think of is to use echo in shell script
echo "\033]0;Your title \007"
And to change open a new tab with new title name is
meta-terminal--tab-t"Your title"

How to detect a non-rolling log file and pattern match in a shell script which is using tail, while, read, and?

I am monitoring a log file and if PATTERN didn't appear in it within THRESHOLD seconds, the script should print "error", otherwise, it should print "clear". The script is working fine, but only if the log is rolling.
I've tried reading 'timeout' but didn't work.
log_file=/tmp/app.log
threshold=120
tail -Fn0 ${log_file} | \
while read line ; do
echo "${line}" | awk '/PATTERN/ { system("touch pattern.tmp") }'
code to calculate how long ago pattern.tmp touched and same is assigned to DIFF
if [ ${diff} -gt ${threshold} ]; then
echo "Error"
else
echo "Clear"
done
It is working as expected only when there is 'any' line printed in the app.log.
If the application got hung for any reason and the log stopped rolling, there won't be any output by the script.
Is there a way to detect the 'no output' of tail and do some command at that time?
It looks like the problem you're having is that the timing calculations inside your while loop never get a chance to run when read is blocking on input. In that case, you can pipe the tail output into a while true loop, inside of which you can do if read -t $timeout:
log_file=/tmp/app.log
threshold=120
timeout=10
tail -Fn0 "$log_file" | while true; do
if read -t $timeout line; then
echo "${line}" | awk '/PATTERN/ { system("touch pattern.tmp") }'
fi
# code to calculate how long ago pattern.tmp touched and same is assigned to diff
if [ ${diff} -gt ${threshold} ]; then
echo "Error"
else
echo "Clear"
fi
done
As Ed Morton pointed out, all caps variable names are not a good idea in bash scripts, so I used lowercase variable names.
How about something simple like:
sleep "$threshold"
grep -q 'PATTERN' "$log_file" && { echo "Clear"; exit; }
echo "Error"
If that's not all you need then edit your question to clarify your requirements. Don't use all upper case for non exported shell variable names btw - google it.
To build further on your idea, it might be beneficial to run the awk part in the background and a continuous loop to do the checking.
#!/usr/bin/env bash
log_file="log.txt"
# threshold in seconds
threshold=10
# run the following process in the background
stdbuf -oL tail -f0n "$log_file" \
| awk '/PATTERN/{system("touch "pattern.tmp") }' &
while true; do
match=$(find . -type f -iname "pattern.tmp" -newermt "-${threshold} seconds")
if [[ -z "${match}" ]]; then
echo "Error"
else
echo "Clear"
fi
done
This looks to me like a watchdog timer. I've implemented something like this by forcing a background process to update my log, so I don't have to worry about read -t. Here's a working example:
#!/usr/bin/env bash
threshold=10
grain=2
errorstate=0
while sleep "$grain"; do
date '+[%F %T] watchdog timer' >> log
done &
trap "kill -HUP $!" 0 HUP INT QUIT TRAP ABRT TERM
printf -v lastseen '%(%s)T'
tail -F log | while read line; do
printf -v now '%(%s)T'
if (( now - lastseen > threshold )); then
echo "ERROR"
errorstate=1
else
if (( errorstate )); then
echo "Recovered, yay"
errorstate=0
fi
fi
if [[ $line =~ .*PATTERN.* ]]; then
lastseen=$now
fi
done
Run this in one window, wait $threshold seconds for it to trigger, then in another window echo PATTERN >> log to see the recovery.
While this can be made as granular as you like (I've set it to 2 seconds in the example), it does pollute your log file.
Oh, and note that printf '%(%s)T' format requires bash version 4 or above.

Check if file exists [BASH]

How do I check if file exists in bash?
When I try to do it like this:
FILE1="${#:$OPTIND:1}"
if [ ! -e "$FILE1" ]
then
echo "requested file doesn't exist" >&2
exit 1
elif
<more code follows>
I always get following output:
requested file doesn't exist
The program is used like this:
script.sh [-g] [-p] [-r FUNCTION_ID|-d FUNCTION_ID] FILE
Any ideas please?
I will be glad for any help.
P.S. I wish I could show the entire file without the risk of being fired from school for having a duplicate. If there is a private method of communication I will happily oblige.
My mistake. Fas forcing a binary file into a wrong place. Thanks for everyone's help.
Little trick to debugging problems like this. Add these lines to the top of your script:
export PS4="\$LINENO: "
set -xv
The set -xv will print out each line before it is executed, and then the line once the shell interpolates variables, etc. The $PS4 is the prompt used by set -xv. This will print the line number of the shell script as it executes. You'll be able to follow what is going on and where you may have problems.
Here's an example of a test script:
#! /bin/bash
export PS4="\$LINENO: "
set -xv
FILE1="${#:$OPTIND:1}" # Line 6
if [ ! -e "$FILE1" ] # Line 7
then
echo "requested file doesn't exist" >&2
exit 1
else
echo "Found File $FILE1" # Line 12
fi
And here's what I get when I run it:
$ ./test.sh .profile
FILE1="${#:$OPTIND:1}"
6: FILE1=.profile
if [ ! -e "$FILE1" ]
then
echo "requested file doesn't exist" >&2
exit 1
else
echo "Found File $FILE1"
fi
7: [ ! -e .profile ]
12: echo 'Found File .profile'
Found File .profile
Here, I can see that I set $FILE1 to .profile, and that my script understood that ${#:$OPTIND:1}. The best thing about this is that it works on all shells down to the original Bourne shell. That means if you aren't running Bash as you think you might be, you'll see where your script is failing, and maybe fix the issue.
I suspect you might not be running your script in Bash. Did you put #! /bin/bash on the top?
script.sh [-g] [-p] [-r FUNCTION_ID|-d FUNCTION_ID] FILE
You may want to use getopts to parse your parameters:
#! /bin/bash
USAGE=" Usage:
script.sh [-g] [-p] [-r FUNCTION_ID|-d FUNCTION_ID] FILE
"
while getopts gpr:d: option
do
case $option in
g) g_opt=1;;
p) p_opt=1;;
r) rfunction_id="$OPTARG";;
d) dfunction_id="$OPTARG";;
[?])
echo "Invalid Usage" 1>&2
echo "$USAGE" 1>&2
exit 2
;;
esac
done
if [[ -n $rfunction_id && -n $dfunction_id ]]
then
echo "Invalid Usage: You can't specify both -r and -d" 1>&2
echo "$USAGE" >2&
exit 2
fi
shift $(($OPTIND - 1))
[[ -n $g_opt ]] && echo "-g was set"
[[ -n $p_opt ]] && echo "-p was set"
[[ -n $rfunction_id ]] && echo "-r was set to $rfunction_id"
[[ -n $dfunction_id ]] && echo "-d was set to $dfunction_id"
[[ -n $1 ]] && echo "File is $1"
To (recap) and add to #DavidW.'s excellent answer:
Check the shebang line (first line) of your script to ensure that it's executed by bash: is it #!/bin/bash or #!/usr/bin/env bash?
Inspect your script file for hidden control characters (such as \r) that can result in unexpected behavior; run cat -v scriptFile | fgrep ^ - it should produce NO output; if the file does contain \r chars., they would show as ^M.
To remove the \r instances (more accurately, to convert Windows-style \r\n newline sequences to Unix \n-only sequences), you can use dos2unix file to convert in place; if you don't have this utility, you can use sed 's/'$'\r''$//' file > outfile (CAVEAT: use a DIFFERENT output file, otherwise you'll destroy your input file); to remove all \r instances (even if not followed by \n), use tr -d '\r' < file > outfile (CAVEAT: use a DIFFERENT output file, otherwise you'll destroy your input file).
In addition to #DavidW.'s great debugging technique, you can add the following to visually inspect all arguments passed to your script:
i=0; for a; do echo "\$$((i+=1))=[$a]"; done
(The purpose of enclosing the value in [...] (for example), is to see the exact boundaries of the values.)
This will yield something like:
$1=[-g]
$2=[input.txt]
...
Note, though, that nothing at all is printed if no arguments were passed.
Try to print FILE1 to see if it has the value you want, if it is not the problem, here is a simple script (site below):
#!/bin/bash
file="${#:$OPTIND:1}"
if [ -f "$file" ]
then
echo "$file found."
else
echo "$file not found."
fi
http://www.cyberciti.biz/faq/unix-linux-test-existence-of-file-in-bash/
Instead of plucking an item out of "$#" in a tricky way, why don't you shift off the args you've processed with getopts:
while getopts ...
done
shift $(( OPTIND - 1 ))
FILE1=$1

Generate warning when multiple executables in path when executing command in bash

Say I have:
>which -a foo
/bin/foo
/usr/bin/foo
I want something like:
>foo
Warning: multiple foo in PATH
... foo gets executed ...
This functionality would have saved me really really lots of time today.
I should have guessed this is happening earlier but the problem was unclear
to me at the beggining and I started to dig in quite opposite direction.
Well, you can do it, but it is not so easy as you may think.
First, you need to create a function that will check
all directories in the PATH, and look there for the command you try to run.
Then you need to bind this function to the DEBUG trap of your current shell.
I wrote a small script that does that:
$ cat /tmp/1.sh
check_command()
{
n=0
DIRS=""
for i in $(echo $PATH| tr : " ")
do
if [ -x "$i/$1" ]
then
n=$[n+1]
DIRS="$DIRS $i"
fi
done
if [ "$n" -gt 1 ]
then
echo "Warning: there are multiple commands in different PATH directories: "$DIRS
fi
}
preexec () {
check_command $1
}
preexec_invoke_exec () {
[ -n "$COMP_LINE" ] && return # do nothing if completing
local this_command=`history 1 | sed -e "s/^[ ]*[0-9]*[ ]*//g"`;
preexec "$this_command"
}
trap 'preexec_invoke_exec' DEBUG
Example of usage:
$ . /tmp/1.sh
$ sudo cp /bin/date /usr/bin/test_it
$ sudo cp /bin/date /bin/test_it
$ test_it
Warning: there are multiple commands in different PATH directories: /usr/bin /bin
Wed Jul 11 15:14:43 CEST 2012
$
It is possible, though a bit trick to generalise. See my answer to https://unix.stackexchange.com/q/42579/20437 for the history and PROMPT_COMMAND magic. Your checkSanity function would look something like this:
checkSanity() {
cmd="${1%% *}" # strip everything but the first word
candidates=$(which -a $cmd | wc -l)
if (( candidates > 1 )); then
echo "Warning: multiple $cmd in PATH"
fi
}
But that will print the warning after the command finishes, not at the start. Use the DEBUG trap instead to get the wanted result:
trap 'checkSanity "$BASH_COMMAND"' DEBUG

shell scripting help cron job not executing

#!/bin/bash
#!/bin/sh
# Need help
__help() { echo "$0 [ stop|start ]" 1>&2; exit 1; }
# Not enough args to run properly
[ $# -ne 1 ] && __help
# See what we're called with
case "$1" in
start) # Start sniffer as root, under a different argv[0] and make it drop rights
s=$(/usr/local/sbin/tcpdump -n -nn -f -q -i lo | awk 'END {print NR}')
echo "$s" > eppps_$(/bin/date +'%Y%m%d%H%M')
;;
stop) # End run, first "friendly", then strict:
/usr/bin/pkill -15 -f /usr/local/sbin/tcpdump >/dev/null 2>&1|| { sleep 3s; /usr/bin/pkill -9 -f /usr/local/sbin/tc$
;;
*) # Superfluous but show we only accept these args
__help
;;
esac
exit 0
This code runs perfectly on manual testing. But when i couple it with cron it just doesn't do anything. No output file is created.
My cron entries for the script looks like
http://postimage.org/image/1pztgd6xw/
It looks like you are not setting the working directory, so you may need to give an absolute path for the output file

Resources