0

I'm trying to find a way to scan a folder on my OSX system for all files containing a specific string of text(#SomeTag") in specific line(hashtag in first line). Just to clarify, I'm looking for text within the file, not in the file name.

I tried ag, fzf and also the combination but can't make it work how i want.

I would like to search in files with fzf that has some hashtags in specific line. For example:

#TagOne #TagTwo searchpattern

This would search for searchpattern only in files that have the #TagOne #TagTwo in first line.

Update: So far i came up whit this solution which works but its far from optimal but it works exactly how i want. The script takes 1-3 arguments after finding the files i can full text fuzzy search in the content of all found files.

 #!/bin/sh
if [ "$#" == 1 ]; then
    ag -Ril $1 ./Evernote | xargs ag --nobreak --nonumbers --noheading . | fzf
fi
if [ "$#" == 2 ]; then
    ag -Ril $1 ./Evernote | xargs ag -il $2 | xargs ag --nobreak --nonumbers --noheading . | fzf
fi
if [ "$#" == 3 ]; then
    ag -Ril $1 ./Evernote | xargs ag -il $2 | xargs ag -il $3 | xargs ag --nobreak --nonumbers --noheading . | fzf
fi
emKaroly
  • 756
  • 1
  • 10
  • 22
  • What are *"files with fzf"*? How can we tell you are only searching in the first line? Please try and think and explain a little more clearly else I fear no-one will be able to help you. – Mark Setchell May 25 '17 at 07:08
  • @MarkSetchell fzf is a command-line fuzzy finder. – emKaroly May 25 '17 at 07:13

2 Answers2

3
for file in `find [path] -type f`; do head -1 $file | grep [pattern] >> /dev/null && echo $file; done

Replace [path] with the directory you want to search and [pattern] with whatever you're looking for, like '#TagOne #TagTwo searchpattern'.

The for file in ``; do ....; done bit iterates over every line returned by the code in the graves (the backtick things), assigning each line into a thing called "file." Inside the graves, we have find [path] -type f, which finds all "normal" files (excluding links, directories, etc.) in your path and prints sends each to stdout (which is being consumed by our for loop.)

We then call head -1 on each of those files, which just extracts the first line of each, and greps through it for your pattern. Since we don't care about grep's normal output, I redirect it to /dev/null to keep it from printing. Conveniently, grep's exit code can be treated as a true/false, depending upon whether it located anything. The && echo $file takes advantage of this to print the filename, only if grep matched something in the first line.

UPDATE To support multiple patterns, you could chain the above solution, but you'd end up opening each file for every pattern you're requiring. If you have many patterns to search for, try this:

for file in `find . -type f`; do                                                                    
  FIRSTLINE=`head -1 $file`;                                                                      
  if [[ $FIRSTLINE == *pattern1* &&                                                                     
        $FIRSTLINE == *pattern2* &&                                                                     
        $FIRSTLINE == *pattern3* ]];
  then
      echo $file;
  fi;
done

This can all be crushed down to one line for use as an alias, but we're crossing a line here that bash isn't so great at. Having set the requirement that we match on a pattern that can't be bounded by a regular expression, you're probably better off resorting to python:

#!/usr/bin/env python
from os import walk
from os.path import join
import sys

directory = sys.argv[1]  # Use the first argument as the directory to search

for root, subdirs, files in walk(directory):
    for file in files:
        path = join(root, file)
        line = open(path).readline()
        if ('TagOne' in line and      # You could also get these on 
                'TagTwo' in line and  # the command-line...
                'TagThree' in line):
            print path
Sniggerfardimungus
  • 11,583
  • 10
  • 52
  • 97
  • Ok almost works, the problem is that i want to be able to find by tags in any order. For example #TagOne #TagTwo should produce same search result as #TagTwo #TagThree #TagOne – emKaroly May 25 '17 at 07:36
  • Are you looking for specific hashtags, or just any hashtags? If you can give specific examples of the tags you're looking for (a list that comprehensively defines all the possibilities you need,) it may be possible to define a regular expression that will match them. – Sniggerfardimungus May 25 '17 at 16:46
  • @SniggerfardimungusI am looking for specific tags. For example i have notes in markdown format all files contain one note and every note have a line "Tags:" where are mutiple tags like "C++", "STL" etc. I want to find all files where all searched tags are present. – emKaroly May 25 '17 at 16:57
  • Well your hashtag finding code is a lot better than mine :) But i need to output the content of these files so fzf can fuzzy search it, what i do with last two commands. After that i want to use this with vim like: vim $(fzf). So it search for tagged files output the content than i fuzzy search it with fzf and finaly i open the file on specific line in vim. – emKaroly May 26 '17 at 06:47
0

I believe ctags could fill the bill. It is a tool that makes it easy to navigate large source code projects. It provides some of the features that you may be used to in modern IDEs, such as the ability to jump from the current source file to definitions of functions and structures in other files. Since ctags is essentially a cached index, you can search on any string and find all of its references in your project/directory. Ctags is designed to be run inside of Vim and Emacs. Note: the fzf.vim plugin has a Tags search feature.

In your specific case, you'll want to add a custom tag. I think ctag's regex option suffices:

--regex-<LANG>=/line_pattern/name_pattern/[flags]
       Define regular expression for locating tags in specific language.

For example, your could recursively scan all text files for #word and save those results in tags file. First create a ctags option file:

# ctags definition file for searching for tags. 
--langdef=hashtext
--map-hashtext=+.txt
--regex-hashtext=/.*(#[a-zA-Z0-9]*)/\1/h,hashtag/I   #define your regex here
--fields=+ln

For the sake of the example, say this options file is called hashtext.ctags. Next, Then run ctags in your directory, like so:

ctags -R --options=hashtext.ctags *

This will recursively search for regex and create an index in a tags file. Open in vim the file(s) where you wish browse by hashtags and use :tag function.

Once all this machinery is in place, you can use fzf to search and jump to various tags. For details and example set-ups see here: fzf tags. Or, better yet, use the fzf vim plugin, which has Tag and BTag commands.

gregory
  • 10,969
  • 2
  • 30
  • 42