3

I'm trying to get a count of the number of replaced strings with find and sed.

What I'm starting with:

# find . -name \*.php -exec sed -i -e "s|finddddd|replaceeeee|g" {} \;

This replaces four matches of "finddddd". After running this command, I reset my test file so the command will work again.

I tried using wc -l, The command found and replaced for instances of "findddd", but shows 0 matches found on the line below.

# find . -name \*.php -exec sed -i -e "s|finddddd|replaceeeee|g" {} \; | wc -l
0

Also tried moving wc -l in the command, but get errors.

find . -name \*.php -exec sed -i -e "s|finddddd|replaceeeee|g" {}| wc -l;
find: missing argument to `-exec'
0

and

find . -name \*.php -exec sed -i -e "s|finddddd|replaceeeee|g" | wc -l {} \;
wc: {}: No such file or directory
wc: ;: No such file or directory
0 total
find: missing argument to `-exec'

How do I get the base command to output count of matches?

  • 3
    When using the -i option, sed doesn't produce any output, it writes the results back to the file. So there's nothing to pipe to wc. – Barmar Oct 30 '14 at 19:01
  • 2
    I don't think there's any way to get sed to tell you how many replacements it made. You could write a perl script that does it. – Barmar Oct 30 '14 at 19:03
  • 2
    Do you want the number of replacements or the number of lines where a replacement was made? I mean, should two replacements on a single line be counted once or twice? – terdon Oct 30 '14 at 19:05
  • FWIW, the reason your third command block (the one ending with {}| wc -l;) is failing is because you're doing find ... -exec without an escaped semicolon; i.e., you have ; where you need \; (or, if you prefer, ';' or ";"). – G-Man Oct 30 '14 at 19:20
  • @terdon, that would count as 2. – a coder Oct 30 '14 at 19:37
3

You're using the wrong tool. wc will count lines of input but since you're using sed -i, there are no lines printed so nothing for wc to count. Even without it, the sed command would have printed all lines of the file so it still would not have worked correctly. Here's a different approach:

  1. Use perl instead

    find . -name \*.php \
      -exec perl -i -lpe 's|finddddd|replaceeeee|g && print STDERR "."' {} \;  \
    2>&1 | wc -l
    

    The trick is to print a . each time a replacement succeeded, then pass the results through wc. The -i flag edits the file in place and the -l causes a newline to be added to each print call. The -p tells perl to print each input line after applying the script passed by -e. Because we're editing the file in place, we need to print to STDERR to avoid having the . added to the file. The STDERR is then redirected to STDOUT (2>&1) which lets us use wc.

  2. If you need to count multiple replacements on the same line separately, use perl again:

    find . -name \*.php -exec \
        perl -i -lpe '$l++ while s|finddddd|replaceeeee|; 
        END{print STDERR $l}' {} + 2>&1 
    

    Here, perl itself is doing the counting. Note that I used + instead of \; to end the -exec call. This means that it will attempt to combine the commands into as few as possible. If you don't have too many .php files, they will all be passed to a single perl run and the number will be correct. If you have too many files, this will fail and you should use this instead:

    find . -name \*.php -exec \
      perl -lpe '$l++ while s|finddddd|replaceeeee|; 
                 END{print STDERR $l}' {} + 2>&1 >/dev/null | 
      awk '{k+=$1; }END{print k}'
    
  • I was able to invert the quotes to 's|finddddd|replaceeeee|g && print STDERR "."' -- it works, but are there any hidden pitfalls? – a coder Oct 30 '14 at 19:51
  • @acoder not that I can think of. It's just doing the replacement and printing, pretty straightforward really. – terdon Oct 30 '14 at 19:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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