22.14 利用脚本自动生成 BSDlibc 库文本

论坛方案

原方案已失效。

改进方案

对原方案的改进:

  • if zgrep -q '.Lb libc' $i && zgrep -q '.Sh LIBRARY' $i; then 这一句的问题是 .Lb libc 匹配的不只是 libc,还有 libcalendar 等以 libc 开头的库。可以写成 .Lb libc$ 来改正这个问题

  • 正文组织排序并不合理,并不是按功能模块等组合,用于学习并不合适,用于速查倒是可行。


首先安装需要的软件包。

  • 使用 pkg 安装:

# pkg install groff ghostscript10
  • 或者使用 Ports 安装:

# 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.2-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   # 第一个目录条目标记与其它不同
chapterid=0
for lv1 in `cat level1.list`;do      # 按 level1 分组处理
	chapterid=$(( chapterid + 1 ))
    sectionid=0
    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 "$chapterid. ${lv1}" >> toc.mdoc  # 目录不支持多级目录,只能通过标记以示不同
            first=0
        fi
        sectionid=$(( sectionid + 1 ))
        echo ".XA $n" >> toc.mdoc   # 写入小节页码
        echo '      '"$sectionid. $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 为完整文件


####################################################################

# 按字母序对关键字排列生成索引并和章节标签合并
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 章标签
echo ".DS C" >>printindex.mdoc
echo "INDEX" >>printindex.mdoc
echo ".DE" >>printindex.mdoc
echo ".SP 1" >>printindex.mdoc
echo ".2C" >>printindex.mdoc
echo ".LB 0 0 0 0" >>printindex.mdoc
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
      echo ".LI" >>printindex.mdoc
      printf '%-40s %4d\n' "$key" $page >>printindex.mdoc
   done
done
echo ".LE" >>printindex.mdoc
#####################################################################################
groff -Tps -mm printindex.mdoc >>toc.ps
ps2pdf toc.ps  # 转成 pdf 格式但没有书签
# 用 bookmark.info 和无标签的 toc.pdf 生成带标签的 libc.pdf
gs -sDEVICE=pdfwrite -q -dBATCH -dNOPAUSE -sOutputFile=libc.pdf bookmark.info -f toc.pdf

rm -rf libc toc.* printindex.mdoc level* index.* content.list bookmark.info libc.ps libc.ps.tmp

现成文本

运行脚本即可在同路径文件夹下找到 PDF 文档。现成的文档请看:

https://github.com/FreeBSD-Ask/BSDlibc

最后更新于