Sunday, November 29, 2015

Small and useful bash scripts

A growing collection of scripts and headers for your bash scripts.

Check and exit if a script is already running

This is to prevent multiple running instances of the same script:
''
test "$(pidof -x \"$(basename $0)\")" != $$ && exit
# or (bash only and protected against spaces in paths):
test $(pidof -x "$(basename $0)") != $$ && exit
''

Note: ''pgrep -af 'mqtt_watch.sh /solar/watts 5' '' is interesting to match a script with its arguments.

Or with a file (not totally secure, race conditions may still happen though unlikely):
''
LOCKFILE=/var/lock/$(basename $0).lock
[ -f $LOCKFILE ] && exit 0
trap "{ rc=$?; rm -f $LOCKFILE ; exit $rc; }" EXIT
touch $LOCKFILE
...
''

Via a safe, locking mechanism (see ''man flock''):
''
(
  flock -n 9 || exit 1
  # ... commands executed under lock ...
) 9>/var/lock/mylockfile
''

With an additional timeout (lock is overridden after a while):
''
LOCK_FILE="/var/run/$(basename $0).pid"
LOCK_TIMEOUT=10
test $(( $(date +%s) - $(stat -c %Y "$LOCK_FILE" 2>/dev/null||echo 0) )) -ge "$LOCK_TIMEOUT" && rm -f "$LOCK_FILE"  # remove stale lock
( set -o noclobber; echo "$$" > "$LOCK_FILE") 2> /dev/null || exit 1
trap "{ rc=\$?; rm -f '$LOCK_FILE'; exit \$rc; }" EXIT
''
In case you would better wait for the lock than exit the script, you can loop on lock creation: ''while ! ( set -o noclobber; echo "$$" > "$LOCK_FILE") 2> /dev/null; do echo '(waiting for the lock)'; sleep 0.1; done'' (note that this would not enforce the timeout).

Get the full directory name of the script

This works no matter where it is being called from:
''
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
''

Re-run a script as root if it was not called with sudo

At the start of the script, just add:
''
[ "$UID" -ne 0 ] && exec sudo "$0" "$@"
''

Automatically manage cronjob additions and removal of commands

Super quick backup of a file thanks to bash
''
cp /etc/rssh.{conf,original}
''

Automatically manage cronjob additions and removal of commands

Use command lines to add or remove scheduled jobs in your ''crontab'':
''
croncmd="/home/me/myfunction start 2> /home/me/myfunction/cron_errors < /dev/null"
cronjob="0 0 * * * $croncmd"
# Add
( crontab -l | grep -v "$croncmd" ; echo "$cronjob" ) | crontab -
# Remove
( crontab -l | grep -v "$croncmd" ) | crontab -
''

Use pv to get a progress bar with dd

''pv'' can be used in many contexts to provide a progress bar:

''
dd bs=4M if=image.dd | pv | dd of=/dev/sdz
''

Re-index a set of files with leadging zeroes (e.g. to build a timelapse)

This works by extracting the number in their name (bashism ''${f//[!0-9]/}'' removes all non-digit), then add non-significant leading zeros to them:
''
for f in * .jpg; do mv "$f" /tmp/$(printf "%05d" ${f//[!0-9]/}).jpeg; done
''

Bash process substitution

This usual case:
''
sort file1 | uniq >tmp1
sort file2 | uniq >tmp2
diff tmp1 tmp2
''

Is better done with  process substitution (no temporary file, no subshell, not even pipes):
''
diff <(sort file1|uniq) <(sort file2|uniq)
''

How it works is ''<(...)'' is replaced by a ''/dev/fd'' file descriptor as if it was a file (ref.)

Ubuntu: find the biggest installed packages

''
dpkg-query --show --showformat='${Package;-50}\t${Installed-Size}\n' | sort -k 2 -n | grep -v deinstall | awk '{printf "%.3f MB \t %s\n", $2/(1024), $1}' | tail -50
''
Then after removing it, ''apt-get clean'' will also purge the package cache (a good thing to free some harddrive space)

Find your serial port attached to USB

This returns devices like ttyUSB (or ttyACM):
''
find /sys/bus/usb/devices/usb*/ -name tty|sed 's|.*\(tty[^/]\+[0-9]\).*|\1|'
''


Check ethernet bandwith

''
sudo apt install speedtest-cli
speedtest-cli
''

Compute date difference with flexible parametrization (very bashic!)

Here is ''date_diff.sh'':
''
#!/bin/bash
set -- "${1:-$(</dev/stdin)}" "${@:2}"
[[ -f "$1" ]] && set -- "$(<$1)" "${@:2}"
echo $( date -d@$(( ( $(date +'%s') - $(date -d "$1" '+%s') ) )) +%s ) 3600 / p | dc
''

Then:
''
root:~# ./date_diff.sh "2016-08-09 06:03"
27
''
This is twenty-seven hours from now to the recorded date.

The second and third very bash-specific line in the script makes it possible to pass the date via an argument as above, or through a pipe as below. Say you stored a timestamp with ''date +'%Y-%m-%d %H:%M' > last.txt''. Then:

''
root:~# cat last.txt | ./date_diff.sh 
27
root:~# ./date_diff.sh last.txt 
27
''

Add a line to a reverse, while keeping under 500 lines

''
line="Something to log"
sed -ni "1s/^/$log\n/;500q;p" "$lf"
''

Robust parsing of a key = value ini file:

We want to set variables from a two-column file, without the risk that bash interprets the value (so the value can really be anything)

Say you have this file /tmp/test.ini to parse:
''
# Some comment
AA=da fook
BB=ya ' laa
CC=zarma $(ls)
DD_EE=0
EE=
''

You can parse it safely with this script to generate the respective variables

''
#!/bin/bash
prefix=SAFE_
while read -r line; do
k="$(echo $line|cut -d= -f1)"
v="$(echo $line|cut -d= -f2-)"
[[ $k =~ ^[A-Z_]+$ ]] && export $prefix$k="$v"
done < <(cat /tmp/test.ini)

echo BB=$SAFE_BB
echo CC=$SAFE_CC

# Result is:
#    BB=ya ' laa
#    CC=zarma $(ls)
''

Without ''$prefix'' set, make sure you do not allow variables in the left colum like PATH or LD...

Filter out non printable charcacters of a stream

Eg. when ''syslog'' gets corrupted with binary characters and many tools claim the file is binary:

''
tr -cd '\11\12\15\40-\176'
''



Switch my wifi router off when not in use (WR703N TP-link)

Automatic wifi shutdown when idle on a small WRT router

The script below runs on my OpenWRT wifi router (a cheap but effective $20 TP-Link TL-WR703N, which I flashed with OpenWRT -- see the how to here).



It is configured as a wifi router, when switched on.

But when no one connects to it, it will blink its led for a while before it shuts down by itself. So it does not waste useless energy, it is more secure at home and we live in one less wifi network.


Monday, November 16, 2015

Easily count tiny items (200 PCB vias here)

One way to check the number of items I just bought on ebay.

How can I check that there are really 200 vias in these bags?
I bought packs of 200 tiny rivets for vias (or check aliexpress for cheaper and choice). Since I am making my PCB on a mill and I often only use only single-sided PCB copper clads, it gets tricky to solder the headers from below. This is very annoying for headers and Arduino shields for example, as they protrude from below also...

I was somehow able to do it but a proper way is to insert so called PCB "through hole rivets" first. This way, the headers can be conveniently soldered from the top, once they are themselves inserted in the rivets (I wrote about it here).

But such rivets are not cheap (I got 200 of them for $20 on ebay!). I felt I had been cheated on when seeing the small volume it represented. Really 200?