Skip to content
VLSI Mentor

Linux & Bash · Part I · Module 14 · Finding Files and Searching Their Contents

Searching Recursively, and Excluding What You Did Not Mean

One flag turns a single-file search into a whole-tree search, and on a real repository that is where the trouble starts. The matches you wanted are there, along with several hundred from generated output, vendor code and version-control internals — and sorting them out is the actual skill.

Intermediate17 min readLinuxPart IFundamentalsSearchinggrep

Part I · Module 14 · Chapter 14.4 · Searching Recursively

Three chapters of searching one file at a time. Real work happens in a tree.

1. One Flag

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ grep -rn clk_div tree
tree/docs/notes.md:1:clk_div mentioned in docs
tree/rtl/alu.v:2:  clk_div = 2;
tree/build/gen.v:1:clk_div in a build artefact

-r searches every file beneath a directory. With -n for line numbers, each result names the file and the line, which is what makes the output actionable.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -rn 'clk_div' .           # from here down
grep -rn 'clk_div' rtl/        # one subtree
grep -rln 'TODO' .             # just the file names — Chapter 14.5

2. Why That Is Not Enough

On the three-file tree above, three matches is a fine answer. On a real repository it is not.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ grep -rn 'clk_div' . | wc -l
847

A search for one signal returns eight hundred lines, and almost none of them are the definition you wanted. They come from:

  • generated output — synthesis reports, elaborated netlists, simulation logs;
  • build trees — object files, intermediate Verilog, copies of your own source;
  • vendor and third-party code you are not going to change;
  • version-control internals — .git holds every version of every file;
  • binary files that happen to contain the byte sequence.
Why a recursive search needs exclusions. Searching a whole tree reaches generated output, third-party code and version-control data as well as the source you meant, so the real matches are buried among hundreds that are irrelevant. Naming the file types you want and the directories you do not leaves a result you can actually read.Search the whole treeevery file, wherever it camefromGenerated outputmatches tooand so does third-party codeThe real hits areburiedamong hundreds you did notwantName what you wantand name the directories toskipOnly source is searchedbuild trees and history arenever openedA result you can readand a search that finishes farsooner12
Figure 1 — why a recursive search needs exclusions. Searching a whole tree reaches generated output, third-party code and version-control data as well as the source you meant, so the handful of real matches is buried among hundreds that are irrelevant. Naming the file types you want and the directories you do not leaves a result short enough to read — and a search that finishes far sooner.

3. Naming What You Want

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ grep -rn --include='*.v' clk_div tree
tree/rtl/alu.v:2:  clk_div = 2;
tree/build/gen.v:1:clk_div in a build artefact

--include='PATTERN' searches only files whose names match. The pattern is a glob (Chapter 3.5), not a regular expression — so *.v means what it looks like.

Quote it. Unquoted, the shell expands *.v against the current directory before grep ever sees it (Chapter 9.1), and you end up restricting the search to whatever happened to be in your working directory.

Several are allowed:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -rn --include='*.v' --include='*.sv' --include='*.vh' 'clk_div' .

4. Naming What You Do Not

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ grep -rn --exclude-dir=build clk_div tree
tree/docs/notes.md:1:clk_div mentioned in docs
tree/rtl/alu.v:2:  clk_div = 2;

--exclude-dir=NAME skips a directory entirely — grep does not descend into it at all, so it costs nothing.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -rn --exclude-dir=.git --exclude-dir=build --exclude-dir=node_modules 'clk_div' .

And --exclude does the same for files:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -rn --exclude='*.log' --exclude='*.o' 'clk_div' .
FlagSkipsMatches against
--include='*.v'Everything elseThe file name
--exclude='*.log'These filesThe file name
--exclude-dir=buildThis whole subtreeThe directory name

5. Make It a Default

Typing six exclusions every time is how people stop using them. Two ways to avoid it.

