为什么 shell 内置函数没有适当的手册页?

Dis*_*ame 33 command-line osx freebsd man

所有 shell 内置函数共享相同的手册页:

BUILTIN(1)                BSD General Commands Manual               BUILTIN(1)

NAME
     builtin, !
Run Code Online (Sandbox Code Playgroud)

等等。

然后有一小段文字描述了什么是 shell 内置函数,然后是一个看起来像这样的列表:

  Command       External    csh(1)    sh(1)
       !             No          No        Yes
       %             No          Yes       No
Run Code Online (Sandbox Code Playgroud)

但是如果我们这样做,man grep我们会得到诸如

  • 错误
  • 历史
  • 也可以看看
  • 标准
  • 描述

等等。

难道 shell 内置函数没有自己的历史、描述和参数,比如-A-r?为什么手册页中没有提供,我将如何学习正确有效地使用它们?

ter*_*don 26

因为内置函数是 shell 的一部分。他们拥有的任何错误或历史都是 shell 本身的错误和历史。它们不是独立的命令,也不存在于它们内置的 shell 之外。

bash至少,等效的是help命令。例如:

$ help while
while: while COMMANDS; do COMMANDS; done
    Execute commands as long as a test succeeds.

    Expand and execute COMMANDS as long as the final command in the
    `while' COMMANDS has an exit status of zero.

    Exit Status:
    Returns the status of the last command executed.
Run Code Online (Sandbox Code Playgroud)

所有 bash 内置函数都有help页面。甚至help自己:

$ help help
help: help [-dms] [pattern ...]
    Display information about builtin commands.

    Displays brief summaries of builtin commands.  If PATTERN is
    specified, gives detailed help on all commands matching PATTERN,
    otherwise the list of help topics is printed.

    Options:
      -d    output short description for each topic
      -m    display usage in pseudo-manpage format
      -s    output only a short usage synopsis for each topic matching
        PATTERN

    Arguments:
      PATTERN   Pattern specifiying a help topic

    Exit Status:
    Returns success unless PATTERN is not found or an invalid option is given.
Run Code Online (Sandbox Code Playgroud)

受@mikeservsed脚本的启发,这里有一个小函数,可以使用 Perl 打印手册页的相关部分。将此行添加到 shell 的初始化文件(~/.bashrc对于 bash):

manperl(){ man "$1" | perl -00ne "print if /^\s*$2\b/"; }
Run Code Online (Sandbox Code Playgroud)

然后,通过给它一个手册页和一个部分的名称来运行它:

$ manperl bash while
       while list-1; do list-2; done
       until list-1; do list-2; done
              The while command continuously executes the list list-2 as long as the last command in the list list-1 returns an exit
              status of zero.  The until command is identical to the while command, except that the test is negated; list-2 is  exe?
              cuted  as  long  as the last command in list-1 returns a non-zero exit status.  The exit status of the while and until
              commands is the exit status of the last command executed in list-2, or zero if none was executed.

$ manperl grep SYNOPSIS
SYNOPSIS
       grep [OPTIONS] PATTERN [FILE...]
       grep [OPTIONS] [-e PATTERN | -f FILE] [FILE...]

$ manperl rsync "-r"
       -r, --recursive
              This tells rsync to copy directories recursively.  See also --dirs (-d).
Run Code Online (Sandbox Code Playgroud)

  • @FrancisDavey:但是大多数内置函数都存在于各种 shell 中(具有不同的扩展名)。联机帮助页不是特定于 shell 的;它们是全系统的。 (4认同)
  • 不清楚的是为什么没有给他们手册页。手册页只是 MANPATH 上的文件。它们不必对应于单独的二进制文件。原则上没有理由为什么 bash 不能为其内置程序提供手册页——而不是拥有一个内部帮助系统。 (3认同)
  • @DisplayName 他们_are_ bash。它们是其中的一部分,是的,它们在 `bash` 手册页的 `SHELL BUILTIN COMMANDS` 部分进行了解释。他们的“手册页”是`help builtin_name`。 (2认同)
  • @FrancisDavey 正如 rici 所说,命令不是系统范围的。为不存在于每个 shell 中的命令提供手册页会有点误导,但更糟糕的是,为 * 存在于多个 shell 中但行为不同的命令提供手册页会非常混乱(例如,接受不同的参数,具有不同的语法等)。 (2认同)
  • @mikeserv 但是,我*会*欢迎 shell 内置程序的手册页,例如 git 提供的内容,其中 `man git commit` 为 `git-commit` 提供了一个手册页。像`man bash if`这样的东西会*精彩*。 (2认同)

mik*_*erv 5

虽然某些 shell 内置函数确实可能在完整手册中很少显示——特别是对于那些bash你可能只在 GNU 系统上使用的特定内置函数(GNU 的人,作为一项规则,不相信man和更喜欢他们自己的info页面) - 绝大多数 POSIX 实用程序 - shell 内置程序或其他 - 在 POSIX 程序员指南中得到了很好的体现。

这是我的底部摘录man sh (大概有 20 页左右......)

在此处输入图片说明

所有这些都在那里,和其他人没有提到这样的setreadbreak...好,我不需要给它们命名所有。但请注意(1P)右下角 - 它表示 POSIX 类别 1 手册系列 - 这些是man我正在谈论的页面。

可能你只需要安装一个包?对于 Debian 系统来说看起来很有希望。虽然help很有用,但如果你能找到它,你绝对应该得到那个POSIX Programmer's Guide系列。它可能非常有帮助。它的组成页面非常详细。

除此之外,shell 内置函数几乎总是列在特定 shell 手册的特定部分中。zsh,例如,有一个完整的单独man页面 - (我认为它总共有 8 或 9 个左右的单独zsh页面 - 包括zshall很大的页面。)

grep man当然可以:

man bash 2>/dev/null | 
grep '^[[:blank:]]*read [^`]*[-[]' -A14

   read [-ers] [-a aname] [-d  delim]  [-i  text]  [-n
   nchars]  [-N  nchars]  [-p prompt] [-t timeout] [-u
   fd] [name ...]
          One line is read from the standard input, or
          from  the  file descriptor fd supplied as an
          argument to the -u  option,  and  the  first
          word is assigned to the first name, the sec?
          ond word to the second name, and so on, with
          leftover words and their intervening separa?
          tors assigned to the last  name.   If  there
          are  fewer  words read from the input stream
          than names, the remaining names are assigned
          empty  values.   The  characters  in IFS are
          used to split the line into words using  the
          same  rules  the  shell  uses  for expansion
Run Code Online (Sandbox Code Playgroud)

...这与我以前搜索 shellman页面时所做的非常接近。但在大多数情况下help是相当不错的bash

我最近一直在sed编写一个脚本来处理这种事情。这就是我如何抓住上图中的部分。它仍然比我喜欢的要长,但它正在改进 - 并且非常方便。在当前的迭代中,它将非常可靠地提取上下文相关的文本部分,以根据命令行上给定的 [a] 模式 [s] 与部分或小节标题匹配。它为输出着色并打印到标准输出。

它通过评估缩进级别来工作。非空的输入行一般会被忽略,但是遇到空行就开始注意了。它从那里收集行,直到它确认当前序列在另一个空行出现之前肯定比它的第一行缩进得更远,否则它会放弃线程并等待下一个空白。如果测试成功,它会尝试将引导行与其命令行参数进行匹配。

这意味着匹配模式将匹配:

heading
    match ...
    ...
    ...
        text...
Run Code Online (Sandbox Code Playgroud)

..和..

match
   text
Run Code Online (Sandbox Code Playgroud)

..但不是..

heading
    match
    match

    notmatch
Run Code Online (Sandbox Code Playgroud)

..或者..

         text

         match
         match
         text

         more text
Run Code Online (Sandbox Code Playgroud)

如果可以匹配,它就会开始打印。它将从它打印的所有行中去除匹配行的前导空白 - 因此,无论它发现该行的缩进级别如何,它都会像在顶部一样打印它。它将继续打印,直到遇到与其匹配行相同或小于缩进级别的另一行 - 因此整个部分仅通过标题匹配被抓取,包括任何/所有小节,它们可能包含的段落。

所以基本上,如果你要求它匹配一个模式,它只会针对某种主题标题进行匹配,并且会着色和打印它在匹配标题的部分中找到的所有文本。除了第一行的缩进之外,它不会保存任何内容 - 因此它可以非常快地处理\n几乎任何大小的 ewline 分隔输入。

我花了一段时间才弄清楚如何递归到如下副标题:

Section Heading
    Subsection Heading
Run Code Online (Sandbox Code Playgroud)

但我最终解决了它。

不过,为了简单起见,我确实不得不重新设计整个过程。虽然之前我有几个小循环以稍微不同的方式做大部分相同的事情以适应它们的上下文,但通过改变它们的递归方式,我设法对大部分代码进行了去重。现在有两个循环 - 一个打印和一个检查缩进。两者都依赖于相同的测试——测试通过时打印循环开始,当测试失败或从空行开始时缩进循环接管。

整个过程非常快,因为大多数时候它只是/./d删除任何非空白行并移动到下一行 - 甚至zshall是立即填充屏幕的结果。这没有改变。

无论如何,到目前为止,它非常有用。例如,read上面的事情可以这样做:

mansed bash read
Run Code Online (Sandbox Code Playgroud)

...它得到了整个块。它可以采用任何模式或其他任何东西,或多个参数,尽管第一个始终是man它应该搜索的页面。这是我完成后的一些输出的图片:

mansed bash read printf
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

...两个块都完整返回。我经常像这样使用它:

mansed ksh '[Cc]ommand.*'
Run Code Online (Sandbox Code Playgroud)

...它非常有用。此外,获取SYNOPS[ES]使它非常方便:

在此处输入图片说明

如果您想试一试,那就在这里 - 如果您不这样做,我不会责怪您。

mansed() {
MAN_KEEP_FORMATTING=1 man "$1" 2>/dev/null | ( shift
b='[:blank:]' s='[:space:]' bs=$(printf \\b) esc=$(printf '\033\[') n='\
' match=$(printf "\([${b}]*%s[${b}].*\)*" "$@")
sed -n "1p
    /\n/!{  /./{    \$p;d
        };x;    /.*\n/!g;s///;x
    :indent
        /.*\n\n/{s///;x
        };n;\$p;
        /^\([^${s}].*\)*$/{s/./ &/;h;   b indent
        };x;    s/.*\n[^-[]*\n.*//; /./!x;t
        s/[${s}]*$//;   s/\n[${b}]\{2,\}/${n} /;G;h
    };
    #test
    /^\([${b}]*\)\([^${b}].*\n\)\1\([${b}]\)/!b indent
        s//\1\2.\3/
    :print
    /^[${s}]*\n\./{ s///;s/\n\./${n}/
        /${bs}/{s/\n/ & /g;
            s/\(\(.\)${bs}\2\)\{1,\}/${esc}38;5;35m&${esc}0m/g
            s/\(_${bs}[^_]\)\{1,\}/${esc}38;5;75m&${esc}0m/g
            s/.${bs}//g;s/ \n /${n}/g
            s/\(\(${esc}\)0m\2[^m]*m[_ ]\{,2\}\)\{2\}/_/g
        };p;g;N;/\n$/!D
        s//./;  t print
    };
    #match
        s/\n.*/ /;  s/.${bs}//g
        s/^\(${match}\).*/${n}\1/
        /../{   s/^\([${s}]*\)\(.*\)/\1${n}/
        x;  s//${n}\1${n}. \2/; P
    };D
");}
Run Code Online (Sandbox Code Playgroud)

简而言之,工作流程是:

  • 任何非空白行且不包含\newline 字符的行都将从输出中删除。
    • \n输入模式空间中永远不会出现 ewline 字符。它们只能作为编辑的结果。
  • :print:indent都是相互依赖的闭环,是获得\newline的唯一方法。
    • :print如果一行的前导字符是一系列空格后跟一个\newline 字符,则循环开始。
    • :indent的循环从空白行开始 - 或在:print失败的循环行上#test- 但从其输出中:indent删除所有前导空白 + \newline 序列。
    • 一旦:print开始,它将继续拉入输入行,将前导空白去除到其循环中第一行的数量,将重击和欠击退格转义转换为彩色终端转义,并打印结果直到#test失败。
    • :indent开始之前,它首先检查h旧空间是否有任何可能的缩进延续(例如子节),然后只要#test失败就继续拉入输入并且第一行之后的任何行继续匹配[-。当第一行之后的行与该模式不匹配时,它将被删除 - 随后所有行都将被删除,直到下一个空行。
  • #match#test桥接两个闭环。
    • #test当前导空白系列短于\n行序列中紧随其后的最后一个ewline的系列时,通过。
    • #match\n开始:print循环所需的前导ewlines 前置到任何:indent与任何命令行参数匹配的输出序列。那些不呈现为空的序列 - 产生的空行被传递回:indent.

  • 你的sed-fu很强。当然,你可以用`manperl(){ man $1 | 做同样的事情。perl -00ne "如果 /^\s*$2\b/ 则打印"; }` 然后是 `manperl sh SYNOPSIS` 或 `manperl sh read` :) (2认同)