[fgp] : outil de recherche contenu fichiers, à récursivité adaptative

Observations sur grep
grep est ou outil de recherche bien optionné, sous-exploité lorsqu’utilisé sporadiquement, qui a l’avantage d’être disponible par défaut, mais qui présente certains inconvénients:
● sa récursivité est non maîtrisable; -r est juste binaire, ce qui est problématique dans de nombreux cas;
● la sortie non formatée par défaut des résultats rend difficile l’exploitation des recherches lorsque le nombre de fichiers est élevé;
● ses options par défaut ne sont pas les plus judicieuces en usage courant;
En résumé, grep n’est pas adapté en usage terminal pour une recherche avancée dans une arborescence de répertoires.

Il existe de nombreuses solutions de recherche de contenu, mais n’en trouvant aucune répondant à mes critères d’usage courant, fgp est né (fgp = contraction de find + grep).

fgp, c’est quoi ?
C’est un outil de recherche de contenu fichiers à récursivité adaptative s’appuyant sur find et grep, pour usage terminal, offrant une sortie conviviale des résultats.
Son usage reste simple, intuitif, en limitant l’accès aux options essentielles et courantes.
/!\ → non adapté pour usage dans un script.

Pourquoi une récursivité adaptative ?
Parce-que généralement, lorsqu’on choisit un répertoire initial de recherche, on est en priorité plus intéressé par ce qui sera trouvé aux niveaux 1,2,3 qu’au niveau 15 de sous-répertoires.

Usage:
$ fgp <options> <regex Etendu> <fichier ou rep> <fichier ou rep> ...

Options par défaut:
● recherche à partir du répertoire courant, si aucun répertoire indiqué;
● récursivité automatique (max 10) jusqu’aux premiers résultats, ou niveau max imposé par option ‹ -N ›
● regex Etendu ; ex: ‹ (abc|xyz) ›
● Masjucules/minuscules indifférenciées (inversable avec -ni)
● ne traverse pas les systèmes de fichiers différents
● exclusion des répertoires/fichiers cachés
● exclusion des répertoires .cache, Trash, lost+found, et fichiers binaires
● exclusion des fichiers non autorisés
● avertissement au delà de 100 fichiers trouvés
● extraction partielle ligne pour les très longues lignes, pour ne pas saturer l’affichage
● affichage du temps de recherche (si > 1 sec)
● détection des variables commençant par un tiret

Options:

 -N  : profondeur max N de recherche répertoires
 -r  : récursivité (max 10)
 -mN : affichage limité aux N premières lignes trouvées par fichier (ex: -m2)
 -a,--all : recherche aussi dans les fichiers cachés
 -l,--files-with-matches : seul le nom du fichier sera affiché
 -ni,--no-ignore-case : distinction minuscule ou Majuscule
 -w,--word : recherche du mot cat -> 'xx/cat,yy' sera trouvé, mais pas 'xxcatyy'
 -v,--invert-match : recherche inversée
 -AN : affiche N lignes suivantes (After ; seulement pour entrée | )
 -q,--quiet : sortie muette, test uniquement (seulement pour entrée | )
 -nc,--no-color : suppression sortie couleur
 -h,--help : affiche cette aide

Le plus simple pour estimer si fgp présente un intérêt relativement à grep est de comparer à ce que trouve grep.
Ouvrir un terminal pour fgp, et un autre pour grep, et comparer commande par commande:

fgp '^[decus]' /etc/apt/s{,*/}*[st]
fgp -a 'firefox'
fgp '(modules=|blacklist)' /etc/ /usr/share/
fgp '^P.*(esr|rbird)$' /var/
fgp -v '^(#|$)' /etc/dpkg/*.cfg
lspci -v | fgp -A7 audio
man cp | fgp '-deref' 

fgp:

#!/usr/bin/bash
# 20241109 ~ @verner
# fgp : outil de recherche contenu fichiers, à récursivité adaptative
# ――――――――
_help() { cat <<EOF | \
 sed '/^ -/s/[^:]*/\x1b[38;5;123m &\x1b[m/;s/fgp/\x1b[38;5;120m&\x1b[m/g'
 Usage: $ fgp <options> <regex E> <fichier ou rep> <fichier ou rep> ...

 Description:
  Recherche contenu fichiers à récursivité adaptative et sortie formatée.
  La liste d'options accessibles est limitée aux courantes (90% des besoins)

 Options par défaut:
  * recherche à partir du répertoire courant, si aucun répertoire indiqué;
  * récursivité automatique (max 10) jusqu'aux premiers résultats,
    ou niveau max imposé par option '-N'
  * regex Etendu ; ex: '(abc|xyz)'
  * Masjucules/minuscules indifférenciées (inversable avec -ni)
  * ne traverse pas les systèmes de fichiers différents
  * exclusion des répertoires/fichiers cachés
  * exclusion des répertoires .cache, Trash, lost+found, et fichiers binaires
  * exclusion des fichiers non autorisés
  * avertissement au delà de 100 fichiers trouvés
  * détection des variables commençant par un tiret

 Options:
 -a,--all : recherche aussi dans les répertoires/fichiers cachés
 -N  : profondeur max N de recherche répertoires
 -r  : récursivité (max 10)
 -mN : affichage limité aux N premières lignes trouvées par fichier (ex: -m2)
 -l,--files-with-matches : seul le nom du fichier sera affiché
 -w,--word : recherche de 'cat' -> 'xx/cat,yy' sera trouvé, mais pas 'xxcatyy'
 -v,--invert-match : recherche inversée
 -AN : affiche N lignes suivantes (After ; seulement pour entrée | )
 -q,--quiet : sortie muette, test uniquement (seulement pour entrée | )
 -ni,--no-ignore-case : distinction minuscule ou Majuscule
 -nc,--no-color : suppression sortie couleur
 -h,--help : affiche cette aide

 Exemples:
  fgp '^[scude]' /etc/apt/s{,*/}*[st]
  fgp -a 'firefox'
  fgp '(modules=|blacklist)' /etc /usr/share
  fgp '^P.*(esr|rbird)$' /var/
  fgp -v '^(#|$)' /etc/dpkg/*.cfg
  lspci -v | fgp 'audio' -A7
  man cp | fgp '-deref'
EOF
}
# ―――――――――
opt=EIhis
for x ; do
  case $x in
	-a|--all) a=1 ;;			 	 # + hiddens
	-l|--files-with-matches) l=-l ;; # print file-name only
	-w|--word) opt=w$opt ;;			 # exact word (more restrictive)
	-m*) ((${x/-m/}>0)) && mc=$x ;;  # max count output
   -ni|--no-ignore-case) opt=${opt/i/} ;;  # strict case
	-A*) ((${x/-A/}>0)) && A=$x ;;  # After (pipe input only)
	-v|--invert-match) v=-v ;;
	-q|--quiet) q=-q ;;
   -nc|--no-color) cl=never ;;
	-r) md=10 ;;
	-h|--help) _help ; exit ;;
	-[0-9]*) ((${x/-/}>0)) 2>/dev/null && md=${x/-/} ;; # depth
	*) [ -d "$x" ] && R+=("$x") ||
	 { [ -f "$x" ] && F+=("$x") || pat="$x" ;} ;;
  esac
done
# ―――――――――
[ "$pat" ] || { echo " > argument absent" ; exit 1 ;}
[ "$F$R" ] || R=.
t0=$(date "+%s")
# ―――――――――
[ "$cl" ] || { cl=always; GREP_COLORS='ms=01;36'; p='\x1b[38;5'; c0='\x1b[0m'
			cb="$p;75m";cc="$p;123m";cg="$p;120m";co="$p;215m";cy="$p;228m"; }
# ――――――――
if [ -p /dev/stdin ] ; then
	cat /dev/stdin | grep -$opt $v $q $mc $A --color=$cl -- "$pat" ; exit
fi
# ――――――――
p1="${pat/^/^[0-9]*:}" ; grep -q '\[' <<<"$pat" && p2=¤ || p2="${pat/^/}"
_gp() { grep -n$opt $mc -- "$pat" "$f" |sed 's#\(.\{120\}\)#\1\n#g' | \
	sed -rn "\%$p1%I{s/^[0-9]*:/${cy}& $c0/;s,$p2,${cc}&$c0,gI;p}"
	echo ; }
# ―――――――――
if [ "$F" ] ; then
  if [ "$v" ] ; then grep -ETvn $l --color=$cl -- "$pat" "${F[@]}"
    else while read f ; do sed "s,[^/]*$,${cg}&$c0," <<<"$f"
		  [ "$l" ] || _gp
		 done< <(grep -l -$opt -- "$pat" "${F[@]}")
  fi
fi

if [ "$R" ] ; then fmax=100; D=1
   [ "$v" ] && echo " > -v non compatible argument répertoire" && exit 1

msg1() { printf "――――\n» $lim$co$z$c0 fichiers sous "
	printf "$cb$(echo "${R[@]}")$c0 | depth: $co$D$c0 "
	T=$(($(date "+%s")-$t0))
	if (($T>1)) ; then S=$((T%60))s ; M=$(((T%3600)/60))mn
	   (($T<60)) && printf "| ~ $S" || printf "| ~ $M $S"
	fi
	echo ; }

   { [ "$a" ] || [[ ${R/\/.//} != $R ]] ;} && hd='/¤' || hd='.*/\..*'

  for Rx in "${R[@]}" ; do d=1 ; y=0 ; [ "$md" ] && M=$md || M=1
   while ((d<=M)) ; do ex=1
	while read f ; do
	  if ((d>1)) && [ "$ex" ] ; then ex= ; ((M>D)) && D=$M
	   printf "${co}%0.s../" $(seq 1 $((d-1))) ; printf " $d ${c0}\n"
	  fi
	  sed "s,$Rx,${cb}&$c0,;s,[^/]*$,${cg}&$c0," <<<"$f"
	  ((y++)) ; ((z++))
	  [ "$l" ] || _gp
	  # ~~~~~~~~~~~
	  if ((z>=fmax)) ; then lim='Plus de ' ; msg1
		read -t 15 -p "On continue ? [o,y / n] " q </dev/tty
		[[ $q == [oOyY]* ]] && fmax=$((fmax+100)) || { echo ; exit; }
	  fi
	  # ~~~~~~~~~~~
  	done< <(find "$Rx" -mount -follow -mindepth $d -maxdepth $d \
		\( -regex "${hd}\|\.cache\|Trash\|lost\+found\|recent"  \
		   -o ! -readable -prune \) -o -type f -print0 2>/dev/null | \
		    xargs -0 grep -l -$opt -- "$pat" |LC_ALL=C sort -V)

    ((z>20)) && l=-l
    [ -z "$md" ] && ((y<10)) && ((M<10)) && ((M++))
    ((d++))
   done
  done
  if ((z==0)); then printf "${cy}» Rien trouvé / Options possibles:\n"
	    [ "$a" ] || printf " * recherche dans les fichiers cachés: -a\n"
					printf " * modifier le répertoire de recherche ${c0}\n"
					exit 1
 elif ((z>3)); then msg1
  fi
fi
# ―――――――――
exit

Exemples de sortie (partielle):

2024-10-25_21-51

2024-10-25_2


► Afin de garder ce sujet exploitable et de prévenir d’éventuels débordements hors-sujet,
tout commentaire/question doit être abordé dans le sujet dédié: → Observations sur fgp


3 J'aime

4 messages ont été scindés en un nouveau sujet : Observations sur fgpP

Changelog 20241109

  • ajout de l’option -q,--quiet
    Bien que rarement utilisée en terminal, mais très fréquemment en script pour tester une variable sans afficher le résultat, cette option pourrait potentiellement être demandée plus tard (anticipation);

  • fgp traite normalement une variable commençant par un tiret (piège classique de grep rencontré dans ce sujet .

exemples:

man cp | grep '-deref'
   grep: argument « eref » incorrect pour « --directories »
man cp | fgp '-deref'
       -d    same as --no-dereference --preserve=links
       -L, --dereference
       -P, --no-dereference

→ fgp: fgp_20241109.txt (5,2 Ko)