An alias or function in ~/.bashrc (Chapter 10.5):

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
rtlgrep() {
  grep -rn --include='*.sv' --include='*.v' --include='*.vh' \
       --exclude-dir=build --exclude-dir=.git \
       "$@"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ rtlgrep clk_div .

A function rather than an alias, because it needs "$@" — Chapter 10.5's point exactly.

GREP_OPTIONS is not the answer. It was an environment variable that added flags to every invocation; GNU grep deprecated it precisely because it silently changed the behaviour of other people's scripts. If you see it in an old setup file, remove it.

6. Binary Files

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ grep -rn clk_div .
Binary file ./sim/waves.vcd matches

grep detects binary content and refuses to print matching lines, saying so instead — which is right, since dumping a megabyte of control characters into a terminal can leave it unusable.

Two flags control it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -rnI  'clk_div' .                      # skip binary files entirely
grep -rna  'clk_div' .                      # treat them as text and print anyway

-I is the one to want. A "Binary file matches" line is noise in a source search, and -I removes it along with the file.

That message is also a useful signal in itself: a .v file reported as binary usually means it has the wrong line endings, a stray null byte, or is not what you think it is (Chapter 4.7).

7. Searching Many Files Without -r

-r walks a tree. Sometimes you want a specific set of files instead, and there are two ways.

A glob, when the shell can express it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -n 'clk_div' rtl/*.v
grep -n 'clk_div' rtl/**/*.v        # needs shopt -s globstar — Chapter 10.6

A list from find, when it cannot:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
find . -name '*.v' -newer last_build -print0 | xargs -0 grep -n 'clk_div'

That second form is Chapter 14.7, and it is what you use when the selection depends on something a glob cannot express — age, size, type, depth.

8. ripgrep

Worth knowing about, because it is now common on engineering machines.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
rg clk_div

ripgrep is a grep replacement that respects .gitignore by default, skips binary files and hidden directories automatically, and is substantially faster. For searching a source repository it does the right thing with no flags at all, which is most of this chapter's content made unnecessary.

Three cautions:

  • It is not installed by default anywhere, so a script cannot assume it.
  • Its options differ — -g '*.v' rather than --include, and its default is already recursive.
  • Its defaults can hide things. A file listed in .gitignore is skipped, which is usually right and occasionally exactly wrong — searching for a value in generated output, for instance, needs rg -u.

Chapter 14.1 noted that grep on the verification machine was in fact ugrep, another of these. Know which you are running, and write scripts against grep rather than whichever replacement happens to be installed.

9. In Practice

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# the search you will type most
grep -rn --include='*.sv' --include='*.v' 'clk_div' .

# with the usual noise excluded
grep -rnI --exclude-dir=.git --exclude-dir=build --exclude-dir=work 'clk_div' .

# which files, not which lines — Chapter 14.5
grep -rl --include='*.sv' 'uvm_component_utils' .

# case-insensitive across documentation as well
grep -rni --include='*.md' --include='*.txt' 'clock divider' docs/

# a whole-word search, which on an identifier is the important one
grep -rnw --include='*.v' 'clk' rtl/

# count matches per file
grep -rc --include='*.sv' 'assert' . | grep -v ':0$'

# find a signal's declaration rather than every mention
grep -rnE --include='*.v' '^\s*(wire|reg|logic).*\bclk_div\b' .

The last one combines everything so far: recursive, restricted by type, extended syntax from Chapter 14.3, anchored, and with word boundaries. It finds the declaration and not the four hundred uses.

And note grep -v ':0$' on the counting one — grep -rc reports every file including those with zero matches, which is rarely what you want.

Practice

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mkdir -p /tmp/rg/{rtl,build,docs,.git} && cd /tmp/rg
printf 'module alu;\n  wire clk_div;\n  assign clk_div = 1;\nendmodule\n' > rtl/alu.v
printf 'module fifo;\n  wire clk_divider;\nendmodule\n' > rtl/fifo.v
printf 'clk_div in a generated artefact\n' > build/gen.v
printf 'clk_div appears in the notes\n' > docs/notes.md
printf 'clk_div in version control data\n' > .git/objects.txt
printf 'binary\000clk_div\000data\n' > sim.vcd

1 — Search everything, and count the noise.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -rn clk_div .
grep -rn clk_div . | wc -l

2 — Restrict by file type.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -rn --include='*.v' clk_div .

3 — Exclude directories.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -rn --exclude-dir=build --exclude-dir=.git clk_div .

4 — Combine both.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -rn --include='*.v' --exclude-dir=build clk_div .

5 — Watch the binary message, then suppress it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -rn clk_div . 2>&1 | grep -i binary
grep -rnI clk_div . | wc -l

6 — See why quoting the include matters.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cd rtl
grep -rn --include='*.v' clk_div ..
grep -rn --include=*.v  clk_div ..
cd ..

The second may behave differently depending on what the shell expanded.

7 — Use word boundaries.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -rn  --include='*.v' clk_div rtl/
grep -rnw --include='*.v' clk_div rtl/

fifo.v has clk_divider, which only one of those should match.

8 — Make it a function.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
rtlgrep() { grep -rn --include='*.v' --include='*.sv' --exclude-dir=build --exclude-dir=.git "$@"; }
rtlgrep clk_div .

9 — Count per file, without the zeros.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -rc --include='*.v' clk_div .
grep -rc --include='*.v' clk_div . | grep -v ':0$'

10 — Clean up. cd /tmp && rm -rf /tmp/rg

Check Your Understanding

1. What is the difference between -r and -R?

2. Why does grep -rn pattern . on a repository usually return too much?

3. Why must --include='*.v' be quoted?

4. --exclude-dir=build — does it skip one directory or several?

5. grep -rn pattern . prints "Binary file ./x.v matches". What does that suggest about x.v?

6. Why is grep pattern $(find . -name '*.v') a bad idea?

10. Remember

  • -r searches a whole tree; -R additionally follows symbolic links, which can duplicate results. Prefer -r.
  • Always give the directory — grep -rn pattern . — because BSD grep requires it.
  • -r alone is nearly useless on a repository. The signal is buried in generated output, vendor code and version-control data.
  • --include='*.v' is usually the single most effective flag, and it is faster because files are skipped by name without being opened.
  • Quote the glob, or the shell expands it first.
  • --exclude-dir=build skips a whole subtree and matches the name, at any depth.
  • --include, --exclude and --exclude-dir are GNU extensions that BSD grep also supports; a strictly portable script should use find.
  • -I skips binary files and removes the "Binary file matches" noise.
  • A source file reported as binary is a signal, not just noise.
  • Wrap your usual flags in a shell function, not an alias — it needs "$@".
  • GREP_OPTIONS is deprecated. Remove it if you find it.
  • Never substitute find into an argument list. Use -r, or find -print0 | xargs -0.
  • ripgrep does most of this automatically but is not installed by default and can hide ignored files.
  • Choosing where to search matters more than choosing the tool — Chapter 24.3 measures an --include form that silently matches nothing, and Chapter 23.5 one whose answer depends on which grep is installed.

You can now search a tree and get a readable answer. The next chapter is about shaping that answer — asking "which files" or "how many" instead of reading every matching line yourself.

Where this fits

Part of the Linux & Bash curriculum.