Any script to notify when there is no write activity in folder - macos

I am trying to come up with a nice and easy way of detecting when there has not been any write activity in a folder I'd like to watch.
Basically, what I'd like to have is something like this:
#!/bin/sh
# colored red text (error)
function errorMsg () {
echo '\033[0;31m'"$1"'\033[0m'
}
# check for folder to monitor argument
if [ "$#" -ne 1 ]
then
errorMsg "add experiment (folder) to monitor for activity!"
exit
fi
# time out time, 3 minutes
TIMEOUT_TIME=180
LAST_CHECKED=0
function refreshTimer () {
# when called, check seconds since epoch
CURRENT_TIME=date +%s
if [ CURRENT_TIME - LAST_CHECKED > TIMEOUT_TIME ]
then
echo "file write activity halted!" | mail -s "trouble!" "user#provider.ext"
fi
LAST_CHECKED=date +%s
}
# set last checked to now.
LAST_CHECKED=date +%s
# start monitoring for file changes, and update timer when new files are being written.
fswatch -r ${1} | refreshTimer
but all sorts of bash magic is required I presume, since fswatch is a background task and piping its output creates a subshell.
I would also be in need of some timer logic...
I was thinking something like a setTimeout of which the time argument keeps being added to when there IS activity, but I don't know how to write it all in one script.
Bash, Python, Ruby, anything that can be installed using homebrew on OSX is fine but the simpler the better (so I understand what is happening).

