第 24.5 节 利用脚本自动生成 BSDlibc 库文本

论坛方案

此部分来自 FreeBSD 论坛,作者 mrclksr。原文地址 https://forums.freebsd.org/threads/wheres-bsd-libc-documentation.63107/

首先安装依赖:

# pkg install netpbm groff ghostscript9-base

然后执行该脚本:

#!/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

改进方案

原有方案的问题:

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

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

下面提供的方案就是解决上面这两个问题,代码可以保存为脚本文件运行,不过我不建议,里面有两处可以手工调整,建议分步执行。

首先安装依赖:

# pkg install groff ghostscript9-base

然后执行该脚本:

#!/bin/sh
fetch https://mirrors.ustc.edu.cn/freebsd/releases/amd64/13.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 文档。现成的文档请看:

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

最后更新于