当我尝试在 macOS 上 grep 手册页时,遇到了奇怪的行为。例如,Bash 手册页明显出现了以下字符串NAME
:
$ man bash | head -5 | tail -1
NAME
如果我 grep forname
我确实得到了结果,但如果我 grep forNAME
我没有:
$ man bash | grep 'NAME'
$ man bash | grep NAME
我已经尝试过我知道的其他大写单词,并且搜索SHELL
不会产生任何结果,而搜索会BASH
产生结果。
这里发生了什么?
更新: 感谢所有的答案!我认为值得添加我遇到这个问题的背景。我想编写一个 bash 函数来包装man
,如果我尝试查找 shell 内置函数的手册页,请跳转到 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
}
答案1
如果您| sed -n l
向该tail
命令添加 a 以显示不可打印的字符,您可能会看到类似以下内容的内容:
N\bNA\bAM\bME\bE
也就是说,每个字符都写为X
Backspace X
。在现代终端上,字符最终会被自己覆盖(因为 Backspace 又名 BS 又名\b
是^H
将光标向左移动一列的字符),没有任何区别。但在古代的电传打字机中,这会导致字符以粗体显示,因为它的墨水量是原来的两倍。
尽管如此,像more
/这样的寻呼机less
确实理解该格式意味着粗体,所以这仍然是roff
输出粗体文本的方法。
一些 man 实现会roff
以不使用这些序列的方式调用(或者col -b -p -x
像实现的情况一样在内部调用以剥离它们man-db
(除非MAN_KEEP_FORMATTING
设置了环境变量)),并且在检测到输出时不调用寻呼机不会去终端(所以man bash | grep NAME
可以在那里工作),但不是你的。
您可以使用col -b
来删除这些序列(还有其他类型(_
BS X
)以及下划线)。
对于使用 GNU 的系统(如 GNU 或 FreeBSD),您可以通过确保选项传递到 来roff
避免首先使用这些序列,例如通过确保选项传递到。-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
不设置变量),那么(只要没有传递该选项)将使用 ANSI SGR 终端转义序列那些关于角色属性的废话。当使用选项调用时理解它们。GROFF_NO_SGR
grotty
-c
less
-R
FreeBSD 的 man 会grotty
调用该-c
选项,除非你要求颜色通过设置 MANCOLOR 变量(在这种情况下-c
不会传递到grotty
并grotty
恢复为使用 ANSI SGR 转义序列的默认值)。
MANCOLOR=1 man bash | grep NAME
将在那里工作。
在 Debian 上,GROFF_SGR 不是默认值。如果您这样做:
GROFF_SGR=1 man bash | grep NAME
然而,因为man
's stdout 不是终端,它本身也将变量传递GROFF_NO_SGR
给grotty
(我想它可以用来col -bpx
剥离 BS 序列,因为col
不知道如何剥离 SGR 序列,即使它仍然与MAN_KEEP_FORMATTING
)一起执行,它会覆盖我们的GROFF_SGR
.你可以这样做:
GROFF_SGR=1 MANPAGER='grep NAME' man bash
(在终端中)获得 SGR 转义序列。
那时,你会注意到其中一些姓名s 在终端(和寻呼机)上以粗体显示less -R
。如果将输出输入sed -n l
( MANPAGER='sed -n /NAME/l'
),您将看到类似以下内容:
\033[1mNAME\033[0m$
其中\e[1m
是在 ANSI 兼容终端中启用粗体的序列,以及\e[0m
将所有 SGR 属性恢复为默认值的序列。
该文本的grep NAME
工作原理是该文本确实包含NAME
,但如果查找仅部分内容为粗体/下划线的文本,您仍然可能会遇到问题...
答案2
如果您查看任何手册页,您会发现标题以粗体显示。这是通过使用控制字符格式化它们来实现的。为了能够grep
喜欢你想要的,这些都必须被剔除。
该col
实用程序可用于此目的:
$ man bash | col -b | grep 'NAME'
该-b
选项有以下描述在 OpenBSD 上:
不输出任何退格键,仅打印写入每列位置的最后一个字符。这对于处理 mandoc(1) 的输出很有用。
Linuxcol
手册(在 Ubuntu 上)没有最后一句(但它的工作方式相同)。
在 Linux 上,取消设置MAN_KEEP_FORMATTING
环境变量(或将其设置为空字符串)也可能有所帮助,并且允许您无需传递throughgrep
的输出。man
col -b