Bash - A way to watch files for changes? - macos

My current solution is to use the python library watchdog and the bash snippet (originally taken from here).
watchmedo shell-command client/js/src/templates/ proto/ --recursive \
--patterns="*.soy;*.proto" \
--command="echo \"WATCHMEDO file changed - rebuilding\"; make genfiles;"
Basically I'm watching a few template files, and then running make genfiles automatically if one of them changes.
I'm wondering if there's a way to do this in pure bash? I'd rather not have all my devs have to depend on that Python library.
I'm on OSX.

#!/bin/bash
watched_files=$# # pass watched files as cmd line arguments
if [ -z "$watched_files" ]; then
echo "Nothing to watch, abort"
exit
else
echo "watching: $watched_files"
fi
previous_checksum="dummy"
while [ 1 ]; do
checksum=$(md5 $watched_files | md5) # I use Mac so I have `md5`, in linux it's `md5sum`
if [ "$checksum" != "$previous_checksum" ]; then
echo "None shall pass!" # do your stuff here
fi
previous_checksum="$checksum"
sleep 1
done

This is a nice cli FAM client: http://fileschanged.sourceforge.net/

Related

Testing server setup bash scripts

I'm just learning to write bash scripts.
I'm writing a script to setup a new server.
How should I go about testing the script.
i.e.
I use apt install for certain packages like apache, php etc. and then a couple of lines down there is an error.
I then need to fix the error and run it again but it will run all the install commands again.
The system will probably say the package is installed already, but what if there are commands which append strings to files.
If these are run again it will append the same string to the file a second time.
What is the best approach to write bash-scripts like this?
Can you do test runs which rollback everything after an error or end of the script?
Or even better to have the script continue from the line where the error occured the next time it is run?
I'm doing this on an Ubuntu 18.04 server.
it's a matter of how clear you want it to be to read it, but
[ -f .step01-done ] || your install command && touch .step01-done
[ -f .step02-done ] || your other install command && touch .step02-done
maybe a little easier to read:
if ! [ -f .step01-done ]; then
if your install command ; then
touch .step01-done
fi
fi
if ! [ -f .step02-done ]; then
if your other install command ; then
touch .step02-done
fi
fi
...or something in between.
Now, I would suggest creating a directory somewhere and maybe logging output from the commands to some file there (maybe tee it) but definitely putting all these files you are creating with touch there. That way if you start it from another directory by accident, it won't matter. You just need to make sure that apt-get or whatever you use actual returns false if it fails. It should.
You could even make a function that does it in a nice way...
#!/bin/bash
function do_cmd() {
if [ -f "$1.done" ]; then
echo "$2: skipping already completed step"
return 0
fi
echo -n "$2: "
$3 1> "$1.out" 2> "$1.err"
if $?; then
echo "ok"
touch "$1.done"
return 0
else
echo "failed"
echo -e "see \"$1.out\" and/or \"$1.err\" for details."
return 1
# could "exit 1" instead
fi
}
[ -d /root/mysetup ] || mkdir /root/mysetup
if ! [ -d /root/mysetup ]; then
echo "failed to find or create /root/mysetup directory
exit 1
fi
cd /root/mysetup
# ---------------- your steps go here -------------------
do_cmd prog1 "installing prog1" "apt-get install prog1" || exit 1
do_cmd prog2 "installing prog2" "apt-get install prog2" || exit 1
do_cmd startfoo "starting foo service" "service foo start" || exit 1
echo "all setup functions finished."
You would use:
do_cmd identifier "description" "command or function"
description
identifier: unique identifier used when files are generated:
identifier.out: standard output from command
identifier.err: standard error from command
identifier.done: created when command is successful
description: this is actually printed to the terminal when the step is being executed.
command or function: this is the actual command to run
not sure why stackoverflow forced me to format that last bit as code but w/e

How can a Bash command remember and toggle a setting?

