第 24.5 节 利用脚本自动生成 BSDlibc 库文本
论坛方案
此部分来自 FreeBSD 论坛,作者 mrclksr。原文地址 https://forums.freebsd.org/threads/wheres-bsd-libc-documentation.63107/。
首先安装依赖:
# pkg install netpbm groff ghostscript9-base
ghostscript9-base 已弃用。ghostscript10 替代不了,会卡住。
然后执行该脚本:
#!/bin/sh
pstarget="/tmp/$$.libcdoc.ps"
pdftarget="libcdoc.pdf"
pdftarget_noidx="/tmp/$$.$pdftarget"
pdfindex="/tmp/$$.pdfindex.info"
index="/tmp/$$.index"
sorted_index="$index.sorted"
flist="/tmp/$$.flist"
tocin="/tmp/$$.toc.mdoc"
keywords="/tmp/$$.keywords"
mandir="/usr/share/man"
paths="$mandir/man2 $mandir/man3"
content_offset=0
mkidx()
{
for i in `find $paths -name "*.gz"`; do
if zgrep -q '.Lb libc' $i && zgrep -q '.Sh LIBRARY' $i; then
for j in `gettitles $i`; do
echo "$j:$i" >> $index
done
fi
done
cat $index | sort -n | uniq | awk -F: 'BEGIN { prev = "" } {
if ($1 != prev) {
print $0;
}
prev = $1;
}' > $index.tmp
mv $index.tmp $index
for i in `cat $index`; do
fname=`echo $i | cut -d: -f2`
grep $fname $index | sort -n | awk -F: 'BEGIN {n = 0} {
if (n++ > 0)
printf ",";
printf "%s", $1;
}'
echo ":$fname"
done | sort -n | uniq > $index.tmp
mv $index.tmp $index
currp=1
for i in `cat $index`; do
fname=`echo $i | cut -d: -f2`
kwords=`echo $i | cut -d: -f1`
nextp=`mandoc -T ps $fname|egrep '%%Pages: [0-9]+'|cut -d: -f2`
echo "$kwords:$currp:$fname" >> $index.tmp
currp=`expr $currp + $nextp`
done
mv $index.tmp $index
for i in `cat $index | sed -E 's/(^[^:]+):.*/\1/' | tr ',' ' '`; do
echo $i
done | sort -n > $keywords
for i in `cat $keywords`; do
page=`grep -w $i $index | tail -1 | cut -d: -f 2`
echo $i:$page
done > $sorted_index
}
mkpsdoc()
{
for i in `cat $index`; do
fname=`echo $i | cut -d: -f3`
zcat $fname | sed -e 's/^\.Dd.*$/\.Dd __PAGENO__/' \
-e '/\.Os.*/d' | mandoc -T ps >> $pstarget
done
}
mktoc()
{
echo ".XS 1" > $tocin
echo "Table of Contents" >> $tocin
for i in `cat $sorted_index`; do
kword=`echo $i | cut -d: -f 1`
page=`echo $i | cut -d: -f 2`
page=`expr $content_offset + $page`
printf ".XA $page\n$kword\n" >> $tocin
done
echo ".XE" >> $tocin
echo ".PX" >> $tocin
}
get_content_offset()
{
mktoc
content_offset=`groff -T ps -ms $tocin | egrep '%%Pages: [0-9]+' | \
cut -d: -f2`
content_offset=`expr $content_offset + 0`
}
prepend_toc()
{
in=$1
tmp=$in.tmp
groff -T ps -ms $tocin > $tmp
cat $in >> $tmp
mv $tmp $in
}
mkpdfidx()
{
printf "[/Page 1 /View [/XYZ null null null] " > $pdfindex
printf "/Title (Table of Contents) /OUT pdfmark\n" >> $pdfindex
for i in `cat $sorted_index`; do
kword=`echo $i | cut -d: -f 1`
page=`echo $i | cut -d: -f 2`
page=`expr $page + $content_offset`
printf "[/Page $page /View " >> $pdfindex
printf "[/XYZ null null null] " >> $pdfindex
printf "/Title ($kword) /OUT pdfmark\n" >> $pdfindex
done
}
gettitles()
{
zcat $1 | sed -n '/.Sh NAME/,/.Sh LIBRARY/p' | \
egrep '^\.Nm [^ ]+' | cut -d" " -f 2 | sort -n | uniq
}
mkidx
mkpsdoc
get_content_offset
mktoc
prepend_toc $pstarget
mkpdfidx
cat $pstarget | awk -v p=$content_offset '{
if ($0 ~ /\(__PAGENO__\)/) {
t = sprintf("(%s)", ++p);
sub(/\(__PAGENO__\)/, t);
}
print $0;
}' > $pstarget.tmp
mv $pstarget.tmp $pstarget
ps2pdf $pstarget $pdftarget_noidx
gs -sDEVICE=pdfwrite -q -dBATCH -dNOPAUSE -sOutputFile=$pdftarget $pdfindex \
-f $pdftarget_noidx
rm -f $tocin
rm -f $pstarget
rm -f $index
rm -f $pdftarget_noidx
rm -f $pdfindex
rm -f $sorted_index
改进方案
原有方案的问题:
if zgrep -q '.Lb libc' $i && zgrep -q '.Sh LIBRARY' $i; then
这一句的问题是.Lb libc
匹配的不只是 libc,还有 libcalendar 等以 libc 开头的库。可以写成.Lb libc$
来改正这个问题正文组织排序并不合理,并不是按功能模块等组合,用于学习并不合适,用于速查倒是可行。
下面提供的方案就是解决上面这两个问题,代码可以保存为脚本文件运行,不过我不建议,里面有两处可以手工调整,建议分步执行。
首先安装依赖:
# pkg install groff ghostscript10
或者
# cd /usr/ports/textproc/groff/ && make install clean
# cd /usr/ports/print/ghostscript10/ && make install clean
然后执行该脚本:
#!/bin/sh
fetch https://mirrors.ustc.edu.cn/freebsd/releases/amd64/14.1-RELEASE/src.txz # 下载 src
tar jxvf src.txz usr/src/lib/libc/*.[23] # 解压 man2,man3
mv usr/src/lib/libc libc # 减小目录层级
rm -rf usr
find libc -name *.[23] > content.list # 向 content.list 写入 man2,man3 路径
cat content.list | sed -e 's/libc\///' -e 's/\/.*$//' | uniq |sort > level1.list # 删除路径中 libc/ 前缀,删除尾部到 /,
# 目地是保留 libc 路径下的第一层路径名,作为一级目录名使用
# 生成 level1.list 后可调整 level1.list 中的行顺序,以指定章节顺序
###################################################################
# 生成 level2.list 作为 2 级目录使用,格式 路径:标题
# 内层循环按 level1 分组抽取文档标题
# 外层循环对每个分组按标题排序后写入 level2.list
cat /dev/null >level2.list # 清空 level2.list ,因为我在反复操作,先清空
for i in `cat level1.list `;do
cat /dev/null >level15.list
for j in `grep "libc/$i" content.list`;do
col2=`cat $j | sed -n '/.Sh NAME/,/.Sh/p' | egrep '^.Nd [^ ]+' | sed -e 's/^.Nd //' -e 's/\"//g'` # 抽取文档标题
echo $j:$col2 >>level15.list
done
cat level15.list | sort -t: -r -k2 >>level2.list
done
# 生成 level2.list 后可调分组调整 level2.list 中的行顺序,以指定章内小节顺序
#######################################################################
# toc.mdoc 用于生成目录
# bookmark.info 用于生成 pdf 的标签
# index.list 记录每个关键字的页码
# mktoc 须运行两次,第一次获得目录所占页数,第二次用目录所占页数作偏移量计算正文页码
mktoc(){
cat /dev/null > toc.mdoc
cat /dev/null > bookmark.info
cat /dev/null > index.list
n=$1 # 起始页页码
xsflag=1 # 第一个目录条目标记与其它不同
for lv1 in `cat level1.list`;do # 按level1分组处理
titlecount=`grep "libc/${lv1}" level2.list | wc -l` # 各章小节总数
echo "[ /Title ($lv1) /Page $n /Count $titlecount /View [/XYZ null null 0] /OUT pdfmark" >> bookmark.info # pdf 章标签
first=1 # 目录中,在每章各小节前插入章名,first用于标记第一节
for lv2 in `grep "libc/${lv1}" level2.list | cut -d: -f1`;do
title=`cat $lv2 | sed -n '/.Sh NAME/,/.Sh/p' | egrep '^.Nd [^ ]+' | sed -e 's/^.Nd //' -e 's/\"//g'` # 抽取小节标题
nextp=`mandoc -T ps $lv2 | egrep '%%Pages: [0-9]+' | cut -d: -f2` # 计算小节页数
if [ $first -eq 1 ];then # 每章第一节前插入章名
if [ $xsflag -eq 1 ];then # 第一章第一节,是整个目录的第一节使用XS标记
echo ".XS $n" >> toc.mdoc
xsflag=0
else
echo ".XA $n" >> toc.mdoc
fi
echo ".BX ${lv1}____________________________________" >> toc.mdoc # 目录不支持多级目录,只能通过标记以示不同
first=0
fi
echo ".XA $n" >> toc.mdoc # 写入小节页码
echo $title >> toc.mdoc # 写入小节标题
echo "[ /Title ($title) /Page $n /View [/Fit] /OUT pdfmark" >> bookmark.info # pdf小节标签
# 小节可有多个关键字,关键字和页码写入index.list
for key in `cat $lv2 | sed -n '/.Sh NAME/,/.Sh/p' | egrep '^\.Nm [^ ]+' | cut -d" " -f 2 | sort -n | uniq`;do
echo "$key:$n" >> index.list
done
n=`expr $n + $nextp` # 计算新页码
done
done
echo ".XE" >> toc.mdoc # 目录结束标记
echo ".PX" >> toc.mdoc
groff -T ps -ms toc.mdoc > toc.ps # 目录转成ps格式
}
###########################################################################################
# 每个文档转成ps格式,并拼接成一个文档,Dd(document date)标记替换成__PAGENO__,以供后面用页码替换
for lv2 in `cat level2.list | cut -d: -f1`;do
cat $lv2 | sed -e 's/^\.Dd.*$/\.Dd __PAGENO__/' -e '/\.Os.*/d' | mandoc -T ps >> libc.ps
done
###########################################################################################
# 处理页码
mktoc 1 # 正文第一页页码为1,执行第一遍生成目录
tocpages=`cat toc.ps | egrep '%%Pages: [0-9]+' | cut -d: -f2` # 计算目录占用页数
newstart=`expr $tocpages + 1`
mktoc $newstart # 正文第一页页码紧接目录页码
tocpages=`expr $tocpages + 0` # 转成整型
cat libc.ps | awk -v p=$tocpages '{
if ($0 ~ /\(__PAGENO__\)/) {
t = sprintf("(%s)", ++p);
sub(/\(__PAGENO__\)/, t);
}
print $0;
}' > libc.ps.tmp # 为每一页生成页码
cat libc.ps.tmp >> toc.ps # 拼接到toc.ps文件中,此时toc.ps为完整文件
ps2pdf toc.ps # 转成pdf格式但没有书签
####################################################################
# 按字母序对关键字排列生成索引并和章节标签合并
sort -f index.list > index.list.tmp # 对关键字排序忽略大小写
mv index.list.tmp index.list
cut -c 1 index.list | tr [:upper:] [:lower:] | uniq >> index.level1 # 提取首字母以便索引按字母分小节
indexcount=`wc -l index.level1 | cut -w -f2` # 计算共几个小节(分组)
echo "[ /Title (INDEX) /Count $indexcount /View [/XYZ null null 0] /OUT pdfmark" >> bookmark.info # 索引写入INDEX章标签
for a in `cat index.level1`;do # 按首字母对索引分组
acount=`grep -i "^$a" index.list | wc -l` # 计算每个首字母有多少关键字
echo "[ /Title ($a) /Count $acount /View [/XYZ null null 0] /OUT pdfmark" >> bookmark.info # 分组名
for i in `grep -i "^$a" index.list`;do # 每个关键字写入标签
key=`echo $i | cut -d: -f1`
page=`echo $i | cut -d: -f2`
echo "[ /Title ($key) /Page $page /View [/XYZ null null 0] /OUT pdfmark" >> bookmark.info
done
done
#####################################################################################
# 用 bookmark.info 和无标签的 toc.pdf 生成带标签的 libc.pdf
gs -sDEVICE=pdfwrite -q -dBATCH -dNOPAUSE -sOutputFile=libc.pdf bookmark.info -f toc.pdf
现成文本
运行脚本即可在同路径文件夹下找到 PDF 文档。现成的文档请看:
最后更新于