Take the 2-minute tour ×
Server Fault is a question and answer site for professional system and network administrators. It's 100% free, no registration required.

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?

share|improve this question

5 Answers 5

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
share|improve this answer
    
The brackets and backticks aren't necessary. –  Dennis Williamson Dec 1 '10 at 23:35
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.

share|improve this answer
    
+1. But you might want to add "cp $file /tmp/$file.backup" first, just in case things go terribly wrong. –  Jed Daniels Dec 1 '10 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 '10 at 18:54
1  
Note that without quotes around the "$file", it will fail for files that have spaces in them. –  Sean Reifschneider Dec 1 '10 at 18:59
    
Good point, updated in the comment. Thanks. –  Tok Dec 1 '10 at 19:31
    
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 '10 at 19:38

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
share|improve this answer
    
+1. And I'd give an extra +1 if I could for the backup to .old, just in case things go badly. –  Jed Daniels Dec 1 '10 at 18:33
    
Note that without quotes around the "$i" this will fail on files that have spaces in it. –  Sean Reifschneider Dec 1 '10 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 '10 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.

share|improve this answer
    
I believe he is trying to replace the whole file, not just the string. –  Jed Daniels Dec 1 '10 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.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.