I am in a folder with lots of .txt file, I would like to find all the files which contain both stringA and stringB (not necessarily on the same line), then list these files in the order of the size (from small to big)

I have tried the follows, but it doesn't work:

ls -lS | for f in *; do grep -q stringA $f && grep -l stringB $f; done

Does anyone have a good idea?

  • Usually I use find -iname '*.txt' | xargs grep -ne str1 -e str2. – ott-- Aug 6 '13 at 18:45

You can use GNU find:

find . -maxdepth 1 -exec grep -q stringA {} \; -exec grep -q stringB {} \; \
        -printf '%10s %p\n' | 
    sort -n
  • The printf arg to find is not POSIX, but this is a good solution. – jordanm Aug 6 '13 at 18:42

Using (portable and no problem with space in filenames) :

perl -Mautodie -wle '
    for (<"*.txt">) {
        open my $fh, "<", $_;
        my @list = <$fh>;
        close $fh;
        if (grep { /stringA/ } @list and grep { /stringB/ } @list) {
            $h->{$_}->{size} = (stat($_))[8];
        print join "\n", sort { $h->{$a}->{size} <=> $h->{$b}->{size} } keys %$h;
  • Portable is nice, but loading the entire file into an array and then using grep in boolean context (twice) is pretty inefficient. – jordanm Aug 7 '13 at 4:22

With zsh and GNU tools:

grep -lZ -- stringA *.txt(oL) | xargs -r0 grep -l -- stringB

Method #1

You could use the following chain:

$ grep -l stringA * | tr '\n' '\0' | xargs -0 grep -l stringB | xargs du | sort -nr

The tr converts the output of grep so that if any filenames include spaces they'll be protected going forward. Everything else is pretty straight-forward. The use of xargs to run the output from a previous command through the next command, is a typical pattern in Unix.

You can forego the tr bit and make use of grep's -Z switch.

$ grep -lZ stringA * | xargs -0 grep -l stringB | xargs du | sort -nr


$ grep -lZ stringA * | xargs -0 grep -l stringB | xargs du | sort -nr
9220    stringA99stringB.txt
8196    stringA88stringB.txt
7172    stringA77stringB.txt
6148    stringA66stringB.txt
5124    stringA55stringB.txt
4100    stringA44stringB.txt
3076    stringA33stringB.txt
2052    stringA22stringB.txt
1028    stringA11stringB.txt
4   stringAspacestringB.txt

Method #2

Similar approach except instead of the use of du | sort just pipe the output to ls -lS similar to your approach.

$ grep -lZ stringA * | xargs -0 grep -l stringB | xargs ls -lS


$ grep -lZ stringA * | xargs -0 grep -l stringB | xargs ls -lS
-rw-rw-r-- 1 saml saml 9437200 Aug  6 15:15 stringA99stringB.txt
-rw-rw-r-- 1 saml saml 8388624 Aug  6 15:15 stringA88stringB.txt
-rw-rw-r-- 1 saml saml 7340048 Aug  6 15:15 stringA77stringB.txt
-rw-rw-r-- 1 saml saml 6291472 Aug  6 15:15 stringA66stringB.txt
-rw-rw-r-- 1 saml saml 5242896 Aug  6 15:15 stringA55stringB.txt
-rw-rw-r-- 1 saml saml 4194320 Aug  6 15:15 stringA44stringB.txt
-rw-rw-r-- 1 saml saml 3145744 Aug  6 15:15 stringA33stringB.txt
-rw-rw-r-- 1 saml saml 2097168 Aug  6 15:15 stringA22stringB.txt
-rw-rw-r-- 1 saml saml 1048592 Aug  6 15:15 stringA11stringB.txt
-rw-rw-r-- 1 saml saml      32 Aug  6 15:35 stringAspacestringB.txt

Method #3

This one makes use of grep's ability to use Perl's regular expression engine (PCRE).

$ grep -Pzol "(?s)stringA.*stringB" * | xargs ls -lS

This method is a bit slower because the file is being converted so that it's end of lines are terminated with null characters (\0) instead of newlines (\n).

  • -P activate perl-regexp for grep (a powerful extension of regular extensions)
  • -z suppress newline at the end of line, substituting it for null character. That is, grep knows where end of line is, but sees the input as one big line.
  • -o print only matching. Because we're using -z, the whole file is like a single big line, so if there is a match, the entire file would be printed; this way it won't do that.

In regexp:

  • (?s) activate PCRE_DOTALL, which means that . finds any character or newline


 egrep -l '(stringA|stringB)' * | xargs ls -1S

egrep allows for regular expressions, so the above will search for stringA OR stringB in all files, the -l option tells egrep to just output the files matched (and supress the output of the matching lines). Piping it to xargs ls -1S will pass all the files matches to ls -1S. -1 tells ls to list one file per line and -S tells ls to sort by file size rather than by name.

EDIT Oops that's an OR (misread of the question).

In which case an adaption of sputnick's solution to use two greps

grep -l stringA * | xargs grep -l stringB | xargs -ls -1S

(at least getting around du and sort there's one less process :)

  • 2
    This is wrong in two ways: 1) the OP ask for both strings on each file; 2) the command fails on filenames containing spaces – enzotib Aug 6 '13 at 18:48
  • 2
    OP asked a AND, not a OR – Gilles Quenot Aug 6 '13 at 18:48

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.