Why don't shell builtins have proper man pages?
Because builtins are part of the shell. Any bugs or history they have are bugs and history of the shell itself. They are not independent commands and don't exist outside the shell they are built into.
The equivalent, for bash
at least, is the help
command. For example:
$ 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.
All bash builtins have help
pages. Even help
itself:
$ 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.
Inspired by @mikeserv's sed
script, here's a little function that will print the relevant section of a man page using Perl. Add this line to your shell's initialization file (~/.bashrc
for bash):
manperl(){ man "$1" | perl -00ne "print if /^\s*$2\b/"; }
Then, you run it by giving it a man page and the name of a section:
$ 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).
While it's true that some shell builtins may have a scant showing in a complete manual - especially for those bash
-specific builtins that you're only likely to use on a GNU system (the GNU folks, as a rule, don't believe in man
and prefer their own info
pages) - the vast majority of POSIX utilities - shell builtins or otherwise - are very well represented in the POSIX Programmer's Guide.
Here's an excerpt from the bottom of my man sh
(which is probably 20 pages long or so...)
All of those are there, and others not mentioned such as set
, read
, break
... well, I don't need to name them all. But note the (1P)
at the bottom right - it denotes the POSIX category 1 manual series - those are the man
pages I'm talking about.
It may be that you just need to install a package? This looks promising for a Debian system. While help
is useful, if you can find it, you should definitely get that POSIX Programmer's Guide
series. It can be extremely helpful. And it's constituent pages are very detailed.
That aside, shell builtins are almost always listed in a specific section of the specific shell's manual. zsh
, for example, has an entire separate man
page for that - (I think it totals at 8 or 9 or so individual zsh
pages - including zshall
which is huge.)
You can just grep
man
of course:
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
...which is pretty close to what I used to do when searching a shell man
page. But help
is pretty good in bash
in most cases.
I've actually been working on a sed
script to handle this kind of stuff recently. It's how I grabbed the section in the picture above. It's still longer than I like, but it's improving - and can be pretty handy. In its current iteration it will pretty reliably extract a context-sensitive section of text as matched to a section or subsection heading based on [a] pattern[s] given it on the command line. It colors its output and prints to stdout.
It works by evaluating indent levels. Non-blank input lines are generally ignored, but when it encounters a blank line it starts to pay attention. It gathers lines from there until it has verified that the current sequence definitely indents further in than did its first line before another blank line occurs or else it drops the thread and waits for the next blank. If the test is successful it attempts to match the lead line against its command-line args.
This means that a match pattern will match:
heading
match ...
...
...
text...
..and..
match
text
..but not..
heading
match
match
notmatch
..or..
text
match
match
text
more text
If a match can be had it starts printing. It will strip the matched line's leading blanks from all lines it prints - so no matter the indent level it found that line on it prints it as if it were at the top. It will continue to print until it encounters another line at an equal or lesser-than indent level than its matched line - so entire sections are grabbed with just a heading match, including any/all subsections, paragraphs they might contain.
So basically if you ask it to match a pattern it will only do so against a subject heading of some kind and will color and print all of the text it finds within the section headed by its match. Nothing is saved as it does this except your first line's indent - and so it can be very fast and handle \n
ewline separated input of virtually any size.
It took me awhile to figure out how to recurse into subheadings like the following:
Section Heading
Subsection Heading
But I sorted it out eventually.
I did have to rework the whole thing for simplicity's sake, though. While before I had several small loops doing mostly the same things in slightly different ways to fit their context, by varying their means of recursion I managed to de-duplicate the majority of the code. Now there are two loops - one prints and one checks indent. Both depend on the same test - the print loop starts when the test passes and the indent loop takes over when it fails or begins on a blank line.
The whole process is very fast because most of the time it just /./d
eletes any non-blank line and moves on to the next - even results from zshall
populate the screen instantly. This has not changed.
Anyway, it is very useful so far, though. For example, the read
thing above can be done like:
mansed bash read
...and it gets the whole block. It can take any patterns or whatever, or multiple arguments, though the first is always the man
page in which it should search. Here's a picture of some of its output after I did:
mansed bash read printf
...both blocks are returned whole. I often use it like:
mansed ksh '[Cc]ommand.*'
...for which it is quite useful. Also, getting SYNOPS[ES]
makes it really handy:
Here it is if you want to give it a whirl - I won't blame you if you don't though.
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
");}
Briefly, the workflow is:
- any line which is not blank and which does not contain a
\n
ewline character is deleted from output.\n
ewline characters never occur in input pattern space. They can only be had as the result of an edit.
:print
and:indent
are both mutually dependent closed loops and are the only way to obtain a\n
ewline.:print
's loop cycle begins if the leading characters on a line are a series of blanks followed by a\n
ewline character.:indent
's cycle begins on blank lines - or on:print
cycle lines which fail#test
- but:indent
removes all leading blank +\n
ewline sequences from its output.- once
:print
begins it will continue to pull in input lines, strip leading whitespace up to the amount found on the first line in its cycle, translate overstrike and understrike backspace escapes into color terminal escapes, and print the results until#test
fails. - before
:indent
begins it first checksh
old space for any possible indent continuation (such as a Subsection), and then continues to pull in input as long as#test
fails and any line following the first continues to match[-
. When a line after the first does not match that pattern it is deleted - and subsequently so are all following lines until the next blank line.
#match
and#test
bridge the two closed loops.#test
passes when the leading series of blanks is shorter than the series followed by the last\n
ewline in a line sequence.#match
prepends the leading\n
ewlines needed to begin a:print
cycle to any of:indent
's output sequences which lead with a match to any command-line arg. Those sequences that don't are rendered empty - and the resulting blank line is passed back to:indent
.