為什麼man page標題上有兩個 DATE(1)的追蹤過程

Yujiaao發表於2018-03-14

為什麼標題上有兩個DATE(1) 的追蹤過程

我們通過

man -aw date

得到

/usr/share/man/man1/date.1.gz

用zcat觀察

zcat /usr/share/man/man1/date.1.gz |less

可以看到第一行是

.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.47.3.
.TH DATE "1" "February 2017" "GNU coreutils 8.25" "User Commands"
.SH NAME
date \- print or set the system date and time
.SH SYNOPSIS
.B date
[\fI\,OPTION\/\fR]... [\fI\,+FORMAT\/\fR]
.br
.B date
......

這裡 .TH DATE "1"便是DATE(1)的出處, 但為什兩個呢? 繼續挖

用grep過濾

 zcat /usr/share/man/man1/date.1.gz | grep 'DATE \"1\"'

搜尋僅有一行輸出

date1

查詢man原始碼

$ which man
/usr/bin/man
$ dpkg --search /usr/bin/man
man-db: /usr/bin/man
$ apt-get source man-db

狂看一天之後, 發現man-db用的是pipeline,格式化和最終的輸出呼叫的都是外部指令

/* Return pipeline to format file to stdout. */
static pipeline *make_roff_command (const char *dir, const char *file,
                    pipeline *decomp, const char *dbfilters,
                    char **result_encoding)
......                    

從這個函式的內容裡, 發現主要是呼叫的roff/nroff/troff/groff這一組格式化命令來完成的.

建立一個小檔案a.txt,包含如下內容

.TH MAN 7 2012-08-05 "Linux" "Linux Programmer's Manual"
.SH NAME
man \- macros to format man pages

用groff試下

$ groff -mandoc -T ascii a.txt

輸出了以下內容

MAN(7)                     Linux Programmer's Manual                    MAN(7)



NAME
       man - macros to format man pages






Linux                             2012-08-05                            MAN(7)

果然是我想要的.

用strace檢視呼叫過程

還是pipe呼叫....

下載groff原始碼

$ apt-get source groff

發現可以用-V引數檢視pipeline命令

$ groff -V -mandoc -T ascii a.txt
troff -mandoc -Tascii a.txt | grotty

單獨執行troff

troff -mandoc -Tascii a.txt

得到

x T ascii
x res 240 24 40
x init
p1
x X tty: sgr 0
x font 1 R
f1
s10
V40
H0
md
DFd
tMAN(7)
h504
tLinux
wh24
tProgrammer's
wh24
tManual
h480
tMAN(7)
......

從這裡可以看到在a.txt裡只出現一次的MAN(7)被輸出了兩次.

troff原始碼分析

troff 原始碼也在groff包裡.
搜尋原始碼並沒有找到直接處理.TH標記的地方, 發現troff與man的巨集有關.

troff與巨集指令man macros

通過以下指令得到troff在執行過程中開啟過的.tmac巨集檔案

$ strace troff -mandoc -Tascii ~/a.txt 2>&1 |grep -v such | grep tmac

輸出如下

open("/usr/share/groff/1.22.3/tmac/troffrc", O_RDONLY) = 3
open("/usr/share/groff/1.22.3/tmac/composite.tmac", O_RDONLY) = 4
open("/usr/share/groff/1.22.3/tmac/fallbacks.tmac", O_RDONLY) = 4
open("/usr/share/groff/1.22.3/tmac/tty.tmac", O_RDONLY) = 4
open("/usr/share/groff/1.22.3/tmac/hyphen.us", O_RDONLY) = 4
open("/usr/share/groff/1.22.3/tmac/hyphenex.us", O_RDONLY) = 4
open("/usr/share/groff/1.22.3/tmac/papersize.tmac", O_RDONLY) = 4
open("/usr/share/groff/1.22.3/tmac/pspic.tmac", O_RDONLY) = 4
open("/usr/share/groff/1.22.3/tmac/andoc.tmac", O_RDONLY) = 3
open("/usr/share/groff/1.22.3/tmac/troffrc-end", O_RDONLY) = 3
open("/usr/share/groff/1.22.3/tmac/an-old.tmac", O_RDONLY) = 4
open("/usr/share/groff/1.22.3/tmac/devtag.tmac", O_RDONLY) = 5
open("/usr/share/groff/1.22.3/tmac/an-ext.tmac", O_RDONLY) = 5
open("/usr/share/groff/site-tmac/man.local", O_RDONLY) = 5

在相應目錄下只找到少處和.TH處理有關的片斷

/usr/share/groff/1.22.3/tmac$ grep -w TH -R *
andoc.tmac:.  als TH reload-man
andoc.tmac:.  rm TH
andoc.tmac:\\*[TH]\\
andoc.tmac:.als TH reload-man
andoc.tmac:.\" dummy equation macros -- eqnrc is read before .TH or .Dd is parsed
an-old.tmac:.\" If you need to add things to TH, use `.am1 TH'.
an-old.tmac:.\" .TH title section extra1 extra2 extra3
an-old.tmac: TH

.tmac檔案格式在這裡有詳細的介紹:

doc.tmac 主手冊巨集包
mdoc.tmac 對 doc.tmac呼叫的封裝
mdoc/doc-common 共用字串,定義,與印刷輸出相關內容.
mdoc/doc-nroff 用於 TTY裝置輸出的定義.
mdoc/doc-ditroff 用於其它裝置輸出的定義
mdoc.local 本地補充與客戶化相關.
andoc.tmac 如果你搞不清是該用 -mdoc 還是 -man 包時就用這個.

真相大白

最終在an-old.tmac層層定義的巨集裡找到了這行:

259 .de1 PT
260 .  tl '\\*[an-title](\\*[an-section])'\\*[an-extra3]'\\*[an-title](\\*[an-section])'
261 ..

編輯儲存, 果然如此

.  tl '\\*[an-title](\\*[an-section])'\\*[an-extra3]' [Eureka!] \\*[an-title](\\*[an-section])'

截圖留念

man date

這裡有man-db有相關的中文文件.

相關文章