Prof A Olowofoyeku (The African Chief) wrote:
This isn't strictly a GPC question, but it is related, so I'll ask anyway :|
Is there an easy way to change a string in all source files to another string?
For example, if I want to change all occurrences of "Type foo = Object (bar)" to "Type Tfoo = Class (Tbar)" in all ".pas" and ".pp" files in a directory tree, is there an easy way to do it, using standard GNU tools? I could of course write a program to do it, but I figured there could be an easier way. Thanks.
sed can do it, though only within single lines. E.g.:
sed 's/Type (.*) = Object ((.*))/Type T\1 = Class (T\2)/' input.pas > output.pas
The general syntax for sed reaplacement command is 's/search/replace/'. There can be several replacements, separated with ;. If you want to allow several replacements within one line, add a g after the last / (it doesn't do any harm do always do this, unless you specifically want to replace only the first occurence per line).
In fact, you can use any character instead of /, e.g. 's|foo|bar|' (useful if the expressions contain / themselves).
The syntax of the replacement string is a (basic) "regular expression" (can be found on the net in many places). In particular, . means any character, * means any number of the previous (thus .* means anything), and ( ... ) is grouping, while ( and ) are literal parentheses (both occur in the example above). In the replacement string, the content of ( ... ) groups can be substituted with \1, \2, ... in sequential order.
The exact replacement to use can vary depending on how flexible you want it. E.g., if `Type' doesn't have to be there in the same line, you can omit it from both search and replace strings. (sed does replacements within lines, so it wouldn still work when `Type' is there.) OTOH, if you want to require `Type' to be at the beginning of the line, you could write s/^Type .../Type .../ (^ means beginning of line, but only in the search string -- in the replacement it's not necessary to state it again).
sed is case-sensitive. You can get a limited amount of case-insensitivity by using character sets, which are enclosed in [ ... ], e.g. [Oo]bject to match both Object and object.
Of course, sed doesn't know about Pascal syntax, so it will happily replace in strings, comments, and anything that fits the pattern, so use with care.
Note that (as shown in the usage above) sed doesn't replace the file in place, but writes to standard output. To replace several files in place, you can make a (ba)sh script like this:
#!/bin/sh for f do sed 's/foo/bar/' "$f" > "$f.new" && mv "$f.new" "$f" done
(Put in the sed command you want, of course.)
Then you call it with the file names you want, e.g.
./myscript *.pas
or
./myscript `find -name "*.pas" -o -name "*.pp"`
(using the find program to find all such files in the tree).
The latter only works as long as the file names contain no spaces. Otherwise:
find -name "*.pas" -o -name "*.pp" -print0 | xargs -0 ./myscript
Of course, when replacing files, you'd better make sure your sed expression is correct and/or backup your files first.
A somewhat more elaborate version of the script, that will show the differences and ask the user whether to replace:
#!/bin/sh for f do sed 's/foo/bar/' "$f" > "$f.new" && if diff -u "$f" "$f.new"; then rm "$f.new" # sed made no change else mv -i "$f.new" "$f" fi done
If you want to do it interactively, many editors offer regular expresions, e.g. my editor PENG can do basic and extended regex replacements (the latter just have a slightly different syntax, usually requiring fewer \es), and optionally ask for confirmation for every single replacement. End of plug. ;-)
Frank