Wednesday, June 20, 2007

More sed Tricks

Sometimes I think sed(1) is the reason I like Unix. Well, it's one of the quintessentially Unix tools, anyway.

One of my favorite uses for sed is to rename a whole bunch of files all at once. Whether the problem is a spaces or other special characters in filenames, capitalization problems, or whatever, as long as you can see a pattern sed can probably fix it for you.

For instance, I just had to make a whole bunch of symbolic links to some files

audit_user.dir
auto_data.pag
auto_master.dir
exec_attr.pag
....

so that the links had the names:

audit.user.dir
auto.data.pag
auto.master.dir
exec.attr.pag

and so on. Sed to the rescue:

for f in *_* ; do
ln -s $f `echo $f | sed -e 's,_,.,'`
done
(If you try something like that, put an "echo" before the "ln" to test it out)

That's a fairly simple example, and in fact the Unix C-shell has that kind of search-replace built in. I don't use C-shell, because the standard Bourne shell /bin/sh and derivatives (such as the Bourne Again SHell /bin/bash) are better for scripting.

But consider the problem of turning off some, but not all, of the "init scripts" under a particular runlevel in a typical SystemV-style system, such as Solaris or Linux. Red Hat Linux solves this with the chkconfig command, and Red Hat admins may like to use that handy facility.

But for those of us who have heterogenous collections of systems to worry about, and even on Red Hat systems when digging under the hood, it would be nice to have a way to turn off (or on) several services at once.

A "runlevel" is sort of like a system mode, and the levels are not standardized but generally follow a pattern of sorts. The chkconfig link above gives a nice description of runlevels for Linux, but note that while under Linux runlevel 5 means X Window operation, under Solaris it means shut down.

In a System V-style system, the init scripts themselves are stored in /etc/init.d, with some reasonable name like 'apache' for the Apache web server or 'autofs' for the file system automounter.

Each runlevel has its own directory, /etc/rcN.d, which contains symbolic links to the init scripts in /etc/init.d. To control how a particular runlevel uses a given init script, the symbolic link is prepended with an "S" for Start, "K" for Kill, or sometimes another letter, which means to ignore. After the S or K is a number, like 01 or 25, used to sort the names. The init(1) processing runs each S script in alphanumerical order, so that services should get higher numbers than the more basic services on which they depend.

Renaming the files is a bit of a hassle, either from the command line or with a GUI, because each one must be done. From the command line in particular, changing just the first letter requires typing the whole file name, with the numbers.

So what has all of this to do with sed?

This little script renames all the files matching a list of patterns you supply, either from S##name to K##name or the other way. It preserves the numbers, without you having to type the whole name.
#!/bin/sh
#------------------------------
# Script : s2k Copyright 2000,Loren Heal
# Author : lheal
# Created: 20010305
# Known Bugs:
# TODO: only operates on current directory.
#(------------------------------
FROM=S
TO=K
FORCE=""
TEST=""

usage()
{
echo "$1 [-on|-off|-force|-test] expression [...]"
echo ... where "expression" matches one or more of:
ls -F ${FROM}*
exit
}

[ -z ''"${@}" ] && usage "${0}"

while [ -n "$1" ] ; do
# echo "$1"
case ${1} in
"-on" ) FROM="K"; TO="S" ;;
"-off" ) FROM="S"; TO="K" ;;
"-force" ) FORCE="-f" ;;
"-test" ) TEST="echo" ;;
* ) for fname in `ls ${FROM}*${1}*` ; do
tname=`echo ${fname} | sed "s/^${FROM}/${TO}/"`
if [ -n "${tname}" -a -n "${fname}" -f ${fname} ] ; then
&& ${TEST} mv ${FORCE} ${fname} ${tname}
fi
done
;;
esac
shift
done

No comments: