Classic Shell Scripting - Arnold Robbins [169]
Running the program creates the files:
awk -f splitout.awk merge1
Managing UIDs
Now that we have separated the users by categories, the next task is to create a list of all the UID numbers in use:
awk -F: '{ print $3 }' merge1 | sort -n -u > unique-ids
We can verify that we have only the unique UID numbers by counting lines in merge1 and unique-ids:
$ wc -l merge1 unique-ids
20 merge1
14 unique-ids
34 total
Continuing through our task list, the next step is to write a program that produces unused UIDs. By default, the program reads a sorted list of in-use UID numbers and prints the first available UID number. However, since we'll be working with multiple users, we'll want it to generate a batch of unused UIDs. This is done with the -c option, which provides a count of UIDs to generate. Example 11-4 presents the newuids.sh script.
Example 11-4. The newuids.sh program
#! /bin/sh -
# newuids --- print one or more unused uids
#
# usage:
# newuids [-c N] list-of-ids-file
# -c N print N unused uids
count=1 # how many uids to print
# parse arguments, let sh issue diagnostics
# and exit if need be
while getopts "c:" opt
do
case $opt in
c) count=$OPTARG ;;
esac
done
shift $(($OPTIND - 1))
IDFILE=$1
awk -v count=$count '
BEGIN {
for (i = 1; getline id > 0; i++)
uidlist[i] = id
close(idlist)
totalids = i
for (i = 2; i <= totalids; i++) {
if (uidlist[i-1] != uidlist[i]) {
for (j = uidlist[i-1] + 1; j < uidlist[i]; j++) {
print j
if (--count = = 0)
exit
}
}
}
}' $IDFILE
Most of the work is done in the inline awk program. The first part reads the list of UID numbers into the uidlist array. The for loop goes through the array. When it finds two elements whose values are not adjacent, it steps through and prints the values in between those elements. It decrements count each time so that no more than count UID numbers are printed.
In shells that have arrays and that support arithmetic more directly, such as ksh93 and bash, it's possible to let the shell do all the work. In fact, this awk script was derived from a similar one for ksh93: see http://linux.oreillynet.com/pub/a/linux/2002/05/09/uid.html.
Creating User-Old UID-New UID Triples
We now have to process the dupusers and dupids files. The output file lists the username, old UID and new UID numbers, separated by whitespace, one record per line, for further processing. For dupusers, the processing is pretty straightforward: the first entry encountered will be the old UID, and the next one will be the new chosen UID. (In other words, we arbitrarily decide to use the second, larger UID for all of the user's files.) At the same time, we can generate the final /etc/passwd records for the users listed in both files.
* * *
Tip
This plan treats the disks of both systems equally, requiring that file ownerships (potentially) be changed on both systems. This is simpler to code, at the (possible) expense of more time spent changing file ownerships. A different option would be to leave the files on one system alone, making that system the "master" system, so to speak, and doing ownership changes only on the second system. This would be harder to code; we leave that as one of the infamous "exercises for the reader."
* * *
Here's the code:
rm -f old-new-list
old_ifs=$IFS
IFS=:
while read user passwd uid gid fullname homedir shell
do
if read user2 passwd2 uid2 gid2 fullname2 homedir2 shell2
then
if [ $user = $user2 ]
then
printf "%s\t%s\t%s\n" $user $uid $uid2 >> old-new-list
echo "$user:$passwd:$uid2:$gid:$fullname:$homedir:$shell"
else
echo $0: out of sync: $user and $user2 >&2
exit 1
fi
else
echo $0: no duplicate for $user >&2
exit 1
fi
done < dupusers > unique2
IFS=$old_ifs
We use the shell's read command to read pairs of lines from dupusers, sending the final password file entry to unique2. At the same