
For the last month or so I’ve been coding in BASH scripts. Of course I’ve used BASH before, but I’ve learnt a few things I just hadn’t known of before. So here are some of the new-to-me things I’ve come across.
local variables limited in scope to the inside of a function
function foo()
{
local VAR="hi"
}
now VAR has scope only within foo.
Beware though local can only be used on a variable in a function so be careful about copying code from functions out into the main body of a script.
A variable expansion to convert a string to lower case
VAR=miXedCase
echo ${VAR,,}
or upper case
echo ${VAR^^}
Which is useful for doing a case-insensitive comparison
if [ "${V1,,}" == "${V2,,}" ];
Another useful shell variable expansion trick allows replacing part of the text in the expansion.
echo ${VAR//Case/Beans}
Another thing I’ve finally grokked is when to use [ ] in an if.
if executes a command (actually it could be a group of commands, more about that later) and if it returns 0 then runs the commands in the then part of the statement.
So, if you just want to check that a command ran ok, you can simply go.
if command; then
echo ok
fi
which is the same as the more verbose
command
if [ $? -eq 0 ]; then
echo ok
fi
[ is just another command. One that expects an expression followed by a ]. Look for it in /usr/bin. You could replace it with the test command which is the same thing.
i.e.
if [ "thing" != "another" ]; then
echo "different things"
fi
is the same as
if test "thing" != "another"; then
echo "different things"
fi
Having said that [ aka test is "just another command" I should admit that BASH actually has a copy "built-in" and so doesn't bother using the version in /usr/bin. So it is somewhat special, but the built-in and external versions are meant to be interchangable. man builtins for a list of all the commands which have built-in versions in BASH to speed execution up. There's a few.
What I've found a bit confusing is an expression can be built up either in one [ ] command, or as a group of [ ] commands.
i.e.
if [ "thing" != "another" -o $((1+1)) -eq 2 ]; then
echo "something true"
fi
is the same as
if [ "thing" != "another" ] || [ $((1+1)) -eq 2 ]; then
echo "something true"
fi
Notice the difference?
Inside the test command (in the [ ]) -o acts as an or, but in the shell || can be used to or different test commands together.
Interactive Shells vs non-interactive : Signals, process groups and such.
BASH programming is just doing stuff at the command line and then when it works pasting it into a file and calling it a program. But but but… When you are “doing stuff at the command line” the shell is in interactive mode, and some of the behaviour is different to a script running in a non-interactive shell.
One thing I have hit: in the interactive shell, when a command finishes it signals all of its children letting them know it is time to shut down now too.
We can manually do the same thing the interactive shell is doing with three commands
trap "" SIGHUP # ignore the signal ourselves
kill -s SIGCONT 0 # wake up any sleepers
kill -s SIGHUP 0 # the game is all over
I’ve normally used kill just on one process at a time (kill 5234 or kill %1). Or I’ve used killall to signal all process by program name (killall tail). But what the command kill -s SIGHUP 0 is doing is sending the HUP (hangup) signal to all processes in the process group.
What’s a process group David? It is a family of processes. By default each child process inherits the process group of its parent and thus we can deal with them as a group.
We start a new process group by starting a new process with the setsid command. This makes the new process the new leader of a a new process group.
kill 0 signals all the processes in our process group.
kill -- -leader_pid, signals all the processes in the process group led by the pid given (but note it is negative)
The double dash (--) is how we pass a dash to a BASH program without it being interpreted as a flag.
finally kill -- -1 is how every process in the whole system can be signalled (every process but the init process)
I guess we can think of it as one big process group that every process belongs to, led by the init process.
Here’s how to kill a command. We nicely just send the TERM signal at first giving it time to tell its children and clean itself up. But if it ignores that, then we kill it and and its family with the KILL signal which it cannot ignore.
COMMAND=$(ps -p$PID -o comm=)
if [ -n "$COMMAND" ]; then
echo "Killing old $COMMAND command pid=$PID "
kill -s SIGTERM $PID
sleep 1
if ps -p$PID >/dev/null; then
# give it a few seconds more
sleep 10
if ps -p$PID >/dev/null; then
# kill the whole process group without mercy
kill -s SIGKILL -- -$PID
kill -s SIGKILL $PID
fi
fi
fi
Fun with mailx
This example shows how to send mail via a particular smtp server.
echo -e "$MSG\n"|cat - $LOG|from=sender@example.com smtp=mailserver.example.com mailx -s "$SUBJ" "$TO"
Playing with tput
The tput command allows us to do funky things on the screen in a reasonably terminal-independant way.
tput -S <<EOF
clear
cup 2 10
bold
EOF
echo "Look at me!!!"
tput cup 5 0
tput sgr0
I’m going to stop here and start again another day in a new post. Coming up: redirecting streams beyond just sending them to /dev/null, loading a file with readarray, and watching log files.