2

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 5

4
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.

5
  • +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
  • 1
    A 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.
    – Tok
    Dec 1, 2010 at 18:54
  • 1
    Note 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.
    – mark
    Dec 1, 2010 at 19:38
  • Corrected by quoting the backticks which preserves the spacing. Thanks for the catch.
    – Tok
    Dec 1, 2010 at 19:58
3

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
1
  • The brackets and backticks aren't necessary. Dec 1, 2010 at 23:35
1

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
3
  • +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?
    – mark
    Dec 1, 2010 at 19:05
0

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.

1
  • I believe he is trying to replace the whole file, not just the string. Dec 1, 2010 at 19:04
0

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.

Not the answer you're looking for? Browse other questions tagged .