I want to have a launcher that runs a Bash commands that toggle a setting; switching the setting one way requires one command and switching it the other way requires another command. If there is no easy way to query the system to find out the status of that setting, how should Bash remember the status of the setting so that it can run the appropriate command?
An obvious solution would be to save the status as files and then check for the existence of those files to determine the appropriate command to run, but is there some neater way, perhaps one that would use volatile memory?
Here's an attempt at a toggle script using temporary files:
#!/bin/bash
main(){
settingOn="/tmp/red_on.txt"
settingOff="/tmp/red_off.txt"
if [[ ! -e "${settingOff}" ]] && [[ ! -e "${settingOn}" ]]; then
echo "no prior use detected -- creating default off"
touch "${settingOff}"
fi
if [ -f "${settingOff}" ]; then
echo "switch on"
redshift -o -t 1000:1000 -l 0.0:0.0
rm -f "${settingOff}"
touch "${settingOn}"
elif [ -f "${settingOn}" ]; then
echo "switch off"
redshift -x
rm -f "${settingOn}"
touch "${settingOff}"
fi
}
main

Travis CI build Racket Installation for Container-based

My before_install in my .travis.yml reads
before_install:
- . scripts/get_racket.sh
- alias racket="${RACKET_DIR}/bin/racket"
I also have a script get_racket.sh which reads
#!/bin/bash
if [[ -z "$RACKET_VERSION" ]]; then
echo "Racket version environment variable not set, setting default"
export RACKET_VERSION=HEAD # set default Racket version
echo "Version: $RACKET_VERSION"
fi
if [[ -z "$RACKET_DIR" ]]; then
echo "Racket directory environment variable not set, setting default"
export RACKET_DIR='/usr/racket' # set default Racket directory
echo "Directory: $RACKET_DIR"
fi
if [ ! -e cache ] || [ ! -d cache ]; then
echo "Creating cache folder ..."
mkdir cache
fi
cd cache
INSTALL=$(ls | grep '^racket*.sh' | tr -d '[:blank:]')
if [[ ! -e "$RACKET_DIR" ]] || [[ ! -d "$RACKET_DIR" ]]; then
if [[ -z "$INSTALL" ]]; then
echo "Racket installation script not found, building."
if [ ! -e travis-racket ] || [ ! -d travis-racket ] \
|| [ ! -e travis-racket/install-racket.sh ] \
|| [ ! -f travis-racket/install-racket.sh ]; then
git clone https://github.com/greghendershott/travis-racket.git
fi
bash < travis-racket/install-racket.sh
else
"./$INSTALL"
fi
fi
which racket &>/dev/null
ESTATUS=$?
if [[ -n "$ESTATUS" ]]; then
echo "Adding racket to PATH"
export PATH="${PATH}:${RACKET_DIR}/bin"
fi
alias racket='$RACKET_DIR/bin/racket'
cd ..
but in a script that uses racket later in my build chain, I keep getting
racket: command not found
As you can see in the above snippets, I have tried a few workarounds to install (and later cache for faster builds) racket without sudo privileges (because this is a restriction of Travis CI's Container-based infrastructure). Any help would be much appreciated, I'm stumped.
You need to figure out whether this install script you've shown successfully puts a working Racket binary anywhere on the disk. Maybe it didn't even compile, or maybe it tried to install in /usr/bin, where you don't have write access without sudo, or maybe there's something wrong with the binary. Find the binary, make sure it works.
If it does work, you need to pay attention to where your script puts Racket. Does it go to /usr/bin, $HOME, or someplace else entirely?
Finally, you need to figure out where the failing script is looking for Racket. The line where you set the $PATH will not affect the $PATH as seen from another shell script. I'd bet it's installing somewhere that's not in the default $PATH, and your failing script is looking only in the default $PATH.

Shell script file existence on Mac issue

Ok so I have written a .sh file in Linux Ubuntu and it works perfectly. However on a Mac it always returns that the file was not found even when it is in the same directory. Can anyone help me out?
.sh file:
if [ ! -f file-3*.jar ]; then
echo "[INFO] jar could not be found."
exit
fi
Just thought I'd add, this isn't for more than one file, it's for a file that is renamed to multiple endings.
In a comment to #Paul R's answer, you said "The shell script is also in the same directory as the jar file. So they can just double click it after assigning SH files to open with terminal by default." I suspect that's the problem -- when you run a shell script by double-clicking it, it runs with the working directory set to the user's home directory, not the directory where the script's located. You can work around this by having the script cd to the directory it's in:
cd "$(dirname "$BASH_SOURCE")"
EDIT: $BASH_SOURCE is, of course, a bash extension not available in other shells. If your script can't count on running in bash, use this instead:
case "$0" in
*/*)
cd "$(dirname "$0")" ;;
*)
me="$(which "$0")"
if [ -n "$me" ]; then
cd "$(dirname "$me")"
else
echo "Can't locate script directory" >&2
exit 1
fi ;;
esac
BTW, the construct [ ! -f file-3*.jar ] makes me nervous, since it'll fail bizarrely if there's ever more than one matching file. (I know, that's not supposed to happen; but things that aren't supposed to happen have any annoying tendency to happen anyway.) I'd use this instead:
matchfiles=(file-3*.jar)
if [ ! -f "${matchfiles[0]}" ]; then
...
Again, if you can't count on bash extensions, here's an alternative that should work in any POSIX shell:
if [ ! -f "$(echo file-3*.jar)" ]; then
Note that this will fail (i.e. act as though the file didn't exist) if there's more than one match.
I think the problem lies elsewhere, as the script works as expected on Mac OS X here:
$ if [ ! -f file-3*.jar ]; then echo "[INFO] jar could not be found."; fi
[INFO] jar could not be found.
$ touch file-302.jar
$ if [ ! -f file-3*.jar ]; then echo "[INFO] jar could not be found."; fi
$
Perhaps your script is being run under the wrong shell, or in the wrong working directory ?
It's not that it doesn't work for you, it doesn't work for your users? The default shell for OS X has changed over the years (see this post) - but it looks like your comment says you have the #! in place.
Are you sure that your users have the JAR file in the right place? Perhaps it's not the script being wrong as much as it's telling you the correct answer - the required file is missing from where the script is being run.
This isn't so much an answer, as a strategy: consider some serious logging. Echo messages such as "[INFO] jar could not be found." both to the screen and to a log file, then add extra logging, such as the values of $PWD, $SHELL and $0 to the log. Then, when your customers/co-workers try to run the script and fail, they can email the log to you.
I would probably use something like
screenlog() {
echo "$*"
echo "$*" >> $LOGFILE
}
log() {
echo "$*" >> $LOGFILE
}
Define $LOGFILE at the top of your script. Then pepper your script with statements like screenlog "[INFO] jar could not be found." or log "\$PWD: $PWD".

Bash: Only allow Pipe (|) or Redirect(<) to pass data, and show usage otherwise

I have a script which takes in several arguments.
Now, I have modified the script to except multiple file names and operate on them.
I also want this script to execute when I am receiving input via a pipe (|) or a redirected input (<).
But, I do not want the script to wait for input on terminal when none of the above three inputs are provided, and rather show usage instructions.
I am using the following function:
# PIPED CONTENT
if [ "$#" == "0" ]; then
READINPUT="1"
if [ "x$TEXTINPUT" == x"" ]; then
READINPUT=1
TMPFL=`tempfile -m 777`
while read data; do
echo "${data}" >> $TMPFL
done
TEXTINPUT="`cat $TMPFL`"
rm $TMPFL
fi
# if [ "x$TEXTINPUT" == x"" ]; then
# if [ "$#" == "0" ]; then usage; fi
# fi
fi
Any help is appreciated.
Regards
Nikhil Gupta
if test -t 0; then
echo Ignoring terminal input.
else
process -
fi
The -t test takes a file descriptor as parameter (0 is stdin) and returns true if it is a terminal.
Please be aware that there are two different "test" commands: the built in bash command, and the "test" program which is often installed as /usr/bin/test, part of the coreutils package. The two provide the same functions.
[[ -t 0 ]]
is equivalent to
/usr/bin/test -t 0
You may run either of the above on a "bash" command line, with the same results.

Resources