I have a list of files that I want to check for a particular string. If the string exists in the file, I want to replace it with a new version that I have. What is the easiest way of going about this?
5 Answers
for file in "`grep -l "foo" bar gazi`"; do cp -p --backup=numbered replace "$file"; done
where replace is the path to the replacement file, bar and gazi represent a list of files to be checked, and foo is the search string of interest.
If you require additional assistance formulating an appropriate regular expression for grep (above) please do not hesitate to ask.
-
+1. But you might want to add "cp $file /tmp/$file.backup" first, just in case things go terribly wrong. Dec 1, 2010 at 18:32
-
1A very valid point, although this can be streamlined using the --backup operator for cp:
cp -p --backup=numbered replace $file
which will append ~n~ to a copy of the original $file if it exists before replacing it.– TokDec 1, 2010 at 18:54 -
1Note that without quotes around the "$file", it will fail for files that have spaces in them. Dec 1, 2010 at 18:59
-
The shell will tokenize the output of the grep on whitespace, so $file will never have a space in it anyway. If grep returns a filename with spaces in it, it will split the parts and set them to file individually.– markDec 1, 2010 at 19:38
-
Corrected by quoting the backticks which preserves the spacing. Thanks for the catch.– TokDec 1, 2010 at 19:58
grep can search a file for a string and will return 0 if it finds it, you could use this as part of a conditional to replace the file, something like:
#!/bin/bash
if [ !`/bin/grep -q teststring /path/to/file` ]; then
cp newfile oldfile
fi
Presumably you have a file with the list of files in it? I don't know if you've got a static file you're replacing matching files with, or a potential replacement per file. This handles the second case, for the first, merely replace $i.new with the static file name.
for i in `cat /var/tmp/listoffiles` ; do
if grep string $i > /dev/null 2>&1 ; then
cp $i $i.old
cp $i.new $i
fi
done
-
+1. And I'd give an extra +1 if I could for the backup to .old, just in case things go badly. Dec 1, 2010 at 18:33
-
Note that without quotes around the "$i" this will fail on files that have spaces in it. Dec 1, 2010 at 18:58
-
Actually, quotes around $i won't protect you in that case since the shell will split the output of
cat /var/tmp/listoffiles
on spaces. If you have spaces in the names of the files, you have to do things differently...perhaps with a shell function and xargs?– markDec 1, 2010 at 19:05
While I agree with all of the grep tests, sed can do this without a test really.
sed -i.BACKUP 's/STRING YOU WANT TO REPLACE/NEW STRING/' file
If the string isn't there, it won't do anything. Just worth mentioning. From your question I wasn't sure if you were trying to replace the whole file or just a string within it.
-
I believe he is trying to replace the whole file, not just the string. Dec 1, 2010 at 19:04
The correct way to process files in a loop in a shell script is to pipe the command into while
or redirect a file into the done
statement of a while
loop. This will handle any name except those that contain newlines. You should not use the form for f in $(cmd)
.
In this example, find
is the command that produces the list of files:
find . -type f -name "*.txt" | while read -r filename
do
if grep -qs "$string" "$filename" # or use >/dev/null 2>&1 instead of -qs
then
cp "$replacement" "$filename"
fi
done
or, here the list is contained in a file:
while read -r filename
...
done < "$file_with_list"
Another method is to use xargs
.
For a command that produces the list of files (e.g. find
):
find . -type f -name "*.txt" -print0 | xargs -0 -I % env string=$string sh 'grep -qs "$string" "%" && cp replacement_file "%"'
The preceding method will work in cases where filenames may include newlines.
If the list is in a file:
xargs -a "$file_with_list" -I % env string=$string sh 'grep -qs "$string" "%" && cp replacement "%"'
All of these examples will work in cases where filenames may include spaces.