Name: Feld, Ray; Areas: PC, UNIX; Phone: 123-4567
Name: Joy, Susan S.; Areas: Graphics; Phone: 999-3333
The name of each field ends with a colon, and each field is
separated by a semicolon. Using the top line as an example, you
want to change Feld, Ray to Ray Feld.
We'll present some commands that look promising but don't work.
After each command, we show you the line the way it looked before
the change and after the change.
:%s/: \(.*\), \(.*\);/: \2 \1;/
Name: Feld, Ray; Areas: PC, UNIX; Phone: 123-4567 Before
Name: UNIX Feld, Ray; Areas: PC; Phone: 123-4567 After
We've highlighted the contents of the first hold buffer in
bold
and the contents of the second hold buffer in italic.
Note that the first hold buffer contains more than you want.
Since it was not sufficiently restricted by the pattern that
follows it, the hold buffer was able to save up to the second comma.
Now you try to restrict the contents of the first hold buffer:
:%s/: \(....\), \(.*\);/: \2 \1;/
Name: Feld, Ray; Areas: PC, UNIX; Phone: 123-4567 Before
Name: Ray; Areas: PC, UNIX Feld; Phone: 123-4567 After
Here you've managed to save the last name in the first hold
buffer, but now the second hold buffer will save anything
up to the last semicolon on the line. Now you restrict the
second hold buffer, too:
:%s/: \(....\), \(...\);/: \2 \1;/
Name: Feld, Ray; Areas: PC, UNIX; Phone: 123-4567 Before
Name: Ray Feld; Areas: PC, UNIX; Phone: 123-4567 After
This gives you what you want, but only in the specific case of a
four-letter last name and a three-letter first name. (The
previous attempt included the same mistake.) Why not just return
to the first attempt, but this time be more selective about the
end of the search pattern?
:%s/: \(.*\), \(.*\); Area/: \2 \1; Area/
Name: Feld, Ray; Areas: PC, UNIX; Phone: 123-4567 Before
Name: Ray Feld; Areas: PC, UNIX; Phone: 123-4567 After
This works, but we'll continue the discussion by
introducing an additional concern. Suppose that the Area
field isn't always present or isn't always the second field.
The above command won't work on such lines.
We introduce this problem to make a point. Whenever you rethink
a pattern match, it's usually better to work toward refining the
variables (the metacharacters), rather than using specific text
to restrict patterns. The more variables you use in your
patterns,
the more powerful your commands will be.
In the current example,
think again about the patterns you want to switch.
Each word starts with an uppercase letter and is followed by any
number of lowercase letters, so you can match the names like this:
[A-Z][a-z]*
A last name might also have more than one uppercase letter
(McFly, for example),
so you'd want to search for this possibility in the second and
succeeding letters:
[A-Z][A-Za-z]*
It doesn't hurt to use this for the first name, too (you never
know when McGeorge Bundy will turn up).
Your command now becomes:
:%s/: \([A-Z][A-Za-z]*\), \([A-Z][A-Za-z]*\);/: \2 \1;/
Quite forbidding, isn't it?
It still doesn't cover the case of a name like Joy, Susan S.
Since the first-name field might include a middle initial, you need to
add a space and a period within the second pair of brackets.
But enough is enough.
Sometimes, specifying exactly what
you want is more difficult than specifying what you don't
want. In your sample database, the last names end with a comma,
so a last-name field can be thought of as a string of characters
that are not commas:
[^,]*
This pattern matches characters up until the first comma.
Similarly, the first-name field is a string of characters that
are not semicolons:
[^;]*
Putting these more efficient patterns back into your previous
command, you get:
:%s/: \([^,]*\), \([^;]*\);/: \2 \1;/
The same command could also be entered as a
context-sensitive replacement.
If all lines begin with Name, you can say:
:g/^Name/s/: \([^,]*\), \([^;]*\);/: \2 \1;/
You can also add an asterisk after the first space,
in order to match a colon that has extra spaces (or no spaces)
after it:
:g/^Name/s/: *\([^,]*\), \([^;]*\);/: \2 \1;/