Grep: неожиданные результаты при поиске слов в заголовке со страницы руководства

19

Я сталкиваюсь со странным поведением, когда пытаюсь открыть man-страницу в macOS. Например, страница руководства Bash явно содержит строку NAME:

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

И если я grep для nameя получаю результаты, но если я grep для NAMEя не:

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

Я пробовал другие прописные слова, которые я знаю, там, и поиск SHELLничего не дает, тогда как поиск BASHрезультатов дает.

Что тут происходит?

Обновление : спасибо за все ответы! Я думал, что стоит добавить контекст, в котором я столкнулся с этим. Я хотел написать функцию bash для переноса, manи в тех случаях, когда я пытался найти справочную страницу для встроенной оболочки, переходил к соответствующему разделу справочной страницы Bash. Возможно, есть лучший способ, но вот что я получил в настоящее время:

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
}
иван
источник
Какую операционную систему ты используешь? Я уверен, что принятый ответ правильный, но IO не смог воспроизвести это на моем компьютере Arch Linux. man bash | grep NAMEработает как положено.
Тердон
@terdon Я на MacOS. Я получаю это поведение с Bash 3.2 и 4.4.5
иван
В качестве отступления: если вы обнаружите встроенную функцию, вы можете просто использовать команду bash, helpчтобы получить ее информацию.
Джо
@ Joe Проблема в том, что я часто нахожу helpрезультаты слишком большими. Проверьте help completeпротив completeраздела в man bash, например.
иван

Ответы:

33

Если вы добавите | sed -n lк этой tailкоманде, чтобы показать непечатаемые символы, вы, вероятно, увидите что-то вроде:

N\bNA\bAM\bME\bE

То есть каждый символ записывается как XBackspace X. На современных терминалах символ заканчивается написанием над собой (так как Backspace или BS, он \bже aka ^H- это символ, который перемещает курсор на один столбец влево) без разницы. Но в древних телетайпах это приводило к тому, что персонаж выделялся жирным шрифтом, поскольку чернил в два раза больше.

Тем не менее, пейджеры любят more/ lessдействительно понимают, что формат означает жирный шрифт, так что это по-прежнему то, roffчто выводит жирный текст

Некоторые реализации man вызывают roffтаким образом, что эти последовательности не используются (или внутренне вызывают col -b -p -xдля их удаления, как в случае man-dbреализации (если не установлена MAN_KEEP_FORMATTINGпеременная окружения)), и не вызывают пейджер, когда обнаруживают вывод не собирается в терминал (так man bash | grep NAMEбудет работать там), но не ваш.

Вы можете использовать col -bдля удаления этих последовательностей (есть и другие типы ( _BS X), а также для подчеркивания).

Для систем, использующих GNU roff(например, GNU или FreeBSD), вы можете избежать использования этих последовательностей в первую очередь, убедившись, что -c -b -uопции переданы grotty, например, убедившись, что -P-cbuопции переданы groff.

Например, путем создания сценария-оболочки, который groffсодержит:

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

Что вы поставили перед / usr / bin / groff $PATH.

С macOS ' man(также с использованием GNU roff) вы можете создать man-no-overstrike.confс:

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

И называть manкак:

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

Тем не менее, в GNU roff, если вы установите GROFF_SGRпеременную среды (или не установите GROFF_NO_SGRпеременную в зависимости от того, как были установлены значения по умолчанию во время компиляции), тогда grotty(если она не была передана -c), вместо этого будут использоваться escape-последовательности терминала ANSI SGR из этих трюков BS для атрибутов персонажа. lessпонять их при вызове с -Rопцией.

Человек FreeBSD вызывает grottyс -cопцией, если вы не запрашиваете цвета , устанавливая переменную MANCOLOR (в этом случае -cона не передается grottyи grottyвозвращается к использованию по умолчанию escape-последовательностей ANSI SGR).

MANCOLOR=1 man bash | grep NAME

будет работать там.

В Debian GROFF_SGR не используется по умолчанию. Если вы делаете:

GROFF_SGR=1 man bash | grep NAME

однако, поскольку manstdout не является терминалом, он также берет на себя передачу GROFF_NO_SGRпеременной в grotty(я полагаю, поэтому он может использовать col -bpxдля удаления последовательностей BS, поскольку colне знает, как удалять последовательности SGR, даже если он все еще делает это с MAN_KEEP_FORMATTING) который отменяет наш GROFF_SGR. Вы можете сделать вместо этого:

GROFF_SGR=1 MANPAGER='grep NAME' man bash

(в терминале) иметь escape-последовательности SGR.

В этот раз вы заметите, что некоторые из этих ИМЯ отображаются на терминале жирным шрифтом (и на less -Rпейджере). Если вы передадите вывод в sed -n l( MANPAGER='sed -n /NAME/l'), вы увидите что-то вроде:

\033[1mNAME\033[0m$

Где \e[1mпоследовательность включения жирного шрифта в ANSI-совместимых терминалах и \e[0mпоследовательность для возврата всех атрибутов SGR к значениям по умолчанию.

Этот текст grep NAMEработает так же, как этот текст NAME, но у вас все еще могут быть проблемы, если вы ищете текст, где только его части выделены жирным шрифтом / подчеркиванием ...

Стефан Шазелас
источник
2
Вау, довольно интересно увидеть наследие физического телетайпа. В два раза больше чернил => жирным шрифтом. Имеет смысл
иван
1
Я люблю sed -n lкак замену od.
Том Хейл,
13

Если вы посмотрите на любую страницу руководства, вы заметите, что заголовки выделены жирным шрифтом. Это достигается путем форматирования их с помощью управляющих символов. Чтобы быть в состоянии, grepкак вы хотите, они должны быть удалены.

colУтилита может использоваться для этого:

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

-bВариант имеет следующее описание на OpenBSD :

Не выводите никаких пробелов, печатая только последний символ, записанный в каждой позиции столбца. Это может быть полезно при обработке выходных данных mandoc (1).


В colруководстве по Linux (для Ubuntu) нет последнего предложения (но оно работает точно так же).

В Linux MAN_KEEP_FORMATTINGтакже может помочь сброс переменной среды (или установка ее в пустую строку), которая позволит вам grepне передавать выходные данные manчерез col -b.

Кусалананда
источник
Я думаю (как я проверял это на Arch и в системе Ubuntu), что в Linux это не нужно или больше не нужно. В обеих системах руководство NAMEпо bash просто NAMEнет \b.
Тердон
@terdon Я не заметил упоминания macOS первым, поэтому предположил, что неправильно настроенная система Linux была возможной. Я теперь обрезал биты Linux.
Кусалананда
Вы ничего не пропустили, я спросил ОП, какую ОС они используют, потому что я не мог воспроизвести в Linux, они ответили macOS, и я только что добавил ее. И я не имел в виду, что вы ошиблись, поскольку я знаю, что существуют дистрибутивы Linux, в которых MAN_KEEP_FORMATTINGпеременная работает точно так же, как вы говорите. Я просто хотел отметить, что это не всегда так.
Тердон