Grep: résultats inattendus lors de la recherche de mots dans l'en-tête de la page de manuel

19

Je rencontre un comportement étrange en essayant de grep une page de manuel sur macOS. Par exemple, la page de manuel Bash a clairement une occurrence de la chaîne NAME:

$ man bash | head -5 | tail -1
NAME

Et si je convoite, namej'obtiens des résultats, mais si je convoite, NAMEje ne le fais pas:

$ man bash | grep 'NAME'
$ man bash | grep NAME

J'ai essayé d'autres mots en majuscules que je sais être là, et la recherche de SHELLrendements ne donne rien alors que la recherche de BASHrésultats donne.

Que se passe t-il ici?

Mise à jour : Merci pour toutes les réponses! J'ai pensé qu'il valait la peine d'ajouter le contexte dans lequel je me suis heurté à cela. Je voulais écrire une fonction bash pour envelopper manet dans les cas où j'ai essayé de rechercher la page de manuel pour un shell intégré, passez à la section appropriée de la page de manuel Bash. Il pourrait y avoir une meilleure façon, mais voici ce que j'ai actuellement:

man () {
  case "$(type -t "$1")" in
    builtin)
      local pattern="^ *$1"

      if bashdoc_match "$pattern \+[-[]"; then
        command man bash | less --pattern="$pattern +[-[]"
      elif bashdoc_match "$pattern\b"; then
        command man bash | less --pattern="$pattern[[:>:]]"
      else
        command man bash
      fi
      ;;
    keyword)
      command man bash | less --hilite-search --pattern='^SHELL GRAMMAR$'
      ;;
    *)
      command man "$@"
      ;;
  esac
}

bashdoc_match() {
  command man bash | col -b | grep -l "$1" > /dev/null
}
ivan
la source
Quel système d'exploitation utilisez-vous? Je suis sûr que la réponse acceptée est correcte, mais IO n'a pas pu la reproduire sur ma boîte Arch Linux. man bash | grep NAMEfonctionne comme prévu.
terdon
@terdon Je suis sur macOS. J'obtiens ce comportement avec Bash 3.2 et 4.4.5
ivan
Juste à part: si vous détectez une fonction intégrée, vous pouvez simplement utiliser la helpcommande bash pour obtenir ses informations.
Joe
@Joe Le problème est que je trouve souvent que les helprésultats en oublient trop. Vérifiez help completecontre la completesection dans man bash, par exemple.
ivan

Réponses:

33

Si vous ajoutez un | sed -n là cette tailcommande, pour afficher des caractères non imprimables, vous verrez probablement quelque chose comme:

N\bNA\bAM\bME\bE

Autrement dit, chaque caractère est écrit comme XBackspace X. Sur les terminaux modernes, le personnage finit par être écrit sur lui-même (comme Backspace aka BS aka \baka ^Hest le personnage qui déplace le curseur d'une colonne vers la gauche) sans différence. Mais dans les anciennes machines à écrire, cela ferait apparaître le caractère en gras car il obtient deux fois plus d'encre.

Pourtant, les pagers aiment more/ lesscomprennent ce format comme signifiant gras, c'est donc ce qui rofffait pour produire du texte en gras.

Certaines implémentations man appellent roffde manière à ce que ces séquences ne soient pas utilisées (ou appellent en interne col -b -p -xpour les supprimer comme dans le cas de l' man-dbimplémentation (à moins que la MAN_KEEP_FORMATTINGvariable d'environnement ne soit définie)), et n'invoquent pas de pager lorsqu'elles détectent la sortie ne va pas à un terminal (donc man bash | grep NAMEfonctionnerait là-bas), mais pas le vôtre.

Vous pouvez utiliser col -bpour supprimer ces séquences (il existe également d'autres types ( _BS X) pour souligner).

Pour les systèmes utilisant GNU roff(comme GNU ou FreeBSD), vous pouvez éviter que ces séquences soient utilisées en premier lieu en vous assurant que les -c -b -uoptions sont passées à grotty, par exemple en vous assurant que les -P-cbuoptions sont passées à groff.

Par exemple en créant un script wrapper appelé groffcontenant:

#! /bin/sh -
exec /usr/bin/groff -P-cbu "$@"

Que vous placez devant / usr / bin / groff $PATH.

Avec macOS ' man(utilisant également GNU roff), vous pouvez créer un man-no-overstrike.confavec:

NROFF /usr/bin/groff -mandoc -Tutf8 -P-cbu

Et appelez mancomme:

man -C man-no-overstrike.conf bash | grep NAME

Toujours avec GNU roff, si vous définissez la GROFF_SGRvariable d'environnement (ou si vous ne définissez pas la GROFF_NO_SGRvariable en fonction de la façon dont les valeurs par défaut ont été définies au moment de la compilation), alors grotty(tant qu'elle n'est pas transmise, l' -coption) utilisera des séquences d'échappement du terminal ANSI SGR à la place. de ces astuces BS pour les attributs de personnage. lessles comprendre lorsqu'ils sont appelés avec l' -Roption.

