#!/usr/bin/perl =pod =head1 NAME G - shorthand for multi level greps =head1 SYNOPSIS B [[grep options] string] [[grep options] string] ... =head1 DESCRIPTION B is a short hand of writing (search for single lines matching expressions): grep --option string | grep --option2 string2 or with B<-g> (search full files matching expressions): find . -type f | xargs grep -l string1 | xargs grep -l string1 =head1 OPTIONS =over 4 =item B<-g> Search current subtree for files that match all expressions - but not necessarily on the same line. If a parent dir contains a B<.git> dir B is used. =item I All B options. =back =head1 EXAMPLE Grep for lines with Foo but not Bar: G Foo -v Bar =head1 AUTHOR Copyright (C) 2017-2018 Ole Tange, http://ole.tange.dk and Free Software Foundation, Inc. =head1 LICENSE Copyright (C) 2012 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or at your option any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . =head1 DEPENDENCIES B uses B and B. =head1 SEE ALSO B =cut my $i = 0; for(@ARGV) { if($_ eq "-g") { # -g = recursive-and file grep $opt::g ||= 1; # -g not an option for grep next; } push @{$cmd[$i]}, $_; if(/^[^-]/) { $i++; } } if($opt::g and @cmd) { sub gitdir { # Find .git dir somewhere in parent my $dir = shift; -d "$dir/.git" and return "$dir/.git"; if(join(":",stat $dir) eq join(":",stat "$dir/..")) { # This is root return undef; } return gitdir("../$dir") } sub cache_gitdir { # cat all files in .git to /dev/null my $dir = shift; `find "$dir" -type f -print0 | xargs -0 cat >/dev/null`; } my $a = shift @cmd; my $gitdir = gitdir("."); if($gitdir) { cache_gitdir($gitdir); $run = 'git grep --threads 30 -l '.shell_quote(@$a); } else { $run = 'find . -type f | parallel --lb -Xq grep -l '.shell_quote(@$a); } if(@cmd) { $run .= '|' . join"|", map { 'xargs -d"\n" grep -l '. join(" ", shell_quote(@$_)) } @cmd; } exec $run; } elsif(@cmd) { exec join"|", map { "grep ".join(" ", shell_quote(@$_)) } @cmd; } else { exec 'cat'; } sub shell_quote { # Input: # @strings = strings to be quoted # Output: # @shell_quoted_strings = string quoted with \ as needed by the shell return wantarray ? (map { shell_quote_scalar($_) } @_) : (join" ",map { shell_quote_scalar($_) } @_); } sub shell_quote_scalar { # Quote for other shells my $a = $_[0]; if(defined $a) { # zsh wants '=' quoted # Solaris sh wants ^ quoted. # $a =~ s/([\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\^\*\>\<\~\|\; \"\!\$\&\'\202-\377])/\\$1/g; # This is 1% faster than the above if(($a =~ s/[\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\^\*\<\=\>\~\|\; \"\!\$\&\'\202-\377]/\\$&/go) + # quote newline as '\n' ($a =~ s/[\n]/'\n'/go)) { # A string was replaced # No need to test for "" or \0 } elsif($a eq "") { $a = "''"; } elsif($a eq "\0") { $a = ""; } } return $a; }