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 (securely):
''
LOCKFILE=/var/lock/$(basename $0).lock
[ -f $LOCKFILE ] && exit 0
trap "{ rc=$?; rm -f $LOCKFILE ; exit $rc; }" EXIT
touch $LOCKFILE
...
''

Or via a simple locking mechanism (see ''man flock''):

''
(
  flock -n 9 || exit 1
  # ... commands executed under lock ...
) 9>/var/lock/mylockfile
''

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 (typically images, to build a timelapse)

This works by extracting the number in their name and add non-significant leading zeros (bash)
''
for f in * .jpg; do mv "$f" /tmp/$(printf "%05d" ${f//[!0-9]/}).jpeg; done
''

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