views:

244

answers:

3

Hi,

I need to to iterate over the files in a directory and perform the following replacement.

Before:

Hello ${USER_NAME}, you live at ${HOME_ADDRESS}. It is now ${TIME}

After:

Hello ${userName}, you live at ${homeAddress}. It is now ${time}

The number of different tokens that appear within ${} is large, so it's not really feasible to run:

find . -name '*' -exec sed -i 's/${USER_NAME}/${userName}/g' {} \;
find . -name '*' -exec sed -i 's/${TIME}/${time}/g' {} \;

etc.

I'm hoping it's possible to perform this replacement using a single command, that looks something like:

find . -name '*' -exec sed 's/XXX/YYY/g' {} \;

But I can't figure out what to substitute for XXX and YYY. Is it possible to do this in a single command?

Cheers, Donal

+2  A: 

The -i flag to sed will edit a file in-place. For XXX and YYY, you would use something like:

sed -i 's/USER_NAME/userName/g'

and so on.

Update: I see that your question was really about changing "USER_NAME" into "userName" automatically. You could try this Perl script:

sub convert {
    my $r = lc $_[0];
    $r =~ s/_(.)/\U$1\E/g;
    return $r;
}
while (<>) {
    s/\${([A-Z_]+)}/\${@{[convert $1]}}/g;
    print;
}

Run it like this:

perl -i convert.pl inputfile.txt

Sample output:

$ cat inputfile.txt
Hello ${USER_NAME}, you live at ${HOME_ADDRESS}. It is now ${TIME}
$ perl -i convert.pl inputfile.txt
$ cat inputfile.txt
Hello ${userName}, you live at ${homeAddress}. It is now ${time}
Greg Hewgill
I get the following error: "Can't do inplace edit without backup at convert.pl line 6"However, if I remove the "-i" flag, it seems to work, so I guess I'll just redirect the output, then rename the files. Thanks!
Don
Sounds like you're on Windows: -i won't work without a backup extension. Use -i.bak, that'll rename foo to foo.bak before overwriting foo.
ephemient
Thanks, I also discovered that the script above incorrectly renamed ${FOO_IMG_SRC} to ${fooImg_src}
Don
Don: add a 'g' on the second line of the convert function. I have amended the above answer.
Greg Hewgill
Thanks Greg, I unaccepted the answer just because of this bug, but I'll reaccept it now
Don
+4  A: 

Formatted for clarity:

sed -i '/^Hello/ { s/\$\{USER_NAME\}/\$\{userName\}/g 
                   s/\$\{HOME_ADDRESS\}/\$\{homeAddress\}/g 
                   s/\$\{TIME\}/\$\{time\}/g
                  }'

Where /^Hello/ identifies the lines you wish to act on (make it more specific if needed) and the rest substitutes each variable name.


If writing this into a script consider the use of a HERE document to keep the formatting and make it easier to read and update...

dmckee
If I want to apply the replacement to all lines, presumably I just omit '/^Hello/' ?
Don
Yup. And you can use two addresses there "/^Hello/ /Goodbye$/ {...}" to work and all lines between Hello and goodbye (inclusive).
dmckee
A: 

The answer above works really well. For completeness, I might add that you can do:

sed '/^Hello/ { s/\$\{USER_NAME\}/\$\{userName\}/g' <filename> \
   | sed 's/\$\{HOME_ADDRESS\}/\$\{homeAddress\}/g' \
   | sed 's/\$\{TIME\}/\$\{time\}/g'

They're functionally identical (except that mine dumps to stdout; you'll have to put it somewhere (but you get to keep the original in case, like e, you mess up regularly on your regexps). I like my formulation just because I can start with the one sed command, then add on more with

!! | sed 'yet-enother-regexp'

Arrow keys? vi-mode? Who needs that stuff? :)

bog