L'homme de FreeBSD appelle grottyavec l' -coption à moins que vous ne demandiez des couleurs en définissant la variable MANCOLOR (auquel cas il -cn'est pas passé à grottyet grottyrevient à la valeur par défaut de l'utilisation des séquences d'échappement ANSI SGR là-bas).

MANCOLOR=1 man bash | grep NAME

fonctionnera là-bas.

Sur Debian, GROFF_SGR n'est pas la valeur par défaut. Si tu fais:

GROFF_SGR=1 man bash | grep NAME

cependant, parce que manstdout n'est pas un terminal, il prend sur lui de passer également une GROFF_NO_SGRvariable à grotty(je suppose donc qu'il peut utiliser col -bpxpour supprimer les séquences BS car colil ne sait pas comment supprimer les séquences SGR, même s'il reste le fait avec MAN_KEEP_FORMATTING) qui l'emporte sur notre GROFF_SGR. Vous pouvez faire à la place:

GROFF_SGR=1 MANPAGER='grep NAME' man bash

(dans un terminal) pour avoir les séquences d'échappement SGR.

Cette fois, vous remarquerez que certains de ces NOM apparaissent en gras sur le terminal (et dans un less -Rpager). Si vous alimentez la sortie en sed -n l( MANPAGER='sed -n /NAME/l'), vous verrez quelque chose comme:

\033[1mNAME\033[0m$

\e[1mest la séquence pour activer le gras dans les terminaux compatibles ANSI, et \e[0mla séquence pour rétablir tous les attributs SGR par défaut.

Sur ce texte grep NAMEfonctionne comme ce texte contient NAME, mais vous pouvez toujours avoir des problèmes si vous recherchez du texte dont seules certaines parties sont en gras / souligné ...

Stéphane Chazelas
la source
2
Wow, assez intéressant de voir l'héritage du télé-type physique là-bas. Deux fois plus d'encre => gras. C'est parfaitement logique
ivan
1
J'aime en sed -n ltant que substitut od.
Tom Hale
13

Si vous consultez une page de manuel, vous remarquerez que les en-têtes sont en gras. Ceci est réalisé en les formatant avec des caractères de contrôle. Pour pouvoir grepaimer ce que vous voulez, ceux-ci doivent être supprimés.

L' colutilitaire peut être utilisé pour cela:

$ man bash | col -b | grep 'NAME'

L' -boption a la description suivante sur OpenBSD :

Ne sortez aucun espace arrière, imprimant uniquement le dernier caractère écrit à chaque position de colonne. Cela peut être utile pour traiter la sortie de mandoc (1).


Linux le colmanuel (sur Ubuntu) ne contient pas la dernière phrase (mais il fonctionne de la même manière).

Sous Linux, la suppression de la MAN_KEEP_FORMATTINGvariable d'environnement (ou sa définition sur une chaîne vide) peut également aider et vous permettra de ne greppas transmettre la sortie de manthrough col -b.

Kusalananda
la source
Je pense (comme je l'ai testé sur un système Arch et Ubuntu) que sous Linux ce n'est pas nécessaire, ou plus. Sur les deux systèmes, le NAMEdans le manuel bash est juste NAME, non \b.
terdon
@terdon Je n'ai pas remarqué la mention de macOS en premier, donc j'ai supposé qu'un système Linux mal configuré était une possibilité. J'ai maintenant coupé les bits Linux.
Kusalananda
Vous n'avez rien manqué, j'ai demandé à l'OP quel OS ils utilisent parce que je ne pouvais pas reproduire sous Linux, ils ont dit macOS et je l'ai ajouté maintenant. Et je n'impliquais pas que vous vous trompiez, pour autant que je sache, il existe des distributions Linux où la MAN_KEEP_FORMATTINGvariable fonctionne exactement comme vous le dites. Je voulais juste souligner que ce n'est pas toujours le cas.
terdon