63

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.

4
  • 2
    I don't understand, what's wrong with using find . -name "bar*.c" -exec ack foo {} \;? There's nothing special about grep, you can use any command with find's -exec.
    – terdon
    Commented Apr 24, 2014 at 11:11
  • 1
    @terdon find also searches through version control directories and I don't want that.
    – compie
    Commented Apr 24, 2014 at 13:41
  • 2
    Then please edit your question and explain the limitations you need to work around.
    – terdon
    Commented Apr 24, 2014 at 13:44
  • i find it better to use ripgrep
    – cregox
    Commented Aug 28, 2020 at 12:21

9 Answers 9

38

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.

5
  • 1
    This doesn't answer my question (search for foo in all files named "bar*.c")
    – compie
    Commented Apr 24, 2014 at 14:37
  • 1
    @compie - it does in the sense I'm showing you how to get a slice of the files you want. To search for cpp files ack --type=cpp <string>. See ack's man page for more on all this though. But what I'm basically telling you is that you cannot search using ack the way that you want.
    – slm
    Commented Apr 24, 2014 at 15:43
  • 1
    so it does answer in the sense that it shows ack cannot do what was asked.
    – xealits
    Commented Aug 5, 2014 at 13:49
  • 1
    @xealits - this part answers it: Searching for foo in bar*.c find adir -iname "bar*.c" | ack --files-from=- foo. Or, if you just want one file: echo "barBaz.c" | ack --files-from=- foo Commented Aug 31, 2017 at 22:46
  • 3
    tldr; ack -g '\bbar.*.c$' | ack -x foo
    – chim
    Commented Oct 12, 2017 at 14:26
18

It's easy if the file type is known, and ack knows a lot of file types. So if, for example, you want to only search in C files, than you can do:

ack --cc 'string'

But if it's not one of the known extensions, you need to define your own type. This should work:

ack --type-set barc:match:/bar.+\.c/ --barc 'string'

Note that you need both --type-set and --barc.

(Thanks to Andy, who also helped with this on the mailing list.)

4
  • 1
    Ok that works but using find and 'ack -x' is simpler. I think I will stick with that.
    – compie
    Commented Apr 25, 2014 at 6:58
  • Adding a link to the docs here would be helpful. man ack doesn't really help and doesn't have cc when I search that.
    – YPCrumble
    Commented Apr 16, 2016 at 15:19
  • 1
    ack --help=types There's everthing I can think of there. I mostly use it for Python and Ada. Commented Feb 23, 2017 at 9:58
  • somehow ack --help=types does not work on mac's version v3.3.1 (works on linux' version 2.26). A handy cheatsheet might help: --[no]cmake --[no]rake --[no]cpp --[no]python and many more are there.
    – xealits
    Commented May 25, 2020 at 0:37
10

This would seem to be the fastest and safest:

find . -name '*.c' | ack -x 'some string'

-x Read the list of files to search from STDIN.

However, if the file is likely to be in your locate database, this would be even quicker:

locate --basename '*.c' | ack -x 'some thing'

Locate can also accept old-school regular expressions, a little painful, but if you're looking for c files, it might be required. e.g.

locate --basename --regex '\.\(c\|cpp\|h\|hpp\|cc\)$' |
    ack -x 'some thing'

That just translates to: "search all filename that end in c, cpp, h, hpp or cc."

For @nyxee who raised a very good question, the only answer I can see is to add another pipe:

locate --basename --regex '\.\(c\|cpp\|h\|hpp\|cc\)$' |
    grep -v '/\.'                                     |
    ack -x 'some thing'

That will skip any directory or subdirectory that starts with a .

4
  • how do we exclude hidden directories with the extension. For example, we have very very many directories named .gradle while searching for gradle files.
    – nyxee
    Commented Feb 28, 2021 at 11:25
  • Usw locate --basename --regexp '.+\.\(c\|cpp\|h\|hpp\|cc\)$' | ack -x 'some thing' TO EXCLUDE DIRECTORIES THAT MAY HAVE NAMES LIKE .CPP nad .HPP. I have only added the `.+' operator so that a filename must exist before the extension which is the usual pravtice.
    – nyxee
    Commented Feb 28, 2021 at 11:37
  • thanks best answer for all files types! why? because next time I need it can remember it ;)
    – noBillSide
    Commented Dec 12, 2023 at 13:45
  • @nyxee valid question, i have updated the answer. Commented Mar 28 at 11:28
7

"What's new in ack 2?" http://beyondgrep.com/ack-2.0/

with ack 2.0, you can use the new -x to pipe filenames from one invocation of ack into another.

ack2 -g -i filepattern | ack2 -x -w searchpattern

Only I can't get it to work:

% ack -g "*bar*.c"
Invalid regex '*bar*.c':
Quantifier follows nothing in regex; marked by <-- HERE in m/* <-- HERE bar*.c/ at ack line 314.

Thus it seems -g needs a regex, while I want a 'glob' style option...

2
  • 2
    Yes, -g takes a regex, not a glob. You could write your regex as .*bar.*\.c$. Commented Apr 24, 2014 at 15:37
  • 2
    @AndyLester Thanks for confirming. Do you like the idea of a 'glob' option for ack?
    – compie
    Commented Apr 25, 2014 at 6:59
4

@chim 's answer is the best, but its written as a comment so I'm reposting as a formal answer...

ack -g '\bbar.*.c$' | ack -x foo

I like the fact that your can regex the path...

1
  • best answer for ease of use. Commented May 29, 2020 at 15:52
2

Ack doesn't support glob style file selection. Since I really miss this I created a small shell script ackg:

#!/bin/sh
# 'glob' support for ack
find -name "$2" -type f -exec ack "$1" {} +

Now you can use the command:

ackg foo "bar*.c"

But note: this will unfortunately also search in version control dirs (eg: .git).

2

This can be done with the -G option to ag, the silver searcher (an enhanced clone of ack-grep).

$ echo foo > bar_wanted.c
$ echo foo > unwanted.c
$ ag -G "bar.*\.c" foo
bar_wanted.c
1:foo

Notes:

  • ag uses PCRE regular expression syntax, so the regexp must be bar.*\.c instead of bar*.c.
  • The -G option needs to precede the search term, as anything after that is interpreted as a filename.
1

Do you want only a certain pattern, or do you just want C source files?

If you want all C files, use

ack --cc foo

If you want all C files and NOT C header files use

ack --cc --no-hh foo
0

Using zsh you can do:

ls **/a*.txt | ack --files-from=- something
1

You must log in to answer this question.

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