diff --git a/usr.bin/man/man.1 b/usr.bin/man/man.1 index 820d6a5b33a9..707677ccce06 100644 --- a/usr.bin/man/man.1 +++ b/usr.bin/man/man.1 @@ -1,424 +1,436 @@ .\"- .\" SPDX-License-Identifier: BSD-2-Clause .\" .\" Copyright (c) 2010 Gordon Tetlow .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .Dd January 24, 2025 .Dt MAN 1 .Os .Sh NAME .Nm man .Nd display online manual documentation pages .Sh SYNOPSIS .Nm -.Op Fl adho +.Op Fl adhlo .Op Fl t | w .Op Fl M Ar manpath .Op Fl P Ar pager .Op Fl S Ar mansect .Op Fl m Ar arch Ns Op : Ns Ar machine .Op Fl p Op Ar eprtv .Op Ar mansect .Ar page | Ar .Nm .Fl K | f | k .Ar expression ... .Sh DESCRIPTION The .Nm utility finds and displays online manual documentation pages. If .Ar mansect is provided, .Nm restricts the search to the specific section of the manual. .Pp The sections of the manual are: .Pp .Bl -enum -offset indent -compact .It .Fx General Commands Manual .It .Fx System Calls Manual .It .Fx Library Functions Manual .It .Fx Kernel Interfaces Manual .It .Fx File Formats Manual .It .Fx Games Manual .It .Fx Miscellaneous Information Manual .It .Fx System Manager's Manual .It .Fx Kernel Developer's Manual .El .Pp Options that .Nm understands: .Bl -tag -width indent .It Fl K Ar expression Search full text of all manual pages for an extended regular .Ar expression , as described in .Xr re_format 7 . This option requires .Xr mandoc 1 . This is a slow operation. .It Fl M Ar manpath Force a specific colon separated manual path instead of the default search path. See .Ev MANPATH in .Sx ENVIRONMENT . .It Fl P Ar pager Use specified pager. Defaults to .Ql less -sR if color support is enabled, or .Ql less -s . Overrides the .Ev MANPAGER environment variable, which in turn overrides the .Ev PAGER environment variable. .It Fl S Ar mansect Restrict manual sections searched to the specified colon delimited list. Defaults to .Ql 1:8:2:3:3lua:n:4:5:6:7:9:l . Overrides the .Ev MANSECT environment variable. .It Fl a Display all manual pages instead of just the first found for each .Ar page argument. .It Fl d Print extra debugging information. Repeat for increased verbosity. Does not display the manual page. .It Fl f Ar expression Search names of all manual pages for an extended regular .Ar expression , emulating .Xr whatis 1 . .It Fl h Display short help message and exit. .It Fl k Ar expression Search names and descriptions of all manual pages for an extended regular .Ar expression , emulating basic functionality of .Xr apropos 1 . +.It Fl l +Interpret all arguments as absolute or relative filename(s) +of the manual page(s) to display. +No search is done and the options +.Fl M , +.Fl m , +and +.Fl S +are ignored. .It Fl m Ar arch Ns Op : Ns Ar machine Override the default architecture and machine settings allowing lookup of other platform specific manual pages. See .Sx IMPLEMENTATION NOTES for how this option changes the default behavior. Overrides the .Ev MACHINE_ARCH and .Ev MACHINE environment variables. .It Fl o Force use of non-localized manual pages. See .Sx IMPLEMENTATION NOTES for how locale specific searches work. Overrides the .Ev LC_ALL , LC_CTYPE , and .Ev LANG environment variables. .It Fl p Op Cm eprtv Use the list of given preprocessors before running .Xr nroff 1 Pq Pa ports/textproc/groff or .Xr troff 1 Pq Pa ports/textproc/groff . Valid preprocessors arguments: .Pp .Bl -tag -width indent -compact .It Cm e .Xr eqn 1 Pq Pa ports/textproc/groff .It Cm p .Xr pic 1 Pq Pa ports/textproc/groff .It Cm r .Xr refer 1 Pq Pa ports/textproc/groff .It Cm t .Xr tbl 1 Pq Pa ports/textproc/groff .It Cm v .Xr vgrind 1 .El .Pp Overrides the .Ev MANROFFSEQ environment variable. .It Fl t Send manual page source through .Xr troff 1 Pq Pa ports/textproc/groff allowing transformation of the manual pages to other formats. .It Fl w Display the location of the manual page instead of the contents of the manual page. .El .Sh IMPLEMENTATION NOTES .Ss Locale Specific Searches The .Nm utility supports manual pages in different locales. The search behavior is dictated by the first of three environment variables with a nonempty string: .Ev LC_ALL , LC_CTYPE , or .Ev LANG . If set, .Nm will search for locale specific manual pages using the following logic: .Pp .Bl -item -offset indent -compact .It .Va lang Ns _ Ns Va country . Ns Va charset .It .Va lang . Ns Va charset .It .Li en . Ns Va charset .El .Pp For example, if .Ev LC_ALL is set to .Ql ja_JP.eucJP , .Nm will search the following paths when considering section 1 manual pages in .Pa /usr/share/man : .Pp .Bl -item -offset indent -compact .It .Pa /usr/share/man/ja_JP.eucJP/man1 .It .Pa /usr/share/man/ja.eucJP/man1 .It .Pa /usr/share/man/en.eucJP/man1 .It .Pa /usr/share/man/man1 .El .Ss Platform Specific Searches The .Nm utility supports platform specific manual pages. The search behavior is dictated by the .Fl m option or the .Ev MACHINE_ARCH and .Ev MACHINE environment variables. For example, if .Ev MACHINE_ARCH is set to .Ql aarch64 and .Ev MACHINE is set to .Ql arm64 , .Nm will search the following paths when considering section 4 manual pages in .Pa /usr/share/man : .Pp .Bl -item -offset indent -compact .It .Pa /usr/share/man/man4/aarch64 .It .Pa /usr/share/man/man4/arm64 .It .Pa /usr/share/man/man4 .El .Ss Displaying Specific Manual Files -The +For compatibility reasons, .Nm -utility also supports displaying a specific manual page if passed a path -to the file as long as it contains a +will interpret any argument containing at least one .Ql / -character. +character as an absolute or relative path to a manual page to be +displayed. +This heuristic, made redundant by the more widely supported +.Fl l +option, is now deprecated and may be removed in future releases. .Sh ENVIRONMENT The following environment variables affect the execution of .Nm : .Bl -tag -width indent .It Ev LC_ALL , Ev LC_CTYPE , Ev LANG Used to find locale specific manual pages. Valid values can be found by running the .Xr locale 1 command. See .Sx IMPLEMENTATION NOTES for details. Influenced by the .Fl o option. .It Ev MACHINE_ARCH , Ev MACHINE Used to find platform specific manual pages. If unset, the output of .Ql sysctl Va hw.machine_arch and .Ql sysctl Va hw.machine is used respectively. See .Sx IMPLEMENTATION NOTES for details. Corresponds to the .Fl m option. .It Ev MANPATH A colon .Pq Ql \&: separated list of directories to check for manual pages. Invalid paths, or paths without manual databases, are ignored. Overridden by .Fl M . If .Ev MANPATH begins with a colon, it is appended to the default list; if it ends with a colon, it is prepended to the default list; or if it contains two adjacent colons, the standard search path is inserted between the colons. If none of these conditions are met, it overrides the standard search path. See .Xr manpath 1 . .It Ev MANROFFSEQ Used to determine the preprocessors for the manual source before running .Xr nroff 1 Pq Pa ports/textproc/groff or .Xr troff 1 Pq Pa ports/textproc/groff . If unset, defaults to .Xr tbl 1 Pq Pa ports/textproc/groff . Corresponds to the .Fl p option. .It Ev MANSECT Restricts manual sections searched to the specified colon delimited list. Corresponds to the .Fl S option. .It Ev MANWIDTH If set to a numeric value, used as the width manpages should be displayed. Otherwise, if set to a special value .Ql tty , and output is to a terminal, the pages may be displayed over the whole width of the screen. .It Ev MANCOLOR If set, enables color support. .It Ev MANPAGER Program used to display files. .Pp If unset, and color support is enabled, .Ql less -sR is used. .Pp If unset, and color support is disabled, then .Ev PAGER is used. If that has no value either, .Ql less -s is used. .El .Sh FILES .Bl -tag -width "/usr/local/etc/man.d/*.conf" -compact .It Pa /etc/man.conf System configuration file .It Pa /usr/local/etc/man.d/*.conf Local configuration files .El .Sh EXIT STATUS .Ex -std .Sh EXAMPLES Show the manual page for .Xr stat 2 : .Pp .Dl $ man 2 stat .Pp Show all manual pages for .Ql stat : .Pp .Dl $ man -a stat .Pp List manual pages which match the regular expression either in the title or in the body: .Pp .Dl $ man -k '\e.*archive' .Pp Show the manual page for .Xr ls 1 using .Xr cat 1 as the pager: .Pp .Dl $ man -P cat ls .Pp Show the location of the .Xr ls 1 manual page: .Pp .Dl $ man -w ls .Pp Show a manual page in the current working directory: .Pp -.Dl $ man ./man.1 +.Dl $ man -l man.1 .Pp Show the location of manual pages in sections 1 and 8 which contain the word .Ql arm : .Pp .Dl $ man -w -K '\e' -S 1:8 .Sh SEE ALSO .Xr apropos 1 , .Xr intro 1 , .Xr mandoc 1 , .Xr manpath 1 , .Xr whatis 1 , .Xr intro 2 , .Xr intro 3 , .Xr intro 3lua , .Xr intro 4 , .Xr intro 5 , .Xr man.conf 5 , .Xr intro 6 , .Xr intro 7 , .Xr mdoc 7 , .Xr re_format 7 , .Xr intro 8 , .Xr intro 9 diff --git a/usr.bin/man/man.sh b/usr.bin/man/man.sh index b1cfc6c41752..fe9c388c0df8 100755 --- a/usr.bin/man/man.sh +++ b/usr.bin/man/man.sh @@ -1,1126 +1,1141 @@ #! /bin/sh # # SPDX-License-Identifier: BSD-2-Clause # # Copyright (c) 2010 Gordon Tetlow # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # Rendering a manual page is fast. Even a manual page several 100k in size # takes less than a CPU second. If it takes much longer, it is very likely # that a tool like mandoc(1) is running in an infinite loop. In this case # it is better to terminate it. ulimit -t 20 # Do not ignore the exit codes of roff tools, as they may indicate a # problem with the page being rendered. Note that this also causes a # nonzero exit when the user quits reading before reaching the end, so # we need to look out for and deal with that specific case. set -o pipefail # Usage: add_to_manpath path # Adds a variable to manpath while ensuring we don't have duplicates. # Returns true if we were able to add something. False otherwise. add_to_manpath() { case "$manpath" in *:$1) decho " Skipping duplicate manpath entry $1" 2 ;; $1:*) decho " Skipping duplicate manpath entry $1" 2 ;; *:$1:*) decho " Skipping duplicate manpath entry $1" 2 ;; *) if [ -d "$1" ]; then decho " Adding $1 to manpath" manpath="$manpath:$1" return 0 fi ;; esac return 1 } # Usage: build_manlocales # Builds a correct MANLOCALES variable. build_manlocales() { # If the user has set manlocales, who are we to argue. if [ -n "$MANLOCALES" ]; then return fi parse_configs # Trim leading colon MANLOCALES=${manlocales#:} decho "Available manual locales: $MANLOCALES" } # Usage: build_mansect # Builds a correct MANSECT variable. build_mansect() { # If the user has set mansect, who are we to argue. if [ -n "$MANSECT" ]; then return fi parse_configs # Trim leading colon MANSECT=${mansect#:} if [ -z "$MANSECT" ]; then MANSECT=$man_default_sections fi decho "Using manual sections: $MANSECT" } # Usage: build_manpath # Builds a correct MANPATH variable. build_manpath() { local IFS # If the user has set a manpath, who are we to argue. if [ -n "$MANPATH" ]; then case "$MANPATH" in *:) PREPEND_MANPATH=${MANPATH} ;; :*) APPEND_MANPATH=${MANPATH} ;; *::*) PREPEND_MANPATH=${MANPATH%%::*} APPEND_MANPATH=${MANPATH#*::} ;; *) return ;; esac fi if [ -n "$PREPEND_MANPATH" ]; then IFS=: for path in $PREPEND_MANPATH; do add_to_manpath "$path" done unset IFS fi search_path decho "Adding default manpath entries" IFS=: for path in $man_default_path; do add_to_manpath "$path" done unset IFS parse_configs if [ -n "$APPEND_MANPATH" ]; then IFS=: for path in $APPEND_MANPATH; do add_to_manpath "$path" done unset IFS fi # Trim leading colon MANPATH=${manpath#:} decho "Using manual path: $MANPATH" } # Usage: check_cat catglob # Checks to see if a cat glob is available. check_cat() { if exists "$1"; then use_cat=yes catpage=$found setup_cattool "$catpage" decho " Found catpage \"$catpage\"" return 0 else return 1 fi } # Usage: check_man manglob catglob # Given 2 globs, figures out if the manglob is available, if so, check to # see if the catglob is also available and up to date. check_man() { if exists "$1"; then # We have a match, check for a cat page manpage=$found setup_cattool "$manpage" decho " Found manpage \"$manpage\"" if [ -n "${use_width}" ]; then # non-standard width unset use_cat decho " Skipping catpage: non-standard page width" elif exists "$2" && is_newer $found "$manpage"; then # cat page found and is newer, use that use_cat=yes catpage=$found setup_cattool "$catpage" decho " Using catpage \"$catpage\"" else # no cat page or is older unset use_cat decho " Skipping catpage: not found or old" fi return 0 fi return 1 } # Usage: decho "string" [debuglevel] # Echoes to stderr string prefaced with -- if high enough debuglevel. decho() { if [ $debug -ge ${2:-1} ]; then echo "-- $1" >&2 fi } # Usage: exists glob # # Returns true if glob resolves to a real file and store the first # found filename in the variable $found exists() { if [ -z "$1" ]; then return 1 fi local IFS # Don't accidentally inherit callers IFS (breaks perl manpages) unset IFS # Use some globbing tricks in the shell to determine if a file # exists or not. set +f for file in "$1"* do if [ -r "$file" ]; then found="$file" set -f return 0 fi done set -f return 1 } # Usage: find_file path section subdir pagename # Returns: true if something is matched and found. # Search the given path/section combo for a given page. find_file() { local manroot catroot mann man0 catn cat0 manroot="$1/man$2" catroot="$1/cat$2" if [ -n "$3" ]; then manroot="$manroot/$3" catroot="$catroot/$3" fi if [ ! -d "$manroot" -a ! -d "$catroot" ]; then return 1 fi decho " Searching directory $manroot" 2 mann="$manroot/$4.$2" man0="$manroot/$4.0" catn="$catroot/$4.$2" cat0="$catroot/$4.0" # This is the behavior as seen by the original man utility. # Let's not change that which doesn't seem broken. if check_man "$mann" "$catn"; then return 0 elif check_man "$man0" "$cat0"; then return 0 elif check_cat "$catn"; then return 0 elif check_cat "$cat0"; then return 0 fi return 1 } # Usage: is_newer file1 file2 # Returns true if file1 is newer than file2 as calculated by mtime. is_newer() { if ! [ "$1" -ot "$2" ]; then decho " mtime: $1 not older than $2" 3 return 0 else decho " mtime: $1 older than $2" 3 return 1 fi } # Usage: manpath_parse_args "$@" # Parses commandline options for manpath. manpath_parse_args() { local cmd_arg OPTIND=1 while getopts 'Ldq' cmd_arg; do case "${cmd_arg}" in L) Lflag=Lflag ;; d) debug=$(( $debug + 1 )) ;; q) qflag=qflag ;; *) manpath_usage ;; esac done >&2 } # Usage: manpath_usage # Display usage for the manpath(1) utility. manpath_usage() { echo 'usage: manpath [-Ldq]' >&2 exit 1 } # Usage: manpath_warnings # Display some warnings to stderr. manpath_warnings() { if [ -n "$Lflag" -a -n "$MANLOCALES" ]; then echo "(Warning: MANLOCALES environment variable set)" >&2 fi } # Usage: man_check_for_so page path # Returns: True if able to resolve the file, false if it ended in tears. # Detects the presence of the .so directive and causes the file to be # redirected to another source file. man_check_for_so() { local IFS line tstr unset IFS if [ -n "$catpage" ]; then return 0 fi # We need to loop to accommodate multiple .so directives. while true do line=$($cattool "$manpage" 2>/dev/null | grep -E -m1 -v '^\.\\"[ ]*|^[ ]*$') case "$line" in .so*) trim "${line#.so}" decho "$manpage includes $tstr" # Glob and check for the file. if ! check_man "$path/$tstr" ""; then decho " Unable to find $tstr" return 1 fi ;; *) break ;; esac done return 0 } # Usage: man_display_page # Display either the manpage or catpage depending on the use_cat variable man_display_page() { local IFS pipeline testline # We are called with IFS set to colon. This causes really weird # things to happen for the variables that have spaces in them. unset IFS # If we are supposed to use a catpage and we aren't using troff(1) # just zcat the catpage and we are done. if [ -z "$tflag" -a -n "$use_cat" ]; then if [ -n "$wflag" ]; then echo "$catpage (source: \"$manpage\")" ret=0 else if [ $debug -gt 0 ]; then decho "Command: $cattool \"$catpage\" | $MANPAGER" ret=0 else $cattool "$catpage" | $MANPAGER ret=$? fi fi return fi # Okay, we are using the manpage, do we just need to output the # name of the manpage? if [ -n "$wflag" ]; then echo "$manpage" ret=0 return fi if [ -n "$use_width" ]; then mandoc_args="-O width=${use_width}" fi testline="mandoc -Tlint -Wunsupp >/dev/null 2>&1" if [ -n "$tflag" ]; then pipeline="mandoc -Tps $mandoc_args" else pipeline="mandoc $mandoc_args | $MANPAGER" fi if ! $cattool "$manpage" | eval "$testline"; then if which -s groff; then man_display_page_groff else echo "This manpage needs groff(1) to be rendered" >&2 echo "First install groff(1): " >&2 echo "pkg install groff " >&2 ret=1 fi return fi if [ $debug -gt 0 ]; then decho "Command: $cattool \"$manpage\" | eval \"$pipeline\"" ret=0 else $cattool "$manpage" | eval "$pipeline" ret=$? fi } # Usage: man_display_page_groff # Display the manpage using groff man_display_page_groff() { local EQN NROFF PIC TBL TROFF REFER VGRIND local IFS l nroff_dev pipeline preproc_arg tool # So, we really do need to parse the manpage. First, figure out the # device flag (-T) we have to pass to eqn(1) and groff(1). Then, # setup the pipeline of commands based on the user's request. # If the manpage is from a particular charset, we need to setup nroff # to properly output for the correct device. case "${manpage}" in *.${man_charset}/*) # I don't pretend to know this; I'm just copying from the # previous version of man(1). case "$man_charset" in KOI8-R) nroff_dev="koi8-r" ;; ISO8859-1) nroff_dev="latin1" ;; ISO8859-15) nroff_dev="latin1" ;; UTF-8) nroff_dev="utf8" ;; *) nroff_dev="ascii" ;; esac NROFF="$NROFF -T$nroff_dev" EQN="$EQN -T$nroff_dev" # Iff the manpage is from the locale and not just the charset, # then we need to define the locale string. case "${manpage}" in */${man_lang}_${man_country}.${man_charset}/*) NROFF="$NROFF -dlocale=$man_lang.$man_charset" ;; */${man_lang}.${man_charset}/*) NROFF="$NROFF -dlocale=$man_lang.$man_charset" ;; esac # Allow language specific calls to override the default # set of utilities. l=$(echo $man_lang | tr [:lower:] [:upper:]) for tool in EQN NROFF PIC TBL TROFF REFER VGRIND; do eval "$tool=\${${tool}_$l:-\$$tool}" done ;; *) NROFF="$NROFF -Tascii" EQN="$EQN -Tascii" ;; esac if [ -z "$MANCOLOR" ]; then NROFF="$NROFF -P-c" fi if [ -n "${use_width}" ]; then NROFF="$NROFF -rLL=${use_width}n -rLT=${use_width}n" fi if [ -n "$MANROFFSEQ" ]; then set -- -$MANROFFSEQ OPTIND=1 while getopts 'egprtv' preproc_arg; do case "${preproc_arg}" in e) pipeline="$pipeline | $EQN" ;; g) ;; # Ignore for compatibility. p) pipeline="$pipeline | $PIC" ;; r) pipeline="$pipeline | $REFER" ;; t) pipeline="$pipeline | $TBL" ;; v) pipeline="$pipeline | $VGRIND" ;; *) usage ;; esac done # Strip the leading " | " from the resulting pipeline. pipeline="${pipeline#" | "}" else pipeline="$TBL" fi if [ -n "$tflag" ]; then pipeline="$pipeline | $TROFF" else pipeline="$pipeline | $NROFF | $MANPAGER" fi if [ $debug -gt 0 ]; then decho "Command: $cattool \"$manpage\" | eval \"$pipeline\"" ret=0 else $cattool "$manpage" | eval "$pipeline" ret=$? fi } # Usage: man_find_and_display page # Search through the manpaths looking for the given page. man_find_and_display() { - local found_page locpath p path sect + local found_page has_slash locpath p path sect # Check to see if it's a file. But only if it has a '/' in - # the filename. + # the filename or if -l was specified. case "$1" in - */*) if [ -f "$1" -a -r "$1" ]; then + */*) has_slash=yes + ;; + esac + if [ -n "$has_slash" -o -n "$lflag" ]; then + if [ -f "$1" -a -r "$1" ]; then decho "Found a usable page, displaying that" + if [ -z "$lflag" ]; then + echo "Opening a file directly is deprecated," \ + "use -l instead." >&2 + fi unset use_cat manpage="$1" setup_cattool "$manpage" if man_check_for_so "$manpage" "$(dirname \"$manpage"")"; then found_page=yes man_display_page fi return + elif [ -n "$lflag" ]; then + echo "Cannot read $1" >&2 + ret=1 + return fi - ;; - esac + fi IFS=: for sect in $MANSECT; do decho "Searching section $sect" 2 for path in $MANPATH; do for locpath in $locpaths; do p=$path/$locpath p=${p%/.} # Rid ourselves of the trailing /. # Check if there is a MACHINE specific manpath. if find_file $p $sect $MACHINE "$1"; then if man_check_for_so "$manpage" $p; then found_page=yes man_display_page if [ -n "$aflag" ]; then continue 2 else return fi fi fi # Check if there is a MACHINE_ARCH # specific manpath. if find_file $p $sect $MACHINE_ARCH "$1"; then if man_check_for_so "$manpage" $p; then found_page=yes man_display_page if [ -n "$aflag" ]; then continue 2 else return fi fi fi # Check plain old manpath. if find_file $p $sect '' "$1"; then if man_check_for_so "$manpage" $p; then found_page=yes man_display_page if [ -n "$aflag" ]; then continue 2 else return fi fi fi done done done unset IFS # Nothing? Well, we are done then. if [ -z "$found_page" ]; then echo "No manual entry for \"$1\"" >&2 ret=1 return fi } # Usage: man_parse_opts "$@" # Parses commandline options for man. man_parse_opts() { local cmd_arg OPTIND=1 - while getopts 'K:M:P:S:adfhkm:op:tw' cmd_arg; do + while getopts 'K:M:P:S:adfhklm:op:tw' cmd_arg; do case "${cmd_arg}" in K) Kflag=Kflag REGEXP=$OPTARG ;; M) MANPATH=$OPTARG ;; P) MANPAGER=$OPTARG ;; S) MANSECT=$OPTARG ;; a) aflag=aflag ;; d) debug=$(( $debug + 1 )) ;; f) fflag=fflag ;; h) man_usage 0 ;; k) kflag=kflag ;; + l) lflag=lflag ;; m) mflag=$OPTARG ;; o) oflag=oflag ;; p) MANROFFSEQ=$OPTARG ;; t) tflag=tflag ;; w) wflag=wflag ;; *) man_usage ;; esac done >&2 shift $(( $OPTIND - 1 )) # Check the args for incompatible options. - - case "${Kflag}${fflag}${kflag}${tflag}${wflag}" in + case "${Kflag}${fflag}${kflag}${lflag}${tflag}${wflag}" in Kflagfflag*) echo "Incompatible options: -K and -f"; man_usage ;; Kflag*kflag*) echo "Incompatible options: -K and -k"; man_usage ;; + Kflag*lflag*) echo "Incompatible options: -K and -l"; man_usage ;; Kflag*tflag) echo "Incompatible options: -K and -t"; man_usage ;; fflagkflag*) echo "Incompatible options: -f and -k"; man_usage ;; + fflag*lflag*) echo "Incompatible options: -f and -l"; man_usage ;; fflag*tflag*) echo "Incompatible options: -f and -t"; man_usage ;; fflag*wflag) echo "Incompatible options: -f and -w"; man_usage ;; - *kflagtflag*) echo "Incompatible options: -k and -t"; man_usage ;; + *kflaglflag*) echo "Incompatible options: -k and -l"; man_usage ;; + *kflag*tflag*) echo "Incompatible options: -k and -t"; man_usage ;; *kflag*wflag) echo "Incompatible options: -k and -w"; man_usage ;; + *lflag*wflag) echo "Incompatible options: -l and -w"; man_usage ;; *tflagwflag) echo "Incompatible options: -t and -w"; man_usage ;; esac # Short circuit for whatis(1) and apropos(1) if [ -n "$fflag" ]; then do_whatis "$@" exit fi if [ -n "$kflag" ]; then do_apropos "$@" exit fi } # Usage: man_setup # Setup various trivial but essential variables. man_setup() { # Setup machine and architecture variables. if [ -n "$mflag" ]; then MACHINE_ARCH=${mflag%%:*} MACHINE=${mflag##*:} fi if [ -z "$MACHINE_ARCH" ]; then MACHINE_ARCH=$($SYSCTL -n hw.machine_arch) fi if [ -z "$MACHINE" ]; then MACHINE=$($SYSCTL -n hw.machine) fi decho "Using architecture: $MACHINE_ARCH:$MACHINE" setup_pager build_manpath build_mansect man_setup_locale man_setup_width } # Usage: man_setup_width # Set up page width. man_setup_width() { local sizes unset use_width case "$MANWIDTH" in [0-9]*) if [ "$MANWIDTH" -gt 0 2>/dev/null ]; then use_width=$MANWIDTH fi ;; [Tt][Tt][Yy]) if { sizes=$($STTY size 0>&3 2>/dev/null); } 3>&1; then set -- $sizes if [ $2 -gt 80 ]; then use_width=$(($2-2)) fi fi ;; esac if [ -n "$use_width" ]; then decho "Using non-standard page width: ${use_width}" else decho 'Using standard page width' fi } # Usage: man_setup_locale # Setup necessary locale variables. man_setup_locale() { local lang_cc local locstr locpaths='.' man_charset='US-ASCII' # Setup locale information. if [ -n "$oflag" ]; then decho 'Using non-localized manpages' else # Use the locale tool to give us proper locale information eval $( $LOCALE ) if [ -n "$LANG" ]; then locstr=$LANG else locstr=$LC_CTYPE fi case "$locstr" in C) ;; C.UTF-8) ;; POSIX) ;; [a-z][a-z]_[A-Z][A-Z]\.*) lang_cc="${locstr%.*}" man_lang="${locstr%_*}" man_country="${lang_cc#*_}" man_charset="${locstr#*.}" locpaths="$locstr" locpaths="$locpaths:$man_lang.$man_charset" if [ "$man_lang" != "en" ]; then locpaths="$locpaths:en.$man_charset" fi locpaths="$locpaths:." ;; *) echo 'Unknown locale, assuming C' >&2 ;; esac fi decho "Using locale paths: $locpaths" } # Usage: man_usage [exitcode] # Display usage for the man utility. man_usage() { echo 'Usage:' echo ' man [-adho] [-t | -w] [-M manpath] [-P pager] [-S mansect]' echo ' [-m arch[:machine]] [-p [eprtv]] [mansect] page [...]' echo ' man -K | -f | -k expression [...] -- Search manual pages' # When exit'ing with -h, it's not an error. exit ${1:-1} } # Usage: parse_configs # Reads the end-user adjustable config files. parse_configs() { local IFS file files if [ -n "$parsed_configs" ]; then return fi unset IFS # Read the global config first in case the user wants # to override config_local. if [ -r "$config_global" ]; then parse_file "$config_global" fi # Glob the list of files to parse. set +f files=$(echo $config_local) set -f for file in $files; do if [ -r "$file" ]; then parse_file "$file" fi done parsed_configs='yes' } # Usage: parse_file file # Reads the specified config files. parse_file() { local file line tstr var file="$1" decho "Parsing config file: $file" while read line; do decho " $line" 2 case "$line" in \#*) decho " Comment" 3 ;; MANPATH*) decho " MANPATH" 3 trim "${line#MANPATH}" add_to_manpath "$tstr" ;; MANLOCALE*) decho " MANLOCALE" 3 trim "${line#MANLOCALE}" manlocales="$manlocales:$tstr" ;; MANCONFIG*) decho " MANCONFIG" 3 trim "${line#MANCONFIG}" config_local="$tstr" ;; MANSECT*) decho " MANSECT" 3 trim "${line#MANSECT}" mansect="$mansect:$tstr" ;; # Set variables in the form of FOO_BAR *_*[\ \ ]*) var="${line%%[\ \ ]*}" trim "${line#$var}" eval "$var=\"$tstr\"" decho " Parsed $var" 3 ;; esac done < "$file" } # Usage: search_path # Traverse $PATH looking for manpaths. search_path() { local IFS p path decho "Searching PATH for man directories" IFS=: for path in $PATH; do if add_to_manpath "$path/man"; then : elif add_to_manpath "$path/MAN"; then : else case "$path" in */bin) p="${path%/bin}/share/man" add_to_manpath "$p" p="${path%/bin}/man" add_to_manpath "$p" ;; esac fi done unset IFS if [ -z "$manpath" ]; then decho ' Unable to find any manpaths, using default' manpath=$man_default_path fi } # Usage: search_whatis cmd [arglist] # Do the heavy lifting for apropos/whatis search_whatis() { local IFS bad cmd f good key keywords loc opt out path rval wlist cmd="$1" shift whatis_parse_args "$@" build_manpath build_manlocales setup_pager if [ "$cmd" = "whatis" ]; then opt="-w" fi f='whatis' IFS=: for path in $MANPATH; do if [ \! -d "$path" ]; then decho "Skipping non-existent path: $path" 2 continue fi if [ -f "$path/$f" -a -r "$path/$f" ]; then decho "Found whatis: $path/$f" wlist="$wlist $path/$f" fi for loc in $MANLOCALES; do if [ -f "$path/$loc/$f" -a -r "$path/$loc/$f" ]; then decho "Found whatis: $path/$loc/$f" wlist="$wlist $path/$loc/$f" fi done done unset IFS if [ -z "$wlist" ]; then echo "$cmd: no whatis databases in $MANPATH" >&2 exit 1 fi rval=0 for key in $keywords; do out=$(grep -Ehi $opt -- "$key" $wlist) if [ -n "$out" ]; then good="$good\\n$out" else bad="$bad\\n$key: nothing appropriate" rval=1 fi done # Strip leading carriage return. good=${good#\\n} bad=${bad#\\n} if [ -n "$good" ]; then echo -e "$good" | $MANPAGER fi if [ -n "$bad" ]; then echo -e "$bad" >&2 fi exit $rval } # Usage: setup_cattool page # Finds an appropriate decompressor based on extension setup_cattool() { case "$1" in *.bz) cattool='/usr/bin/bzcat' ;; *.bz2) cattool='/usr/bin/bzcat' ;; *.gz) cattool='/usr/bin/gzcat' ;; *.lzma) cattool='/usr/bin/lzcat' ;; *.xz) cattool='/usr/bin/xzcat' ;; *.zst) cattool='/usr/bin/zstdcat' ;; *) cattool='/usr/bin/zcat -f' ;; esac } # Usage: setup_pager # Correctly sets $MANPAGER setup_pager() { # Setup pager. if [ -z "$MANPAGER" ]; then if [ -n "$MANCOLOR" ]; then MANPAGER="less -sR" else if [ -n "$PAGER" ]; then MANPAGER="$PAGER" else MANPAGER="less -s" fi fi fi decho "Using pager: $MANPAGER" } # Usage: trim string # Trims whitespace from beginning and end of a variable trim() { tstr=$1 while true; do case "$tstr" in [\ \ ]*) tstr="${tstr##[\ \ ]}" ;; *[\ \ ]) tstr="${tstr%%[\ \ ]}" ;; *) break ;; esac done } # Usage: whatis_parse_args "$@" # Parse commandline args for whatis and apropos. whatis_parse_args() { local cmd_arg OPTIND=1 while getopts 'd' cmd_arg; do case "${cmd_arg}" in d) debug=$(( $debug + 1 )) ;; *) whatis_usage ;; esac done >&2 shift $(( $OPTIND - 1 )) keywords="$*" } # Usage: whatis_usage # Display usage for the whatis/apropos utility. whatis_usage() { echo "usage: $cmd [-d] keyword [...]" exit 1 } # Supported commands do_apropos() { [ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/apropos) ] && \ exec apropos "$@" search_whatis apropos "$@" } # Usage: do_full_search reg_exp # Do a full search of the regular expression passed # as parameter in all man pages do_full_search() { local gflags re re=${1} # Build grep(1) flags gflags="-H" # wflag implies -l for grep(1) if [ -n "$wflag" ]; then gflags="${gflags} -l" fi gflags="${gflags} --label" set +f for mpath in $(echo "${MANPATH}" | tr : '[:blank:]'); do for section in $(echo "${MANSECT}" | tr : '[:blank:]'); do for manfile in ${mpath}/man${section}/*.${section}*; do mandoc "${manfile}" 2>/dev/null | grep -E ${gflags} "${manfile}" -e "${re}" done done done set -f } do_man() { local IFS man_parse_opts "$@" man_setup shift $(( $OPTIND - 1 )) IFS=: for sect in $MANSECT; do if [ "$sect" = "$1" ]; then decho "Detected manual section as first arg: $1" MANSECT="$1" shift break fi done unset IFS pages="$*" if [ -z "$pages" -a -z "${Kflag}" ]; then echo 'What manual page do you want?' >&2 exit 1 fi if [ ! -z "${Kflag}" ]; then # Short circuit because -K flag does a sufficiently # different thing like not showing the man page at all do_full_search "${REGEXP}" fi for page in "$@"; do decho "Searching for \"$page\"" man_find_and_display "$page" done # The user will very commonly quit reading the page before # reaching the bottom. Depending on the length of the page # and the pager's buffer size, this may result in a SIGPIPE. # This is normal, so convert that exit code to zero. if [ ${ret:-0} -gt 128 ]; then if [ "$(kill -l "${ret}")" = "PIPE" ]; then ret=0 fi fi exit ${ret:-0} } do_manpath() { manpath_parse_args "$@" if [ -z "$qflag" ]; then manpath_warnings fi if [ -n "$Lflag" ]; then build_manlocales echo $MANLOCALES else build_manpath echo $MANPATH fi exit 0 } do_whatis() { [ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/whatis) ] && \ exec whatis "$@" search_whatis whatis "$@" } # User's PATH setting decides on the groff-suite to pick up. EQN=eqn NROFF='groff -S -P-h -Wall -mtty-char -mandoc' PIC=pic REFER=refer TBL=tbl TROFF='groff -S -mandoc' VGRIND=vgrind LOCALE=/usr/bin/locale STTY=/bin/stty SYSCTL=/sbin/sysctl debug=0 man_default_sections='1:8:2:3:3lua:n:4:5:6:7:9:l' man_default_path='/usr/share/man:/usr/share/openssl/man:/usr/local/share/man:/usr/local/man' cattool='/usr/bin/zcat -f' config_global='/etc/man.conf' # This can be overridden via a setting in /etc/man.conf. config_local='/usr/local/etc/man.d/*.conf' # Set noglobbing for now. I don't want spurious globbing. set -f case "$0" in *apropos) do_apropos "$@" ;; *manpath) do_manpath "$@" ;; *whatis) do_whatis "$@" ;; *) do_man "$@" ;; esac