1

I want to recursively find files in my source tree by contents. I've tried the following using grep:

$ grep -rn printf | grep %s | grep bcm_errmsg\(rv\)

This returns me every line as I wanted but now, I would like to get the filename of each file that matches, so I changed it to:

$ grep -rn printf | grep %s | grep -l bcm_errmsg\(rv\)

but instead of printing the filenames, I only get

(standard input)

printed to stdout. How do I fix this to get each filename & path (to use sed on it)?

What I want to do: Find every file with printf lines that also contain %s and bcm_errmsg(rv) and then apply the following sed command to the files found:

sed -i 's/%s/%d/g; s/bcm_errmsg(rv)/rv/g;'
9
  • Assuming that the order of printf, %s and bcm_errmsg(rv) is unambiguous, you should be able to construct a single regular expression that matches all three in a single grep. In fact if you end goal is to edit the matching files in sed, I doubt you need grep at all - just let sed do the pattern matching Commented Jul 6, 2017 at 13:30
  • For my GNU grep 2.27, the filename is automatically prefixed with option -r. Later grepping behind the pipe doesn't remove that path, so your sed knows everything before the first : is the path.
    – Philippos
    Commented Jul 6, 2017 at 13:46
  • @steeldriver please see EDIT1 above
    – stdcerr
    Commented Jul 6, 2017 at 13:54
  • Do you want to apply the sed commands to every line in files that contain the matching text, or only to the matching lines themselves? Commented Jul 6, 2017 at 14:17
  • 4
    Possible duplicate of multiple replacements on single line with sed
    – Gohu
    Commented Jul 6, 2017 at 21:11

3 Answers 3

1

I think you're overcomplicating things. To look for string foo in all files located in the current dir and subdirs, simply run

grep -r "foo" *

By default, grep outputs the matching line prepended by the filename where it was found.

The following will instead only output the filename, without the matching line:

grep -rl "foo" *
2
  • ... so the latter can't do the additional filter steps that are required in the question
    – Philippos
    Commented Jul 6, 2017 at 13:49
  • @steeldriver please see EDIT1 above
    – stdcerr
    Commented Jul 6, 2017 at 13:54
1

Following @steeldriver's initial idea, you can do:

egrep -rl '.*printf.*%s.*bcm_errmsg\(rv\).*' . | xargs -d '\n' sed -i '/printf/ s/%s/%d/; /printf/ s/bcm_errmsg(rv)/rv/'
4
  • This almost works only that if there's a printf with a %s sign, it will replace it with %d even if it does not contain bcm_errmsg(rv) which is wrong...
    – stdcerr
    Commented Jul 6, 2017 at 20:24
  • @cerr. Answer updated.
    – Gohu
    Commented Jul 6, 2017 at 20:53
  • This selects those files that have a printf, then a %s, and bc_errmsg(v) in that order. It will completely miss out on those files that happen to have these 3 strings in some other order.
    – user218374
    Commented Jul 8, 2017 at 7:25
  • Even the sed command is incorrect in that it does the s/// only on the lines that contain printf. Per OP the subs need to happen only on those lines that contain all 3 strings.
    – user218374
    Commented Jul 8, 2017 at 7:27
1
grep -rlZP '(?=.*printf)(?=.*%s)(?=.*bcm_errstr\(rv\))' . |
xargs -r0 sed -i -e '
        /%s/!b
        /printf/!b
        /bcm_errstr(rv)/!b
        s/%s/%d/g;s/bcm_errstr(rv)/rv/g
'

We first run a recursive dir. from the current dir that scans for files which have the strings: printf, %s, and bcm_errstr(rv) on the same line but maybe in any order. The grep options that help us do that are:

  • -r => will recursively launch on all files in the current dir and below.
  • -l => will list filenames that match the criteria, viz., all 3 strings on same line.
  • -Z => the selected filenames are null separated (\0) rather than the usual newline (\n) so that we are able to tackle any kind of filenames.
  • -P => enable the Perl regex engine whereby we are able to use lookaheads to determine whether the 3 strings are existing on the same line.

On the other side of the pipe, xargs is waiting to receive the filenames, separated by \0. It then feeds all these filenames, as much as possible, to sed command line. The sed command you already know from your previous question , where it performs the subs. only on those lines that contain the 3strings on the same line.

You must log in to answer this question.

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