9

When searching on multiple files, the output of grep looks something like this:

{FILE}:{MATCH}

This is a problem since if I double-click on the {FILE} portion, I will at least also select (and copy) the : and possibly some of the match. How can I get grep to instead format its output something like this:

{FILE} : {MATCH}

Basically, I want to insert a space before the column. (and optionally one after)

9 Answers 9

12

grep -T will work 7/8ths of the time.

% for f in a ab abc abcd abcde abcdef abcdefg abcdefgh; do echo pattern > $f; done
% grep -T pattern *
a      :pattern
ab     :pattern
abc    :pattern
abcd   :pattern
abcde  :pattern
abcdef :pattern
abcdefg:pattern
abcdefgh       :pattern

From the GNU grep manual:

-T --initial-tab

Make sure that the first character of actual line content lies on a tab stop, so that the alignment of tabs looks normal. This is useful with options that prefix their output to the actual content: -H, -n, and -b. In order to improve the probability that lines from a single file will all start at the same column, this also causes the line number and byte offset (if present) to be printed in a minimum-size field width.

0
6

This cannot be done by grep itself as far as I can tell. Assuming your filenames don't have a : in them:

grep ... | sed 's/:/ : /'

Only the first : will be padded.

Of course, you can tell grep to only print filenames:

grep -l ...
6

What constitutes a word as far as selection with double-clicking is concerned is terminal (and/or X toolkit) dependant and for some terminals, customizable.

For xterm, characters are organised in classes (letters, spaces...) and double clicking selects adjacent characters of the same class.

The default is described there. In that default, : is not in the same class as / itself not in the same class as letters or digits.

Some systems change that default by providing a resource file for xterm like /etc/X11/app-defaults/XTerm.

On a Linux Mint system, in there, I find:

! Here is a pattern that is useful for double-clicking on a URL:
*charClass: 33:48,35:48,37-38:48,43-47:48,58:48,61:48,63-64:48,95:48,126:48

That puts characters 58 (:) and 47 in the same class as letters and digits (48).

What you could do is change that to leave : in its own class via your own resource file.

For instance, if you add:

XTerm*charClass: 33:48,35:48,37-38:48,43-47:48,61:48,63-64:48,95:48,126:48

to your ~/".Xdefaults-$(uname -n)", then double clicking on the file name will stop at the :.

You can experiment with it with:

xterm -cc 33:48,35:48,37-38:48,43-47:48,61:48,63-64:48,95:48,126:48

You can also define a different selection method on triple or quadruple click as a regular expression. For instance

XTerm*on3Clicks: regex [^:]+

Would select sequences of non-colon characters upon triple-click.

1
  • If there was a way to have a "runner-up" this would be it. The accepted answer solves my problem well-enough, but you gave me a good solution that attacks my problem. Thanks.
    – azani
    Nov 19, 2014 at 1:05
2

Awk works for me:

grep string files | awk -F":" {'print $1" : "substr($0,index($0,$2))'}

Just a bit of explanation, if it would be helpful: The format {FILE}:{MATCH} from a multi-file grep is piped to Awk. Awk is told to use a colon (-F":") as a delimiter for identifying fields and then the following reformatting occurs: Print the first field (FILE), then print " : ", then print everything until the end of the line beginning where the second field (MATCH) starts. This should account for multiple ":" in the match (thanks muru).

3
  • This will have problems if there's a : in the matched text.
    – muru
    Nov 18, 2014 at 19:57
  • This will have problems if there's a : in the filename -- but that's hard to avoid. Nov 19, 2014 at 0:40
  • @AGHz you update will print the whole file name but it still will not correctly separate the name from the match when the file names contain :.
    – terdon
    Nov 19, 2014 at 3:43
2

This is grossly inefficient, as it results in spawning a new grep process for each input file, but:

Given a list of files (e.g., *), do

for f in file_list
do
    grep --label="$f " --with-filename pattern < "$f"
done

--label=string says, in effect, “When reading standard input, pretend that the input filename is string.”  --with-filename (-H for short) says, “Print the file name for each match.”  This is the default when there is more than one file to search.  IMNSHO, it should also be on by default when you specify --label, but it doesn’t seem to work that way.  Since my code (above) adds a space to the filename in the argument to --label, you get output that looks like

filename :Line matching pattern

If your filenames are coming from find, do

find (path…) (expression) -exec sh -c 'grep --label="$1 " -H pattern < "$1"' sh {} ";"
0

As muru said, sed accepts input streams aka. files. Therefore, adding -l to grep will output files instead of text. Pipe it and use xargs.

grep -l ... | xargs sed 's/}:{/ : /'

You might need -i parameter. More here.

Note: You don't have to escape in this case because you are neither using regular expressions nor the -e parameter for sed, so it will just search for the anything you put there. It is safer to include the curly brackets as your text might have semicolons as well.

0

I would pass the output of grep command to Perl like below.

grep ... | perl -pe 's/(?<=:)|(?=:)/ /g'

Example:

$ echo '{FILE}:{MATCH}' | perl -pe 's/(?<=:)|(?=:)/ /g'
{FILE} : {MATCH}

Explanation:

  • (?<=:) Match the boundary which exists after :
  • | OR
  • (?=:) Match the boundary which exists before :
  • Replace all the matched boundaries with a space.
0

If you don't care about speed, then find the files in a loop. Make sure grep does not show the file name (hidden by default if given only one file), and the prepend the file name with sed. Add -s to silence 'Is a directory' grep warnings. I used this solution because grep --label did not work for me.

find your_directory | while read f; do
    grep "my_query" "$f" -s | sed "s:^:[$f] :"
done

Output:
[your_directory/file name] my file contents

0

I too am facing the same problem. Generally, it is helpful to configure the terminal to include : as part of a word when double clicking, e.g. to select patterns such as 127.0.0.1:5354.

The output of grep stands out as the only scenario that doesn't work well with this configuration.

A good example would be https://github.com/kovidgoyal/kitty/issues/2602, where a user requested kitty to stop including : by default just so it would work better with grep. This subsequently caused a regression in https://github.com/kovidgoyal/kitty/issues/2705, where the author now had to add a special case for URL pattern.

As such, I think it makes more sense to just fix the output of grep (and friends) once and for all. The following quick-and-dirty diff for grep seems to work fine for me:

--- grep-3.7/src/grep.c.orig
+++ grep-3.7/src/grep.c
@@ -1161,13 +1161,7 @@
     }
 
   if (out_file)
-    {
       print_filename ();
-      if (filename_mask)
-        print_sep (sep);
-      else
-        putchar_errno (0);
-    }
 
   if (out_line)
     {
@@ -1177,8 +1171,19 @@
           totalnl = add_count (totalnl, 1);
           lastnl = lim;
         }
-      print_offset (totalnl, line_num_color);
       print_sep (sep);
+      print_offset (totalnl, line_num_color);
+    }
+
+  if (out_file)
+    {
+      if (filename_mask)
+        {
+          putchar_errno (32);
+          print_sep (sep);
+        }
+      else
+        putchar_errno (0);
     }
 
   if (out_byte)

And perhaps the best way forward is to convince the ripgrep author to add a flag for this: https://github.com/BurntSushi/ripgrep/issues/1718#issuecomment-872896430

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .