Jenny Soo Hoo
Jenny Soo Hoo

Reputation: 51

How do I search and replace a string in multiple ZIP files?

I have a problem finding the right command to search and find a string in multiple ZIP files containing XML files on Unix.

I am able to find a search string in multiple ZIP files but haven't been successful with replacing the finding and replacing that string.

Unfortunately this isn't quite working. I'm trying to use a grep and sed command once it finds the string the unzipped file (to temp location). But I'm probably incorrect to assume that I can edit to temp?

oldAddress='<ns1:line1/>'
newAddress='<ns1:line1>somestring</ns1:line1>'

for file in *.zip; do
    unzip -c "$file" | grep -q "<ns1:line1/>" | xargs -l {} sed -i 's/$oldAddress/$newAddress/g'
done

Thanks in advance.

Upvotes: 5

Views: 6960

Answers (1)

oz123
oz123

Reputation: 28858

A few notes about your loop structure:

First, while it's tempting to use globals, bash also supports the following loop style:

 while read line ; do   echo $line; done < <(find . -iname 'file*zip')

Second, you can use zipgrep to search the files, and then only unzip those who really need to unzip. This will cause two time deflation of the files. Once for greping and once for unzipping thos who really need to be unzipped. However, this will spare us the need to zip the redundant files back.

Third, you are searching 2 times, for large file or many files this will be two times slower:

 grep -q "<ns1:line1/>" | xargs -l {} sed -i 's/$oldAddress/$newAddress/g'

Instead you can unzip only those matching files, and do the search and replacement in one step using only sed.

A suggested solution

# From within a (bash) script you need to use double quotes instead of singel qoutes to expand the variable
newAddress="<ns1:line1>somestring</ns1:line1>"
oldAddress="<ns1:line1/>"

for fname in *.zip
do
  zipgrep -q $oldAddress $fname;    
  if [ $? -eq 0 ]; then
     filename="${fname%.*}" 
     unzip -qp $fname | sed -e 's#'$oldAddress'#'$newAddress'#g' > $filename
     zip $filename.zip $filename
  fi
done

test data

Here is a for loop to create a test data:

for i in {1..4} ; do touch file$i; done
while read line ; do   
  echo '<ns1:line1/>' > $line;   
  zip $line.zip $line
  rm $line
done < <(find . -iname 'file*')

Upvotes: 1

Related Questions