Reputation: 16803
I read the following article "Using grep and sed to find and replace a string" but how can I extend it to chain multiple greps. For example I have the following directory/file structure
dir1/metadata.txt
dir2/metadata.txt
dir1/metadata.txt has
filename1 '= 1.0.0'
filename2 '= 1.0.0'
dir2/metadata.txt has
filename1 '= 1.0.0'
long_filename '= 1.0.0'
In other words, both dir1/metadata.txt and dir2/metadata.txt contain "filename '1.0.0'" but the spaces between the "filename" and the "'1.0.0'" in each file is different.
Now I want to replace filename1's associated version to '2.0.0' in ALL metadata.txt files so the resulting files look like...
dir1/metadata.txt has
filename1 '= 2.0.0'
filename2 '= 1.0.0'
dir2/metadata.txt has
filename1 '= 2.0.0'
long_filename '= 1.0.0'
I'm trying
find . -name metadata.txt | xargs grep filename1 | sed -i "s/1\.0\.0/2.0.0/g" <some option here>
but I know the "some option here" part. Any clues?
Upvotes: 1
Views: 1073
Reputation: 113934
find . -name metadata.txt -exec grep -l --null filename1 {} + | xargs -0 sed -i "/^filename1 /{s/'= 1\.0\.0'/'= 2.0.0'/;}"
sed -i
will update the timestamp of every file it processes regardless of whether it changes the contents of the file. This is because, in operation, sed -i
creates a new file for each file processed and then overwrites the old file with the new file. To limit this, the above code uses grep
to select only the files that might need modification and sends only those file names, via a pipeline, to sed -i
for the update.
If the timestamp/overwriting issue is not important, consider mklement0's answer which eliminates the need for a pipeline, simplifying the command.
find . -name metadata.txt -exec grep -l --null filename1 {} +
This produces the list of files name metadata.txt
that also contain filename
.
The --null
tells grep
to separate file names with the NUL character.
xargs -0 sed -i "/^filename1 /{s/'= 1\.0\.0'/'= 2.0.0'/;}"
This applies sed -i
to change in-place the files whose names were returned by the above find
command.
In more detail:
/^filename1 /
This selects lines that start with filename1
followed by a space. This assures that we match neither sfilename1
nor filename12
.
s/'= 1\.0\.0'/'= 2.0.0'/
This changes the version number for the selected lines. (This assumes only one space after the equal sign. If this assumption is not correct, we can easily change it.)
The -0
option to xargs
tells it to expect its input to be a NUL-separated list of file names. This makes the pipeline safe even if the file names include spaces, newlines, or other difficult characters.
Upvotes: 3
Reputation: 439692
Try the following:
Linux:
find . -name metadata.txt \
-exec sed -i "s/^\(filename1[[:space:]]\{1,\}'= \)1\.0\.0/\12.0.0/" {} +
OSX / BSD:
find . -name metadata.txt \
-exec sed -i '' "s/^\(filename1[[:space:]]\{1,\}'= \)1\.0\.0/\12.0.0/" {} +
Note: The only reason why platform-specific commands are required is that GNU sed
and BSD
sed interpret the nonstandard -i
option, which specifies the suffix to use for an optional backup of the original file, differently: GNU sed
considers the option-argument for -i
optional, whereas BSD sed
considers it mandatory, requiring an explicit argument to specify the empty string (indicating the desire not to create a backup file)
exec ... +
is a find
feature that invokes the specified command with as many matching paths as can fit on a single command line, potentially resulting in multiple invocations, but typically resulting in only 1, which makes the invocation efficient.
"s/\(filename1[[:space:]]\{1,\}'= \)1\.0\.0/\12.0.0/"
is a POSIX-compliant sed
script that matches literal filename1
at the beginning of a line, followed by a variable amount of whitespace ([[:space:]]\{1,\}
), followed by literal '= 1.0.0
, and replaces the 1.0.0.
with 2.0.0
.
Note that if there are metadata.txt
files that do not have lines beginning with filename1
, they are still rewritten, because sed
's -i
option blindly "updates" the input files given (read: creates a new file that then replaces the original). If that is undesired, consider John1024's answer.
POSIX-compliance notes:
-exec ... +
variant of find
's -exec
primary has been part of POSIX since 2001 (POSIX.1-2001 / IEEE Std 1003.1-2001 / SUS v3 - see http://pubs.opengroup.org/onlinepubs/009695399/; thanks, @JonathanLeffler)sed
's -i
option for in-place updating is not POSIX-compliant - so you may have to work around that.Upvotes: 4