Grep: resultados inesperados ao procurar palavras no cabeçalho da página de manual

19

Estou tendo um comportamento estranho ao tentar grep uma página de manual no macOS. Por exemplo, a página do manual Bash claramente tem uma ocorrência da sequência NAME:

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

E se eu grep for name, obtenho resultados, mas se eu grep for NAME, não:

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

Eu tentei outras palavras em maiúsculas que eu sei que estão lá, e a pesquisa SHELLnão produz nada, enquanto a pesquisa BASHproduz resultados.

Oque esta acontecendo aqui?

Atualização : Obrigado por todas as respostas! Eu pensei que valeria a pena adicionar o contexto em que me deparei com isso. Eu queria escrever uma função bash para quebrar mane, nos casos em que tentei procurar na página do manual por um shell incorporado, pule para a seção relevante da página do manual Bash. Pode haver uma maneira melhor, mas aqui está o que eu tenho atualmente:

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
fonte
Qual sistema operacional você está usando? Tenho certeza de que a resposta aceita está correta, mas o IO não conseguiu reproduzi-la na minha caixa do Arch Linux. man bash | grep NAMEfunciona como esperado.
terdon
@terdon Estou no macOS. Eu recebo esse comportamento com o Bash 3.2 e 4.4.5
ivan
Apenas como um aparte: se você detectar um built-in, poderá usar o helpcomando bash para obter suas informações.
Joe
@ Joe O problema é que muitas vezes acho que os helpresultados deixam muito de fora. Confira help completea completeseção em man bash, por exemplo.
ivan

Respostas:

33

Se você adicionar um | sed -n la esse tailcomando, para mostrar caracteres não imprimíveis, provavelmente verá algo como:

N\bNA\bAM\bME\bE

Ou seja, cada caractere é escrito como XBackspace X. Nos terminais modernos, o caractere acaba sendo escrito sobre si mesmo (como Backspace aka BS aka \baka ^Hé o caractere que move o cursor uma coluna para a esquerda) sem diferença. Mas nas tele-máquinas de escrever antigas, isso faria com que o personagem aparecesse em negrito, pois recebe duas vezes mais tinta.

Ainda assim, os pagers gostam more/ lessentendem esse formato como negrito, e ainda é o que rofffaz com a saída de texto em negrito.

Algumas implementações humanas chamariam de roffmaneira que essas seqüências não sejam usadas (ou chamarão internamente col -b -p -xpara removê-las como no caso da man-dbimplementação (a menos que a MAN_KEEP_FORMATTINGvariável de ambiente esteja definida)) e não invoquem um pager quando detectarem a saída não está indo para um terminal (por man bash | grep NAMEisso funcionaria lá), mas não o seu.

Você pode usar col -bpara remover essas seqüências (existem outros tipos ( _BS X) e também sublinhados).

Para sistemas que usam GNU roff(como GNU ou FreeBSD), você pode evitar que essas sequências sejam usadas em primeiro lugar, certificando-se de que as -c -b -uopções sejam passadas grotty, por exemplo, certificando-se de que as -P-cbuopções sejam passadas groff.

Por exemplo, criando um script de wrapper chamado groffcontendo:

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

Que você coloca à frente de / usr / bin / groff $PATH.

Com o macOS ' man(também usando o GNU roff), você pode criar um man-no-overstrike.confcom:

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

E ligue mancomo:

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

Ainda com o GNU roff, se você definir a GROFF_SGRvariável de ambiente (ou não a GROFF_NO_SGRvariável, dependendo de como os padrões foram definidos no tempo de compilação), então grotty(contanto que não seja aprovada a -copção) usará as seqüências de escape do terminal ANSI SGR desses truques de BS para atributos de caracteres. lessentendê-los quando chamados com a -Ropção

O homem do FreeBSD chama grottycom a -copção, a menos que você esteja solicitando cores , configurando a variável MANCOLOR (nesse caso, -cnão é passada para grottye grottyvolta ao padrão de usar as seqüências de escape ANSI SGR).

MANCOLOR=1 man bash | grep NAME

vai trabalhar lá.

No Debian, GROFF_SGR não é o padrão. Se você fizer:

GROFF_SGR=1 man bash | grep NAME

no entanto, como mano stdout não é um terminal, também é possível passar uma GROFF_NO_SGRvariável para grotty(suponho que ele possa ser usado col -bpxpara extrair as seqüências BS, pois colnão sabe como extrair as seqüências SGR, mesmo que ainda faz isso com MAN_KEEP_FORMATTING) que substitui o nosso GROFF_SGR. Você pode fazer:

GROFF_SGR=1 MANPAGER='grep NAME' man bash

(em um terminal) para ter as seqüências de escape do SGR.

Nesse momento, você notará que alguns desses NOME s aparecem em negrito no terminal (e em um less -Rpager). Se você alimentar a saída para sed -n l( MANPAGER='sed -n /NAME/l'), verá algo como:

\033[1mNAME\033[0m$

Onde \e[1mestá a sequência para ativar negrito nos terminais compatíveis com ANSI e \e[0ma sequência para reverter todos os atributos SGR para o padrão.

Nesse texto grep NAMEfunciona como o texto contém NAME, mas você ainda pode ter problemas se procurar texto em que apenas partes dele estejam em negrito / sublinhado ...

Stéphane Chazelas
fonte
2
Uau, muito interessante ver o legado do tele-tipo físico lá. O dobro de tinta => negrito. Faz todo o sentido
ivan
1
estou amando sed -n l como um substituto od.
Tom Hale
13

Se você olhar para qualquer página de manual, notará que os cabeçalhos estão em negrito. Isso é conseguido através da formatação com caracteres de controle. Para poder grepgostar do que você quer, eles precisam ser retirados.

O colutilitário pode ser usado para isso:

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

A -bopção possui a seguinte descrição no OpenBSD :

Não produza nenhum backspaces, imprimindo apenas o último caractere gravado em cada posição da coluna. Isso pode ser útil no processamento da saída do mandoc (1).


O colmanual do Linux (no Ubuntu) não tem a última frase lá (mas funciona da mesma maneira).

No Linux, desmarcar a MAN_KEEP_FORMATTINGvariável de ambiente (ou defini-la como uma sequência vazia) também pode ajudar e permitirá que vocêgrep , sem passar a saída do manmeio col -b.

Kusalananda
fonte
Eu acho (como eu testei isso em um sistema Arch e Ubuntu) que no Linux isso não é necessário ou não é mais. Nos dois sistemas, o NAMEmanual do bash é apenas NAME, não \b.
terdon
@terdon Não identifiquei a menção do macOS primeiro, então presumi que um sistema Linux configurado incorretamente era uma possibilidade. Agora eu aparei os bits do Linux.
Kusalananda
Você não perdeu nada, perguntei ao OP qual sistema operacional eles estavam usando porque não consegui reproduzir no Linux, disseram o macOS e eu o adicionei agora. E eu não estava sugerindo que você estava errado, pois sei que existem distribuições Linux por aí em que a MAN_KEEP_FORMATTINGvariável funciona exatamente como você diz. Eu só queria ressaltar que nem sempre é o caso.
Terdon