-1

I am using grep to search matching patterns inside files, and copy its filename from the output. Since I do this operation a lot I may end up copying : as well. Hence is it possible to add two space before the first matching semicolumn or ending of the filename?

$ grep -Hrn -e "zorro" --color=always --exclude-dir=dir -I -F . | \
    cut -c 11- | awk '$0="\033[33;35m"$0' | tr -s '[:space:]'
doo.tex:1:zorro alper alper alper skfjsdlkfj dslfj dslkj

wanted output:

doo.tex  :1:zorro alper alper alper skfjsdlkfj dslfj dslkj
        ^
  <two space added>
5
  • Are you just trying to get a list of filenames (e.g. so you can save them in a file or use them as args to another command), or do you actually need the colourisation and line numbers? Do you need to extract ALL matching filenames, or just a few that match some other pattern(s) in the line? If you just need the filenames, then a) you should use an array (rather than manuall copy-pasting with a mouse), and b) you're making this harder for yourself by adding colours and line numbers.
    – cas
    May 6 at 23:57
  • @cas thanks for your detailed answer. I just wanted to see the list of all the filenames showing the matching line next to it. Usually by looking them I open the matching file to replace or remove the matching pattern manually. Coloring helps me to separate parts easily. In rare occasion I may need to pipe only the filenames into another process.
    – alper
    May 7 at 9:20
  • you're welcome. hopefully some of it will be of use to you in future. or to other readers.
    – cas
    May 7 at 11:20
  • Instead of your current grep+cut+awk+tr pipeline and adding on whatever else you want now, if you use find to find the files, and awk to search within the files and print whatever you want when there's a match then your script will be vastly simpler. Ask a new question with concise, testable sample input and expected output if you want help with that.
    – Ed Morton
    May 7 at 15:38
  • 1
    @EdMorton I did my best to ask it as a question (unix.stackexchange.com/q/701777/198423)
    – alper
    May 7 at 17:25

2 Answers 2

2

My first though went to

awk -F: -vOFS=: '{$1 = $1 "  "; print}'

But that's doing more work than needed I think, splitting and re-joining the line.

The alternative is quite concise

sed 's/:/  :/'

replacing the first colon with 2 spaces and a colon.


Regarding the filenames with colons conversation:

Reading the GNU grep man page: use the -Z option: "Output a zero byte (the ASCII NUL character) instead of the character that normally follows a file name."

So:

# .......v
grep -HrnZ -e "zorro" --color=always --exclude-dir=dir -I -F . | \
  perl -pe 's/\0/  :/'
3
  • Filenames are allowed to contain colons, so I hope your filenames don't. May 6 at 21:33
  • Also hopefully all these hieroglyphics from --color=always are passing until the first : which is true for some purple/magenta color for the filename I have here.
    – thanasisp
    May 6 at 21:40
  • Actually I had urge to ask it becuse filenames might have colons which makes the problem becomes little bit complecated but your answer does the job :-)
    – alper
    May 6 at 22:10
1

If all you need is a list of filenames containing text that matches a pattern, such as "zorro", and you intend to use those filenames with another program, then you can do something like the following to create a bash array containing the filenames, and avoid a lot of manual copy-pasting with the mouse.:

mapfile -d '' -t myfiles < <(grep -IFlZr zorro ./)

mapfile is a bash built-in command that populates an array from stdin. In this case, stdin is coming from grep running as a process substitution. I'm using -d '' with mapfile to tell it the input will be delimited by NUL characters, and -Z with grep so that it outputs a NUL-separated list of matching filenames. This will work with any filename, no matter what characters are in the name - including colons, spaces, newlines, and shell metacharacters.

You can see what's in the array with declare -p myfiles, or use the array's elements as args to another program (e.g. printf '%s\'n' "${myfiles[@]}"), or in a loop like for f in "${myfiles[@]}"; do echo "$f" ; done.

Note: grep's -l option will exit after the first match (same as the -m option), which will speed up the search in files where the search string appears early in the file. In files where the pattern doesn't appear, it still has to read the entire file.

BTW, if the reason why you're using the -I option is that you want to avoid binary files (like .pdf or .dvi output from TeX), then you can use find instead of grep -r. For example:

mapfile -d '' -t myfiles < <(find . -type f -name '*.tex' -exec grep -lZF zorro {} +)

If you need more complicated selection criteria, beyond what grep is capable of then you can use awk or perl or whatever instead of grep. e.g. if you wanted only filenames where "zorro" appeared on line 3 of the file:

mapfile -d '' -t myfiles < <(find . -type f -name '*.tex' \
  -exec perl -n -e 'if ($. == 3) {
                      printf "%s\0", $ARGV if (/\Qzorro\E/);
                      close(ARGV);
                    }' {} +)

This is just a simple example - if you can think of a criterion and write it as a perl (or awk or python or whatever) script, then you can use it to selectively populate the array. You can use any command, or any long and complicated pipeline of commands, as long as it prints filename(s) to stdout separated by a NUL character.

Note: the close(ARGV) close the current file and skips to the next, if any, whether the filename was printed or not. This means it never has to read more than three lines of any file, so will be a lot faster if you have a lot of large files to search.

Note 2: the \Q and \E is perl's way of doing a fixed-string search, similar to -F with grep. If you were using awk, you could do something similar with the index() function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.