Try the following - note that it requires bash:
#!/usr/bin/env bash
# colored red text (error)
function errorMsg () {
printf '\033[0;31m%s\033[0m\n' "$*" >&2
}
# check for folder to monitor argument
if [[ $# -ne 1 ]]
then
errorMsg "add experiment (folder) to monitor for activity!"
exit 2
fi
# time-out: 3 minutes
TIMEOUT_TIME=180
# Read fswatch output as long as it is
# within the timeout value; every change reported
# resets the timer.
while IFS= read -t $TIMEOUT_TIME -d '' -r file; do
echo "changed: [$file]"
done < <(fswatch -r -0 "${1}")
# Getting here means that read timed out.
echo "file write activity halted!" | mail -s "trouble!" "user#provider.ext"
fswatch indefinitely outputs lines to stdout, which must be read line by line to take timely action on new output.
fswatch -0 terminates lines with NULs (zero bytes), which read then reads on be one by setting the line delimiter (separator) to an empty string (-d '').
< <(fswatch -r -0 "${1}") provides input via stdin < to the while loop, where read consumes the stdin input one NUL-terminated line at a time.
<(fswatch -r -0 "${1}") is a process substitution that forms an "ad-hoc file" (technically, a FIFO or named file descriptor) from the output produced by fswatch -r -0 "${1}" (which watches folder ${1}'s subtree (-r) for file changes, and reports each terminated with NUL (-0)).
Since the fswatch command runs indefinitely, the "ad-hoc file" will continue to provide input, although typically only intermittently, depending on filesystem activity.
Whenever the read command receives a new line within the timeout period (-t $TIMEOUT_TIME), it terminates successfully (exit code 0), causing the body of the loop to be executed and then read to be invoked again.
Thus, whenever a line has been received, the timeout period is effectively reset, because the new read invocation starts over with the timeout period.
By contrast, if the timeout period expires before another line is received, read terminates unsuccessfully - with a nonzero exit code indicating failure, which causes the while loop to terminate.
Thus, the code after the loop is only reached when the read command times out.
As for your original code:
Note: Some of the problems discussed could have been detected with the help of shellecheck.net
echo '\033[0;31m'"$1"'\033[0m'
printf is the better choice when it comes to interpreting escape sequences, since echo's behavior varies across shells and platforms; for instance, running your script with bash will not interpret them (unless you also add the -e option).
function refreshTimer ()
The function syntax is nonstandard (not POSIX-compliant), so you shouldn't use it with a sh shebang line (that's what chepner meant in his comment). On OSX, you can get away with it, because bash acts as sh and most bashisms are still available when run as sh, but it wouldn't work on other systems. If you know you'll be running with bash anyway, it's better to use a bash shebang line.
CURRENT_TIME=date +%s
You can't assign the output from commands to a variable by simply placing the command as is on the RHS of the assignment; instead, you need a command substitution; in the case at hand: CURRENT_TIME=$(date +%s) (the older syntax with backticks - CURRENT_TIME=`date +%s` - works too, but has disadvantages).
[ CURRENT_TIME - LAST_CHECKED > TIMEOUT_TIME ]
> in [ ... ] and bash's [[ ... ]] conditionals is lexical comparison and the variable names must be $-prefixed; you'd have to use an arithmetic conditional with your syntax: (( CURRENT_TIME - LAST_CHECKED > TIMEOUT_TIME ))
As an aside, it's better better not to use all-uppercase variable names in shell programming.
fswatch -r ${1} | refreshTimer
refreshTimer will not get called until the pipe fills up (the timing of which you won't be able to predict), because you make no attempt to read line by line.
Even if you fix that problem, the variables inside refreshTimer won't be preserved, because refreshTimer runs in a new subshell every time, due to use of a pipeline (|). In bash, this problem is frequently worked around by providing input via a process substitution (<(...)), which you can see in action in my code above.

Related

Waiting for a file to be created in Bash

I need to create a bash script to wait for a file to be created. The script will use sleep command inside a while loop to periodically check on a file every 10 seconds. Print out a message while waiting. Display the content of the file once the file is created. Below is what I have tried to implement and it obviously does not work. At this point, I'm not entirely sure how to proceed.
#!/bin/bash
let file=$1
while '( -f ! /tmp/$1)'
do
sleep 10
echo "still waiting"
done
echo "Content of the file $1:"
The problem here is with the test, not the sleep (as the original question hypothesized). The smallest possible fix might look as follows:
while ! test -f "/tmp/$1"; do
sleep 10
echo "Still waiting"
done
Keep in mind the syntax for a while loop:
while: while COMMANDS; do COMMANDS; done
Expand and execute COMMANDS as long as the final command in the
`while' COMMANDS has an exit status of zero.
That is to say, the first argument given to while, expanding the loop, is a command; it needs to follow the same syntax rules as any other shell command.
-f is valid as an argument to test -- a command which is also accessible under the name [, requiring a ] as the last argument when used in that name -- but it's not valid as a command in and of itself -- and when passed as part of a string, it's not even a shell word that could be parsed as an individual command name or argument.
When you run '( -f ! /tmp/$1)' as a command, inside quotes, the shell is looking for an actual command with exactly that name (including spaces). You probably don't have a file named '/usr/bin/( -f ! /tmp/$1)' in your PATH or any other command by that name found, so it'll always fail -- exiting the while loop immediately.
By the way -- if you're willing to make your code OS-specific, there are approaches other than using sleep to wait for a file to exist. Consider, for instance, inotifywait, from the inotify-tools package:
while ! test -f "/tmp/$1"; do
echo "waiting for a change to the contents of /tmp" >&2
inotifywait --timeout 10 --event create /tmp >/dev/null || {
(( $? == 2 )) && continue ## inotify exit status 2 means timeout expired
echo "unable to sleep with inotifywait; doing unconditional 10-second loop" >&2
sleep 10
}
done
The benefit of an inotify-based interface is that it returns immediately upon a filesystem change, and doesn't incur polling overhead (which can be particularly significant if it prevents a system from sleeping).
By the way, some practice notes:
Quoting expansions in filenames (ie. "/tmp/$1") prevents names with spaces or wildcards from being expanded into multiple distinct arguments.
Using >&2 on echo commands meant to log for human consumption keeps stderr available for programmatic consumption
let is used for math, not general-purpose assignments. If you want to use "$file", nothing wrong with that -- but the assignment should just be file=$1, with no preceding let.

How can I easily log some specific command line commands into a file?

I often perform configuration changes using single line commands on Mac OS, Linux or even Windows and I want to easily log them in a file, so I can replay if I have to reconfigure the machine again.
Please not that I want to do these only for some commands, so the shell history is of not use.
Ideally I would like to be able to use some kind of shell extension that logs some of the commands.
As you know if you start your bash command with a space, this command is not logged into the history.
What if I can have another prefix that would do the opposite? Is there something there that can be used for this? A solution for bash would be more than enough and if there is an already existing solution it would much better than me writing a new one.
You could do your logging in PROMPT_COMMAND, extracting the specific commands from shell history and writing them to a file.
Something like:
log () {
last_command="$(history -p \!\!)"
if [[ $last_command == " "* ]] # save commands starting with *two* spaces
then
printf "%s\n" "$last_command" >> ~/special.log
fi
}
PROMPT_COMMAND="log; $PROMPT_COMMAND"
This has problems:
PROMPT_COMMAND is run each time the prompt is printed. Just pressing Enter multiple times could cause a command to be logged multiple times.
Marking with two spaces would, of course, need you to remove ignorespace or ignoreboth from HISTCONTROL so that commands starting spaces are logged at all.
AFAICT, history is updated when the next command is read, so the command is logged after the next command returns to the prompt, since that's when the correct history is available in PROMPT_COMMAND.
All this would be easier in zsh, with a preexec hook:
preexec () {
if [[ $1 == " "* ]]
then
printf "%s\n" "$1" >> ~/special.log
fi
}
The preexec function automatically gets the command as the first argument if history is enabled, saving us a deal of trouble. It is run when the command has been read, but before it begins execution, so the timing is perfect. From the documentation:
preexec
Executed just after a command has been read and is about to be
executed. If the history mechanism is active (regardless of whether
the line was discarded from the history buffer), the string that the
user typed is passed as the first argument, otherwise it is an empty
string. The actual command that will be executed (including expanded
aliases) is passed in two different forms: the second argument is a
single-line, size-limited version of the command (with things like
function bodies elided); the third argument contains the full text
that is being executed.
$ ls
$ echo foo | echo bar
bar
$ cat ~/special.log
ls
echo foo | echo bar
A function in .bashrc can be used like a prefix:
log_this_command () {
echo "$#" >> ~/a_log_file # log the command to file
"$#" # and run the command itself
}
Caveat: this only logs expanded arguments, rather than the raw input.
Source function with the same name function screencapture {echo "used parms: $#"; command screencapture $#}
appending to log file function screencapture {echo "$(date) screencapture " $# >> ~/log.txt; command screencapture $#}
as one runs screencapture command, log entry is created and command executes as uninterfered
you could automate in creating these functions, if the list of them is like .... all of them

How can I make a bash command run periodically?

I want to execute a script and have it run a command every x minutes.
Also any general advice on any resources for learning bash scripting could be really cool. I use Linux for my personal development work, so bash scripts are not totally foreign to me, I just haven't written any of my own from scratch.
If you want to run a command periodically, there's 3 ways :
using the crontab command ex. * * * * * command (run every minutes)
using a loop like : while true; do ./my_script.sh; sleep 60; done (not precise)
using systemd timer
See cron
Some pointers for best bash scripting practices :
http://mywiki.wooledge.org/BashFAQ
Guide: http://mywiki.wooledge.org/BashGuide
ref: http://www.gnu.org/software/bash/manual/bash.html
http://wiki.bash-hackers.org/
USE MORE QUOTES!: http://www.grymoire.com/Unix/Quote.html
Scripts and more: http://www.shelldorado.com/
In addition to #sputnick's answer, there is also watch. From the man page:
Execute a program periodically, showing output full screen
By default this is every 2 seconds. watch is useful for tailing logs, for example.
macOS users: here's a partial implementation of the GNU watch command (as of version 0.3.0) for interactive periodic invocations for primarily visual inspection:
It is syntax-compatible with the GNU version and fails with a specific error message if an unimplemented feature is used.
Notable limitations:
The output is not limited to one screenful.
Displaying output differences is not supported.
Using precise timing is not supported.
Colored output is always passed through (--color is implied).
Also implements a few non-standard features, such as waiting for success (-E) to complement waiting for error (-e) and showing the time of day of the last invocation as well as the total time elapsed so far.
Run watch -h for details.
Examples:
watch -n 1 ls # list current dir every second
watch -e 'ls *.lockfile' # list lock files and exit once none exist anymore.
Source code (paste into a script file named watch, make it executable, and place in a directory in your $PATH; note that syntax highlighting here is broken, but the code works):
#!/usr/bin/env bash
THIS_NAME=$(basename "$BASH_SOURCE")
VERSION='0.1'
# Helper function for exiting with error message due to runtime error.
# die [errMsg [exitCode]]
# Default error message states context and indicates that execution is aborted. Default exit code is 1.
# Prefix for context is always prepended.
# Note: An error message is *always* printed; if you just want to exit with a specific code silently, use `exit n` directly.
die() {
echo "$THIS_NAME: ERROR: ${1:-"ABORTING due to unexpected error."}" 1>&2
exit ${2:-1} # Note: If the argument is non-numeric, the shell prints a warning and uses exit code 255.
}
# Helper function for exiting with error message due to invalid parameters.
# dieSyntax [errMsg]
# Default error message is provided, as is prefix and suffix; exit code is always 2.
dieSyntax() {
echo "$THIS_NAME: PARAMETER ERROR: ${1:-"Invalid parameter(s) specified."} Use -h for help." 1>&2
exit 2
}
# Get the elapsed time since the specified epoch time in format HH:MM:SS.
# Granularity: whole seconds.
# Example:
# tsStart=$(date +'%s')
# ...
# getElapsedTime $tsStart
getElapsedTime() {
date -j -u -f '%s' $(( $(date +'%s') - $1 )) +'%H:%M:%S'
}
# Command-line help.
if [[ "$1" == '--help' || "$1" == '-h' ]]; then
cat <<EOF
SYNOPSIS
$THIS_NAME [-n seconds] [opts] cmd [arg ...]
DESCRIPTION
Executes a command periodically and displays its output for visual inspection.
NOTE: This is a PARTIAL implementation of the GNU \`watch\` command, for OS X.
Notably, the output is not limited to one screenful, and displaying
output differences and using precise timing are not supported.
Also, colored output is always passed through (--color is implied).
Unimplemented features are marked as [NOT IMPLEMENTED] below.
Conversely, features specific to this implementation are marked as [NONSTD].
Reference version is GNU watch 0.3.0.
CMD may be a simple command with separately specified
arguments, if any, or a single string containing one or more
;-separated commands (including arguments) - in the former case the command
is directly executed by bash, in the latter the string is passed to \`bash -c\`.
Note that GNU watch uses sh, not bash.
To use \`exec\` instead, specify -x (see below).
By default, CMD is re-invoked indefinitely; terminate with ^-C or
exit based on conditions:
-e, --errexit
exits once CMD indicates an error, i.e., returns a non-zero exit code.
-E, --okexit [NONSTD]
is the inverse of -e: runs until CMD returns exit code 0.
By default, all output is passed through; the following options modify this
behavior; note that suppressing output only relates to CMD's output, not the
messages output by this utility itself:
-q, --quiet [NONSTD]
suppresses stdout output from the command invoked;
-Q, --quiet-both [NONSTD]
suppresses both stdout and stderr output.
-l, --list [NONSTD]
list-style display; i.e., suppresses clearing of the screen
before every invocation of CMD.
-n secs, --interval secs
interval in seconds between the end of the previous invocation of CMD
and the next invocation - 2 seconds by default, fractional values permitted;
thus, the interval between successive invocations is the specified interval
*plus* the last CMD's invocation's execution duration.
-x, --exec
uses \`exec\` rather than bash to execute CMD; this requires
arguments to be passed to CMD to be specified as separate arguments
to this utility and prevents any shell expansions of these arguments
at invocation time.
-t, --no-title
suppresses the default title (header) that displays the interval,
and (NONSTD) a time stamp, the time elapsed so far, and the command executed.
-b, --beep
beeps on error (bell signal), i.e., when CMD reports a non-zero exit code.
-c, --color
IMPLIED AND ALWAYS ON: colored command output is invariably passed through.
-p, --precise [NOT IMPLEMENTED]
-d, --difference [NOT IMPLEMENTED]
EXAMPLES
# List files in home folder every second.
$THIS_NAME -n 1 ls ~
# Wait until all *.lockfile files disappear from the current dir, checking every 2 secs.
$THIS_NAME -e 'ls *.lockfile'
EOF
exit 0
fi
# Make sure that we're running on OSX.
[[ $(uname) == 'Darwin' ]] || die "This script is designed to run on OS X only."
# Preprocess parameters: expand compressed options to individual options; e.g., '-ab' to '-a -b'
params=() decompressed=0 argsReached=0
for p in "$#"; do
if [[ $argsReached -eq 0 && $p =~ ^-[a-zA-Z0-9]+$ ]]; then # compressed options?
decompressed=1
params+=(${p:0:2})
for (( i = 2; i < ${#p}; i++ )); do
params+=("-${p:$i:1}")
done
else
(( argsReached && ! decompressed )) && break
[[ $p == '--' || ${p:0:1} != '-' ]] && argsReached=1
params+=("$p")
fi
done
(( decompressed )) && set -- "${params[#]}"; unset params decompressed argsReached p # Replace "$#" with the expanded parameter set.
# Option-parameters loop.
interval=2 # default interval
runUntilFailure=0
runUntilSuccess=0
quietStdOut=0
quietStdOutAndStdErr=0
dontClear=0
noHeader=0
beepOnErr=0
useExec=0
while (( $# )); do
case "$1" in
--) # Explicit end-of-options marker.
shift # Move to next param and proceed with data-parameter analysis below.
break
;;
-p|--precise|-d|--differences|--differences=*)
dieSyntax "Sadly, option $1 is NOT IMPLEMENTED."
;;
-v|--version)
echo "$VERSION"; exit 0
;;
-x|--exec)
useExec=1
;;
-c|--color)
# a no-op: unlike the GNU version, we always - and invariably - pass color codes through.
;;
-b|--beep)
beepOnErr=1
;;
-l|--list)
dontClear=1
;;
-e|--errexit)
runUntilFailure=1
;;
-E|--okexit)
runUntilSuccess=1
;;
-n|--interval)
shift; interval=$1;
errMsg="Please specify a positive number of seconds as the interval."
interval=$(bc <<<"$1") || dieSyntax "$errMsg"
(( 1 == $(bc <<<"$interval > 0") )) || dieSyntax "$errMsg"
[[ $interval == *.* ]] || interval+='.0'
;;
-t|--no-title)
noHeader=1
;;
-q|--quiet)
quietStdOut=1
;;
-Q|--quiet-both)
quietStdOutAndStdErr=1
;;
-?|--?*) # An unrecognized switch.
dieSyntax "Unrecognized option: '$1'. To force interpretation as non-option, precede with '--'."
;;
*) # 1st data parameter reached; proceed with *argument* analysis below.
break
;;
esac
shift
done
# Make sure we have at least a command name
[[ -n "$1" ]] || dieSyntax "Too few parameters specified."
# Suppress output streams, if requested.
# Duplicate stdout and stderr first.
# This allows us to produce output to stdout (>&3) and stderr (>&4) even when suppressed.
exec 3<&1 4<&2
if (( quietStdOutAndStdErr )); then
exec &> /dev/null
elif (( quietStdOut )); then
exec 1> /dev/null
fi
# Set an exit trap to ensure that the duplicated file descriptors are closed.
trap 'exec 3>&- 4>&-' EXIT
# Start loop with periodic invocation.
# Note: We use `eval` so that compound commands - e.g. 'ls; bash --version' - can be passed.
tsStart=$(date +'%s')
while :; do
(( dontClear )) || clear
(( noHeader )) || echo "Every ${interval}s. [$(date +'%H:%M:%S') - elapsed: $(getElapsedTime $tsStart)]: $#"$'\n' >&3
if (( useExec )); then
(exec "$#") # run in *subshell*, otherwise *this* script will be replaced by the process invoked
else
if [[ $* == *' '* ]]; then
# A single argument with interior spaces was provided -> we must use `bash -c` to evaluate it properly.
bash -c "$*"
else
# A command name only or a command name + arguments were specified as separate arguments -> let bash run it directly.
"$#"
fi
fi
ec=$?
(( ec != 0 && beepOnErr )) && printf '\a'
(( ec == 0 && runUntilSuccess )) && { echo $'\n'"[$(date +'%H:%M:%S') - elapsed: $(getElapsedTime $tsStart)] Exiting as requested: exit code 0 reported." >&3; exit 0; }
(( ec != 0 && runUntilFailure )) && { echo $'\n'"[$(date +'%H:%M:%S') - elapsed: $(getElapsedTime $tsStart)] Exiting as requested: non-zero exit code ($ec) reported." >&3; exit 0; }
sleep $interval
done
If you need a command to run periodically so that you can monitor its output, use watch [options] command. For example, to monitor free memory, run:
watch -n 1 free -m
    the -n 1 option sets update interval to 1 second (default is 2 seconds).
    Check man watch or the online manual for details.
If you need to monitor changes in one or multiple files (typically, logs), tail is your command of choice, for example:
# monitor one log file
tail -f /path/to/logs/file.log
# monitor multiple log files concurrently
tail -f $(ls /path/to/logs/*.log)
    the -f (for “follow”) option tells tail to output new content as the file grows.
    Check man tail or the online manual for details.
I want to execute the script and have it run a command every {time interval}
cron (https://en.wikipedia.org/wiki/Cron) was designed for this purpose. If you run man cron or man crontab you will find instructions for how to use it.
any general advice on any resources for learning bash scripting could be really cool. I use Linux for my personal development work, so bash scripts are not totally foreign to me, I just haven't written any of my own from scratch.
If you are comfortable working with bash, I recommend reading through the bash manpage first (man bash) -- there are lots of cool tidbits.
Avoiding Time Drift
Here's what I do to remove the time it takes for the command to run and still stay on schedule:
#One-liner to execute a command every 600 seconds avoiding time drift
#Runs the command at each multiple of :10 minutes
while sleep $(echo 600-`date "+%s"`%600 | bc); do ls; done
This will drift off by no more than one second. Then it will snap back in sync with the clock. If you need something with less than 1 second drift and your sleep command supports floating point numbers, try adding including nanoseconds in the calculation like this
while sleep $(echo 6-`date "+%s.%N"`%6 | bc); do date '+%FT%T.%N'; done
Here is my solution to reduce drift from loop payload run time.
tpid=0
while true; do
wait ${tpid}
sleep 3 & tpid=$!
{ body...; }
done
There is some approximation to timer object approach, with sleep command executed parallel with all other commands, including even true in condition check. I think it's most precise variant without drift cound using date command.
There could be true timer object bash function, implementing timer event by just 'echo' call, then piped to loop with read cmd, like this:
timer | { while read ev; do...; done; }
I was faced with this challenge recently. I wanted a way to execute a piece of the script every hour within the bash script file that runs on crontab every 5 minutes without having to use sleep or rely fully on crontab. if the TIME_INTERVAL is not met, the script will fail at the first condition. If the TIME_INTERVAL is met, however the TIME_CONDITION is not met, the script will fail at second condition. The below script does work hand-in-hand with crontab - adjust according.
NOTE: touch -m "$0" - This will change modification timestamp of the bash script file. You will have to create a separate file for storing the last script run time, if you don't want to change the modification timestamp of the bash script file.
CURRENT_TIME=$(date "+%s")
LAST_SCRIPT_RUN_TIME=$(date -r "$0" "+%s")
TIME_INTERVAL='3600'
START_TIME=$(date -d '07:00:00' "+%s")
END_TIME=$(date -d '16:59:59' "+%s")
TIME_DIFFERENCE=$((${CURRENT_TIME} - ${LAST_SCRIPT_RUN_TIME}))
TIME_CONDITION=$((${START_TIME} <= ${CURRENT_TIME} && ${CURRENT_TIME} <= $END_TIME}))
if [[ "$TIME_DIFFERENCE" -le "$TIME_INTERVAL" ]];
then
>&2 echo "[ERROR] FAILED - script failed to run because of time conditions"
elif [[ "$TIME_CONDITION" = '0' ]];
then
>&2 echo "[ERROR] FAILED - script failed to run because of time conditions"
elif [[ "$TIME_CONDITION" = '1' ]];
then
>&2 touch -m "$0"
>&2 echo "[INFO] RESULT - script ran successfully"
fi
Based on the answer from #david-h and its comment from #kvantour, I wrote a new version which comes with bash only, i.e. without bc.
export intervalsec=60
while sleep $(LANG=C now="$(date -Ins)"; printf "%0.0f.%09.0f" $((${intervalsec}-1-$(date "+%s" -d "$now")%${intervalsec})) $((1000000000-$(printf "%.0f" $(date "+%9N" -d "$now"))))); do \
date '+%FT%T.%N'; \
done
bash arithmetic operations ($(())) can only operate on integers. That's why the seconds and the nanoseconds have to be calculated separately.
printf is used to combine the two calculations together as well as to remove and to add leading zeros. Leading zeros from "+%N" must be removed because it gets interpreted as ocal instead of decimal and must be added again before merging into the floating point.
Because the concept needs to separate date commands, a date gets cached and reused to prevent flips.

Bash Script Monitor Program for Specific Output

So this is probably an easy question, but I am not much of a bash programmer and I haven't been able to figure this out.
We have a closed source program that calls a subprogram which runs until it exits, at which point the program will call the subprogram again. This repeats indefinitely.
Unfortunately the main program will sometimes spontaneously (and repeatedly) fail to call the subprogram after a random period of time. The eventual solution is to contact the original developers to get support, but in the meantime we need a quick hotfix for the issue.
I'm trying to write a bash script that will monitor the output of the program and when it sees a specific string, it will restart the machine (the program will run again automatically on boot). The bash script needs to pass all standard output through to the screen up until it sees the specific string. The program also needs to continue to handle user input.
I have tried the following with limited success:
./program1 | ./watcher.sh
watcher.sh is basically just the following:
while read line; do
echo $line
if [$line == "some string"]
then
#the reboot script works fine
./reboot.sh
fi
done
This seems to work OK, but leading whitespace is stripped on the echo statement, and the echo output hangs in the middle until subprogram exits, at which point the rest of the output is printed to the screen. Is there a better way to accomplish what I need to do?
Thanks in advance.
I would do something along the lines of:
stdbuf -o0 ./program1 | grep --line-buffered "some string" | (read && reboot)
you need to quote your $line variable, i.e. "$line" for all references *(except the read line bit).
Your program1 is probably the source of the 'paused' data. It needs to flush its output buffer. You probably don't have control of that, so
a. check if your system has unbuffer command available. If so try unbuffer cmd1 | watcher You may have to experiment with which cmd you wrap unbuffer with, maybe you whill have to do cmd1 | unbuffer watcher.
b. OR you can try wrapping watcher as a process-group, (I think that is the right terminology), i.e.
./program1 | { ./watcher.sh ; printf "\n" ; }
I hope this helps.
P.S. as you appear to be a new user, if you get an answer that helps you please remember to mark it as accepted, and/or give it a + (or -) as a useful answer.
use read's $REPLY variable, also I'd suggest using printf instead of echo
while read; do
printf "%s\n" "$REPLY"
# '[[' is Bash, quotes are not necessary
# use '[ "$REPLY" == "some string" ]' if in another shell
if [[ $REPLY == "some string" ]]
then
#the reboot script works fine
./reboot.sh
fi
done

BASH Variables with multiple commands and reentrant

I have a bash script that sources contents from another file. The contents of the other file are commands I would like to execute and compare the return value. Some of the commands are have multiple commands separated by either a semicolon (;) or by ampersands (&&) and I can't seem to make this work. To work on this, I created some test scripts as shown:
test.conf is the file being sourced by test
Example-1 (this works), My output is 2 seconds in difference
test.conf
CMD[1]="date"
test.sh
. test.conf
i=2
echo "$(${CMD[$i]})"
sleep 2
echo "$(${CMD[$i]})"
Example-2 (this does not work)
test.conf (same script as above)
CMD[1]="date;date"
Example-3 (tried this, it does not work either)
test.conf (same script as above)
CMD[1]="date && date"
I don't want my variable, CMD, to be inside tick marks because then, the commands would be executed at time of invocation of the source and I see no way of re-evaluating the variable.
This script essentially calls CMD on pass-1 to check something, if on pass-1 I get a false reading, I do some work in the script to correct the false reading and re-execute & re-evaluate the output of CMD; pass-2.
Here is an example. Here I'm checking to see if SSHD is running. If it's not running when I evaluate CMD[1] on pass-1, I will start it and re-evaluate CMD[1] again.
test.conf
CMD[1]=`pgrep -u root -d , sshd 1>/dev/null; echo $?`
So if I modify this for my test script, then test.conf becomes:
NOTE: Tick marks are not showing up but it's the key below the ~ mark on my keyboard.
CMD[1]=`date;date` or `date && date`
My script looks like this (to handle the tick marks)
. test.conf
i=2
echo "${CMD[$i]}"
sleep 2
echo "${CMD[$i]}"
I get the same date/time printed twice despite the 2 second delay. As such, CMD is not getting re-evaluate.
First of all, you should never use backticks unless you need to be compatible with an old shell that doesn't support $() - and only then.
Secondly, I don't understand why you're setting CMD[1] but then calling CMD[$i] with i set to 2.
Anyway, this is one way (and it's similar to part of Barry's answer):
CMD[1]='$(date;date)' # no backticks (remember - they carry Lime disease)
eval echo "${CMD[1]}" # or $i instead of 1
From the couple of lines of your question, I would have expected some approach like this:
#!/bin/bash
while read -r line; do
# munge $line
if eval "$line"; then
# success
else
# fail
fi
done
Where you have backticks in the source, you'll have to escape them to avoid evaluating them too early. Also, backticks aren't the only way to evaluate code - there is eval, as shown above. Maybe it's eval that you were looking for?
For example, this line:
CMD[1]=`pgrep -u root -d , sshd 1>/dev/null; echo $?`
Ought probably look more like this:
CMD[1]='`pgrep -u root -d , sshd 1>/dev/null; echo $?`'

Resources