ag in practice

Posted on Fri 03 June 2022 in Linux

Introduction

The silver searcher, or ag for short, is a tool for searching text. It relies on pthreads, so it is generally faster than other tools, like ack. I've been using ag instead of grep these days for searching strings in a large codebase. This post gives a few examples of using ag in practice.

Using wildcards

Say you want to find the string "main.install.encoder". Such a string is a key for getting an environment variable. Therefore, you may expect to find it in your codebase like in these examples:

# match these lines
getSytemVariable("main.install.encoder")
getSytemVariable(Constants.MAIN_INSTALL + ".encoder")

# not these lines
getSytemVariable("main.install.decoder")
getSytemVariable(Constants.MAIN_INSTALL + ".decoder")

In this case, you can define a placeholder between getSystemVariable and encoder as follows:

ag "getSystemVariable.*encoder"

This pattern allows us to search for a string that contains getSystemVariable, followed by some characters (.*), and then followed by encoder. It is similar to using grep like this:

grep "getSystemVariable" | grep "encoder"

Example

$ ag "getSystemVariable.*encoder"
main.py
1:getSystemVariable("main.install.encoder")
2:getSystemVariable(Constants.MAIN_INSTALL + ".encoder")
$ grep -r "getSystemVariable" . | grep -r "encoder" .
./main.py:getSystemVariable("main.install.encoder")
./main.py:getSystemVariable(Constants.MAIN_INSTALL + ".encoder")

Now, suppose there is a file like this:

<getSystemVariable>
    <value>encoder</value>
</getSystemVariable>

If you run ag "getSystemVariable.*encoder", then you will not get any matches. I think this is because getSystemVariable and encoder are located in different lines. If the content is converted to a single line, then the pattern will match:

<functions><getSystemVariable><value>main.install.encoder</value></getSystemVariable></functions>
ag "getSystemVariable.*encoder"
config.xml
1:<functions><getSystemVariable><value>main.install.encoder</value></getSystemVariable></functions>

main.py
1:getSystemVariable("main.install.encoder")
2:getSystemVariable(Constants.MAIN_INSTALL + ".encoder")

There is a drawback, though: If there is a file that contains a large line, then ag could match it and print it out. Consider this example:

<functions><getSystemVariable>  ... more content ....  <value>main.install.encoder</value> ... more content ... </getSystemVariable></functions>

If you run ag "getSystemVariable.*encoder", it will find a match that can potentially occupy your whole terminal screen. In addition, you are likely not interested in that match. Instead, you want to find getSystemVariable, followed by some characters, and encoder. In this scenario, you can limit the amount of characters as follows:

ag "getSystemVariable.{0,30}encoder"

Example

ag "getSystemVariable.{0,30}encoder"
main.py
1:getSystemVariable("main.install.encoder")
2:getSystemVariable(Constants.MAIN_INSTALL + ".encoder")

Here, we specify that the maximum number of characters between getSystemVariable and encoder is 30. By doing this, we can prevent matching a large line and print it out.