为什么 shell 内置命令没有合适的手册页?

为什么 shell 内置命令没有合适的手册页?

所有 shell 内置命令共享相同的手册页:

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

NAME
     builtin, !

ETC。

然后有一小段文字描述了 shell 内置命令是什么,然后是一个类似这样的列表:

  Command       External    csh(1)    sh(1)
       !             No          No        Yes
       %             No          Yes       No

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

  • 虫子
  • 历史
  • 也可以看看
  • 标准
  • 描述

ETC。

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

答案1

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

至少等效的bashhelp命令。例如:

$ 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.

所有 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.

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

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

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

$ 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).

答案2

虽然确实有些 shell 内置函数在完整的手册中可能很少显示 - 特别是对于那些bash您只可能在 GNU 系统上使用的特定内置函数(GNU 人员通常不相信man并且更喜欢他们自己的info页面)- 绝大多数 POSIX 实用程序 - shell 内置程序或其他 - 在 POSIX 程序员指南中都有很好的介绍。

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

在此输入图像描述

所有这些都在那里,还有其他未提及的,例如,,set……好吧,我不需要将它们全部命名。但请注意右下角的 - 它表示 POSIX 类别 1 手册系列 - 这些是我正在谈论的页面。readbreak(1P)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

...这与我过去搜索 shell 页面时所做的非常接近man。但在大多数情况下都help相当不错。bash

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

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

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

heading
    match ...
    ...
    ...
        text...

..和..

match
   text

..但不是..

heading
    match
    match

    notmatch

..或者..

         text

         match
         match
         text

         more text

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

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

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

Section Heading
    Subsection Heading

但我最终还是解决了。

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

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

不管怎样,到目前为止它还是非常有用的。例如,read上面的事情可以这样完成:

mansed bash read

...它得到了整个街区。它可以采用任何模式或任何内容,或多个参数,尽管第一个参数始终是man它应该搜索的页面。这是一张照片一些我这样做后的输出:

mansed bash read printf

在此输入图像描述

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

mansed ksh '[Cc]ommand.*'

...这非常有用。此外,获取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
");}

简而言之,工作流程是:

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

答案3

每个 shell 都有自己的一组内置函数。虽然存在共性,但它们都有自己的特点,需要记录下来。

在 Linux 和 FreeBSD(以及继承自 FreeBSD 的 OSX)等系统上,每个 shell 都是作为单独的包提供的,因此没有内置命令的手册页;相反,每个内置函数都记录在 shell 的手册页中。因此,请阅读 bash 手册页以获取 bash 内置函数的文档kill,阅读 dash 手册页以获取 dash 内置函数的文档kill,等等。还有一个kill独立实用程序的手册页。

我可以获得 bash 内置命令的单独手册页吗?man如果参数是内置函数的名称,则显示 bash 内部文档而不是手册页的函数。

有一些 Unix 变体提供了 shell 内置命令的手册页——事实上,大多数商业变体都是这样做的。这是可行的,因为系统带有单个 shell 或一组已知的 shell。手册页讨论了 shell 之间的差异。例如,fg(1)Solaris 10 的手册页shksh和部分csh。这fg(1)AIX 7.1 上的手册页引用“Korn shell”和“POSIX shell”,但将它们放在一起讨论(它们恰好支持完全相同的功能fg)。这fg(1)Tru64 5.0 的手册页讨论 ksh 内置函数并让 csh 用户参考csh(1)手册页。上合组织显然带有一个外壳。您可以在这些操作系统上安装其他 shell 作为附加包;如果您使用自定义 shell,则必须记住,使用非默认 shell 时,内置函数的手册页将不相关。

相关内容