How to make grep -v work?

Cas van Tijn

New Member
Credits
16
Hey I have a question,

I have the following command:
locate -br '^test.sh$'
The output is:
/home/cas/scripts/test.sh

I want to have the path without the name of the file. So I did the following to remove test.sh from the output:
locate -br '^test.sh$' | grep -v test.sh
I expected the output to be:
/home/cas/scripts/
But instead I just don't get an output. When I press enter, I get no output and it just waits for the next command.
See picture below

1608130315930.png


Nothing gives an output. I even tried to invert-grep something different, 'cas', but it wouldn't give any output to.
It looks like grep -v just doesn't work. But normal grep (so not inverted) DOES work.
Why doesn't it work? Or is there a better way to find the path to a file from anywhere, but without the file name in the output (the goal is to get "/home/cas/scripts/")?

Thanks,
Cas
 


JasKinasis

Well-Known Member
Credits
5,050
@Cas van Tijn - I think you are slightly misunderstanding what grep does.
Grep filters entire lines of text. And its behavior is to be expected. It's doing exactly what you tell it to do.

When you use grep, it only reports entire lines of text containing the specified pattern.
And if you use grep -v, or grep --invert-match it only reports lines that DO NOT match the specified pattern!

So where you have used:
Bash:
locate -br '^test.sh$' | grep -v test.sh
The following happens:
1. The locate command will find any instances of files named test.sh and then those results are piped to your grep command. So we'd expect ALL lines to contain test.sh.
2. Because you have used grep -v it will only report lines of text that DO NOT contain the pattern "test.sh".
3. Because ALL of the results from locate contain "test.sh" (because that's what you're looking for) - grep reports nothing - because once again - you used the -v option, telling it to only report lines that DO NOT contain "test.sh".

Does that makes sense?!

So how do we do what you want?

Well, if you want to use the locate command to find all instances of files with a particular file-name and then report only the path to the directory containing the files, you should perhaps consider using dirname.

So you could do something like this:
Bash:
locate -br '^test.sh$' | while read -r line ; do dirname "$line" ; done
That will list all directories which contain a file which exactly matches "test.sh".
The locate command finds the paths for all files which exactly matches "test.sh" and pipes it to a while....read loop, which then calls dirname on each returned line, to report the name of the directory containing a file called "test.sh".

OR, if you're going to use this kind of thing a lot, with different search patterns, you could make it a little more generic and add a function to your ~/.bashrc like this:
Bash:
function dirs_with
{
  if [ $# -ne 1 ] ; then
    echo "Error: requires a single parameter - a regex for a file"
    echo "e.g."
    echo "      dirs_with '\.mp3$'"
    echo "To find all directories containing files ending in .mp3"
    echo "or"
    echo "      dirs_with '^test.sh$'"
    echo "To find all directories containing files called 'test.sh'"
    return 1
  fi
  locate -br "$1" |
      while read -r line ; do
          dirname "$line"
      done | sort -u
}
The above function expects to receive a single parameter - a regex for a file-pattern.
If it receives the wrong number of parameters, it displays an error/usage message and quits.
The rest of the code is pretty much the same as before.

We use the passed-in parameter as the regex to use with the locate command. The results from locate are piped to a while...read loop which runs dirname on each result, to report the path to the directory containing each file.
And the final sort, sorts the results alphabetically and because we've used the -u option, it sorts uniquely and removes any duplicates.

This is in case you used a regex like '\.mp3$' which matches ALL files that end in ".mp3" - which in turn could return multiple results for a single directory. So by sorting them uniquely - any directories with multiple .mp3 files in them will only be reported once!

So now you should be able to feed the function pretty much any valid regex and it should report any directories containing files with that regex.

With the function in your .bashrc, it will be available in any new terminal sessions you open.
To make it available in the current terminal (or any other terminals that were open before you added the function to your .bashrc), run the command:
Bash:
. ~/.bashrc
And that will re-read your .bashrc and will make the function available to you.

So for your particular usage, you'd run the function like this:
Bash:
dirs_with  '^test.sh$'
And if you wanted to identify ALL directories containing .txt files:
Bash:
dirs_with '\.txt$'
Whether you just go with the one-liner, or try adding the function to your .bashrc - it's up to you.

Going back to your original problem - the problem wasn't with grep. It did exactly what you told it to do! The real problem was simply that you were using the wrong tool for the job!

I hope this helps. If you have any questions, or concerns - fire away!

EDIT:
One caveat to using locate is that it relies on a database, which is updated via the updatedb command. Some distros periodically run the updatedb command automatically. But the database is NOT always guaranteed to be completely up to date.
If you're searching for things that have been on your PC for a while and the database isn't too far out of date - it's a pretty fast way of searching for things.
But if you're searching for something that was recently added - you may have to manually run the updatedb command in order to refresh the database before using locate.

If you run updatedb on an outdated database, it will report files that may have already been deleted, and will not report newer files that haven't been indexed yet.
 
Last edited:

Staff online

Members online


Top