Search only in files that match a pattern with ack

ack

Can ack search only through files that match a specific 'glob' pattern (eg: search for foo in all files named "bar*.c"). The command

ack foo "bar*.c"

only works in the current directory.

Note: I know it's possible with find -exec:

find . -name "bar*.c" -type f -exec ack foo {} + 

But I would like a small and simple ack command, because find doesn't skip version control directories.

Best Answer

Searching directories

Based on the synopsis shown in the man page I would say yes it can process a directory, but looking at the switches it cannot look for just a file based on a pattern. For that you'll have to enlist find. The command ack does include the option --files-from=FILE so that it can be fed a list of files from find.

synopsis

       ack [options] PATTERN [FILE...]
       ack -f [options] [DIRECTORY...]

usage

   --files-from=FILE
       The list of files to be searched is specified in FILE.  The list of
       files are separated by newlines.  If FILE is "-", the list is
       loaded from standard input.

There is the --ignore-file= option which may give you what you want but seems a bit of a pain to actually use.

   --ignore-file=FILTERTYPE:FILTERARGS
       Ignore files matching FILTERTYPE:FILTERARGS.  The filters are
       specified identically to file type filters as seen in "Defining
       your own types".

Searching specific types of files

The only other way I can conceive of doing just this via ack is to use its --type switch:

   --type=[no]TYPE
       Specify the types of files to include or exclude from a search.
       TYPE is a filetype, like perl or xml.  --type=perl can also be
       specified as --perl, and --type=noperl can be done as --noperl.

       If a file is of both type "foo" and "bar", specifying --foo and
       --nobar will exclude the file, because an exclusion takes
       precedence over an inclusion.

To see what types are available:

$ ack --help-types | grep -E "perl|cpp"

format. For example, both --type=perl and --perl work. --[no]cpp .cpp .cc .cxx .m .hpp .hh .h .hxx --[no]objcpp .mm .h --[no]perl .pl .pm .pod .t .psgi; first line matches /^#!.*\bperl/ --[no]perltest .t

Examples

Find all the Perl files, based on both the filename (*.pm, *.pl, *.t and *.pod) and the shebang line.

$ ack -f --type perl 
examples/iwatch/iwatch/iwatch
examples/nytprof_perl/bad.pl

Find all the C++ files:

$ ack -f --type=cpp
Shared/Scanner.h
Shared/Sorter.h
Shared/DicomHelper.cpp
Shared/DicomDeviationWriter.h

Searching for foo in bar*.c

So then how can you accomplish what you want? Well you'll have to likely use find to do this:

$ find adir -iname "bar*.c" | ack --files-from=- foo
adir/bar1.c
1:foo
2:foo

adir/dir1/bar1.c
1:foo
2:foo

You can also use ack's ability to search for files that match a given pattern in their filenames (using -g <pattern>), and then pass this list to a second invocation of ack using -x or --files-from=-..

Using -x:

$ ack -g '\bbar.*.c$' | ack -x foo
bar1.c
1:foo
2:foo

dir1/bar1.c
1:foo
2:foo

Using -files-from=-:

$ ack -g '\bbar.*.c$' | ack --files-from=- foo
bar1.c
1:foo
2:foo

dir1/bar1.c
1:foo
2:foo

In either case we're matching the filenames that you want using this regex:

\bbar.*.c$

This matches files whose name is bar.*c and end after the .c using the end of line anchor, $. We also look to make sure that the names have a boundary character using \b. This will fail for files that contain boundary characters such as $bar.c or %bar.c for example.

Related Question