本人日常使用Markdown進行筆記書寫,創建TOC(目錄)可起到內容索引的作用,但有些編輯器或平臺(eg:GitHub)並不支持TOC,故需要手動創建TOC目錄。當筆記內容量很大時,這項工作耗時很長,很影響學習效率。在對如何創建TOC有一定心得後,想通過Shell Script來實現自動生成TOC。

以下是本人寫的腳本,簡單實現TOC目录自动生成。


Personal Requests

  • 自動創建TOC
  • 去除特殊標點符號對TOC的影響
  • 生成的TOC保存到源文件中

Update

  1. 2016.02.02
    • 按目錄層級自動編號
  2. 2016.08.11
    • 解決重複出現的標題的索引問題
    • 解決代碼塊中符號#被腳本視爲標題的問題

Thinking

  • 使用$1判斷輸入的文件路徑是否存在
  • 讀取源文件,獲取以#開頭的行,通過管道符|輸出給while循環
  • 獲取去除#後的數據並進行處理
  • 將處理好的數據進行拼接,生成所需的TOC目錄

Update

  1. 2016.02.02
    • 使用case函數實現自動編號功能
  2. 2016.08.11
    • 使用awk解決相關問題

Script

對字符串的處理主要通過bash字符串處理工具,去除特殊標點符號對TOC目錄的影響(Markdown中的TOC對某些特殊符號採取忽略策略)。参见本人博客 Experience Using Markdown

最新版Script GitHub

舊版Script [V3](https://github.com/LempStacker/personalShellScriptCollection/blob/master/shellScripts/autoCreateMarkdownTOC_v3.sh、 V2V1

以下是最新版本的Shell Script代碼

#!/usr/bin/env bash
#Official Site: https://daringfireball.net/projects/markdown/
#Target: Automatically Generating Table Of Contents(TOC) For Markdown File(.md) On GNU/Linux
#Writer: LempStacker
#Date: 2017.04.11 17:56 Tue +0800
#Update Time:
#--2016.02.02 21:40 Tue +0800
#--2016.08.11 08:27 ~ 15:51 Thu +0800
#--2017.02.22 08:47 ~ 11:24 Wed  +0800
#--2016.01.29 20:00 Fri +0800


########  0. Initialization Setting  ########
# 30black 31red 32green 33pink 34blue 35purple 36light blue 37gray
c_red='\e[31;1m'    # font color
c_blue='\e[34m'       # font color
c_end='\e[0m'       # font color

printf "Usage: $c_red%s$c_end!\n" "bash /PATH/Script /PATH/TargetFIle (git|gitlab|github|hexo) (ol|ul)"

script_save_path=$(dirname $(readlink -f "$0"))   # path where this script save

# - 判斷文件路徑是否指定,文件是否存在
[[ -z "$1" ]] && printf "Sorry, no file specified. Script will stop and exit!\n" && exit 1     # file path specified or not
file_save_path=$(readlink -f "$1")
[[ ! -f "$file_save_path" ]] && printf "Sorry, File path $c_red%s$c_end not exists. Please check then retry!\n" "$file_save_path" && exit 2     # file path exist or not

# - 爲GitLab/Github flag=1 或 Hexo flag=0創建TOC, Git* 爲默認值
if [[ -z "${2,,}" ]]; then
    is_for_git=1
else
    case "${2,,}" in
        git|gitlab|github )
            is_for_git=1
            ;;
        hexo )
            is_for_git=0
            ;;
        * )
            is_for_git=0
            ;;
    esac    #End case
fi  #End if


# - TOC顯示格式 默認爲數字格式
if [[ -z "${3,,}" ]]; then
    list_format=0    # 0 爲Ordered Lists 數字格式, 1 爲 Unordered Lists 非數字格式
else
    case "${3,,}" in
        ol )
            list_format=0
            ;;
        ul )
            list_format=1
            ;;
        * )
            list_format=1
            ;;
    esac    #End case
fi  #End if


# - 數組索引代表符號 # 的個數,標題共6級, 1個代表標題,從 2 開始
declare -A flag_arr
flag_arr=([2]=0 [3]=0 [4]=0 [5]=0 [6]=0)



########  1. Text Processing  ########
tempfile=`mktemp -t tempXXXXXX.txt`
tempnewfile=`mktemp -t tempXXXXXX.txt` #存儲篩選後屬於標題的item

# - 獲取符合條件的item及其行號:從第二行開始以符號#開頭
awk 'NR>1&&match($0,/^#/){printf("%s|%s\n",NR,$0)}' "$file_save_path" > $tempfile

#變量num用於標記行數
num=1
while read line;do
    line_number=${line%%|*}  #提取行號
    item=${line#*|}         #提取item
    #通過```出現次數判斷item是否屬於標題,\`用於轉義符號`
    counts=`awk '$0~/^\`{3}/&&NR<'"$line_number"'{a++}END{print a}' "$file_save_path"`
    #判斷出現次數,如果是奇數則丟棄,如果是偶數則保留
    #-z判斷是否爲空,第一個counts是空,但空取餘後是奇數,結果錯誤,故添加該判斷
    if [[ -z $counts || "$counts"%2 -eq 0 ]]; then
        echo "$num|$item" >> $tempnewfile
        let num+=1
    fi  #End if

done < $tempfile   #End while


# - 拼湊索引
while read line;do
    #提取行號、item
    line_number=${line%%|*}
    item=${line#*|}

    #分別獲取左側,右側內容
    title_content=${item##*'#'}      #可同時兼容#後是否空格 tileContent (right)
    title_level=${item/"$title_content"}     # title_level    (left)

    #從文件開頭到該標題所在行,標題的出現次數,用於標記重複出現的標題,注意:不區分是幾級標題
    #使用符號$進行末尾匹配

    temp_title_content=$(echo $title_content | sed -r 's@[[:punct:]]@@g')

    counts=`awk '$0~/'"${temp_title_content}"'$/&&NR<='"$line_number"'{a++}END{print a}' "$tempnewfile"`
    unset temp_title_content

    len=${#title_level}     #通過${#var}獲取符號`#`的個數

    case $len in
        2)
            flag_arr[2]=$(( ${flag_arr[2]}+1 ))
            flag_arr[3]=0
            flag_arr[4]=0
            flag_arr[5]=0
            flag_arr[6]=0
            flag=${flag_arr[2]}.
            ;;
        3)
            flag_arr[3]=$(( ${flag_arr[3]}+1 ))
            flag_arr[4]=0
            flag_arr[5]=0
            flag_arr[6]=0

            if [[ $list_format -eq 0 ]]; then
                flag=${flag_arr[2]}.${flag_arr[3]}
            else
                flag="    *"    # 1個Tab(每個Tab爲4個空格)
            fi
            ;;
        4)
            flag_arr[4]=$(( ${flag_arr[4]}+1 ))
            flag_arr[5]=0
            flag_arr[6]=0

            if [[ $list_format -eq 0 ]]; then
                flag=${flag_arr[2]}.${flag_arr[3]}.${flag_arr[4]}
            else
                flag="        *"    # 2個Tab(每個Tab爲4個空格)
            fi
            ;;
        5)
            flag_arr[5]=$(( ${flag_arr[5]}+1 ))
            flag_arr[6]=0

            if [[ $list_format -eq 0 ]]; then
                flag=${flag_arr[2]}.${flag_arr[3]}.${flag_arr[4]}.${flag_arr[5]}
            else
                flag="            *"    # 3個Tab(每個Tab爲4個空格)
            fi
            ;;
        6)
            flag_arr[6]=$(( ${flag_arr[6]}+1 ))
            flag=${flag_arr[2]}.${flag_arr[3]}.${flag_arr[4]}.${flag_arr[5]}.${flag_arr[6]}

            if [[ $list_format -eq 0 ]]; then
                flag=${flag_arr[2]}.${flag_arr[3]}.${flag_arr[4]}.${flag_arr[5]}.${flag_arr[6]}
            else
                flag="                *"   # 4個Tab(每個Tab爲4個空格)
            fi
            ;;
    esac    #End case

    #去除行首空格
    right="${title_content#"${title_content%%[![:space:]]*}"}"
    #查找所有匹配的特殊字符並刪除
    # temp=${right//[_#@$)(\{\}\`\~\!\$\%\^\&\*\+\=\:\;\'\"\<\>\.\,\;\?\/\\\|\[\]\,\:]}
    temp=$(echo $right | sed -r 's@[[:punct:]]@@g;s@[[:blank:]]+@ @g')
    #將空格替換爲-
    temp=${temp//[' ']/-}
    #去除行首的- ${var/#PATTERN/SUBSTI}
    temp=${temp/#-}
    #去除行尾的- ${var/%PATTERN/USBSTI}
    temp=${temp/%-}
    #字符轉換爲小寫
    [[ $is_for_git -eq 1 ]] && temp=${temp,,} #Gitlab/GitHub中TOC需全部轉換爲小寫,而Hexo則無需進行大小寫轉換

    [[ $counts -gt 1 ]] && result=$temp'-'$(($counts-1)) || result=$temp    #如果出現多次,則需在其後加上出現的次數,用符號 - 間隔

    # 將生成的TOC添加至文件末尾
    echo "$flag "'['$right'](#'$result')  ' >> "$1"     #GitHub中須在字串後添加2個空格,TOC才能自動換行

done < $tempnewfile     #End while

[[ "$is_for_git" -eq 1 ]] && usage='Git' || usage='Hexo'
[[ "$list_format" -eq 1 ]] && format='Unordered Lists' || format='Ordered Lists'

printf "successfully create TOC for file $c_blue%s$c_end!\nUsage type is $c_blue%s$c_end, List format is $c_blue%s$c_end!\n" "$file_save_path" "$usage" "$format"


########  2.Unset Viriables & Remove Temp File  ########
unset c_red
unset c_blue
unset c_end
unset script_save_path
unset file_save_path
unset is_for_git
unset list_format
unset flag_arr
unset num
unset usage
unset format
[[ -f "$tempfile" ]] && rm -f "$tempfile"
unset tempfile
[[ -f "$tempnewfile" ]] && rm -f "$tempnewfile"
unset tempnewfile

# End Script

Usage

命令格式

bash /PATH/Script /PATH/TargetFIle (git|gitlab|github|hexo) (ol|ul)

默認爲Git格式,目錄採用數字列表格式

FILEPATH建議使用絕對路徑,如果其中含有空格,須用雙引號""將其包裹。

[flying@lemp Desktop]$ bash autoCreateMarkdownTOC.sh "/home/flying/Desktop/2016.01.14Apache httpd.md"
Congratulations
[flying@lemp Desktop]$

手动撰写的TOC

1. [Preparation Knowledges](#preparation-knowledges)
1.1 [Port Assignment](#port-assignment)
1.2 [BSD Socket](#bsd-socket)
1.3 [TCP FSM](#tcp-fsm)
1.4 [Characteristics of Transmission Control Protocol](#characteristics-of-transmission-control-protocol)
2. [Hypertext Transfer Protocol Daemon](#hypertext-transfer-protocol-daemon)
2.1 [Characteristics of Apache httpd](#characteristics-of-apache-httpd)
2.1.1 [MPMs](#mpms)
2.2 [Function Characteristics of Apache httpd](#function-characteristics-of-apache-httpd)
2.3 [httpd installation](#httpd-installation)
3. [**Path Of Configuration Files in CentOS**](#path-of-configuration-files-in-centos)
4. [httpd-2.2 Configurations](#httpd-22-configurations)
4.1 [1 Listen IP:port](#1-listen-ipport)
4.2 [2 Persistent Connection](#2-persistent-connection)
4.3 [**3 MPMs**](#3-mpms)
4.4 [4 DSO](#4-dso)
4.5 [5 Declare DocumentRoot Of Main Server](#5-declare-documentroot-of-main-server)
4.6 [6 站點訪問控制常見機制](#6-站點訪問控制常見機制)
4.7 [7 DirectoryIndex](#7-directoryindex)
4.8 [8 Alias](#8-alias)
4.9 [9 Set Default Charset](#9-set-default-charset)
4.10 [10 Log Setting](#10-log-setting)
4.11 [11 基於用戶的訪問控制](#11-基於用戶的訪問控制)
4.12 [12 VirtualHost](#12-virtualhost)
4.13 [13 status頁面](#13-status頁面)
4.14 [14 user/group](#14-usergroup)
4.15 [15 使用`mod_deflate`模塊壓縮頁面優化傳輸速度](#15-使用moddeflate模塊壓縮頁面優化傳輸速度)
4.16 [16 https configuration](#16-https-configuration)
4.17 [17 http自帶的工具程序](#17-http自帶的工具程序)
5. [httpd-2.4介紹](#httpd-24介紹)
6. [httpd-2.4安裝](#httpd-24安裝)
6.1 [CentOS 6](#centos-6)
6.1.1 [Compiled Installation](#compiled-installation)
6.2 [CentOS 7](#centos-7)

自动生成的TOC

1. [Preparation Knowledges](#preparation-knowledges)
1.1 [Port Assignment](#port-assignment)
1.2 [BSD Socket](#bsd-socket)
1.2 [Socket_API_functions 'Wikipedia')](#socketapifunctions-wikipedia)
1.2.1 [Socket Domain](#socket-domain)
1.3 [TCP FSM](#tcp-fsm)
1.4 [Characteristics of Transmission Control Protocol](#characteristics-of-transmission-control-protocol)
2. [Hypertext Transfer Protocol Daemon](#hypertext-transfer-protocol-daemon)
2.1 [Characteristics of Apache httpd](#characteristics-of-apache-httpd)
2.1.1 [MPMs](#mpms)
2.2 [Function Characteristics of Apache httpd](#function-characteristics-of-apache-httpd)
2.3 [httpd installation](#httpd-installation)
3. [Path Of Configuration Files in CentOS](#path-of-configuration-files-in-centos)
4. [httpd-2.2 Configurations](#httpd-22-configurations)
4.1 [1 Listen IP:port](#1-listen-ipport)
4.2 [2 Persistent Connection](#2-persistent-connection)
4.2.1 [測試](#測試)
4.3 [3 MPMs](#3-mpms)
4.3.1 [更改使用的MPM模塊](#更改使用的mpm模塊)
4.3.2 [MPM配置](#mpm配置)
4.3.3 [確認現在使用的是哪種程序文件](#確認現在使用的是哪種程序文件)
4.3.4 [如何查看httpd程序的模塊列表](#如何查看httpd程序的模塊列表)
4.4 [4 DSO](#4-dso)
4.5 [5 Declare DocumentRoot Of 'Main' Server](#5-declare-documentroot-of-main-server)
4.6 [6 站點訪問控制常見機制](#6-站點訪問控制常見機制)
4.6.1 [`<Directory>`基於源地址實現訪問控制](#directory基於源地址實現訪問控制)
4.7 [7 DirectoryIndex](#7-directoryindex)
4.8 [8 Alias](#8-alias)
4.8.1 [示例](#示例)
4.9 [9 Set Default Charset](#9-set-default-charset)
4.10 [10 Log Setting](#10-log-setting)
4.10.1 [ErrorLog](#errorlog)
4.10.2 [CustomLog](#customlog)
4.11 [11 基於用戶的訪問控制](#11-基於用戶的訪問控制)
4.11.1 [basic認證配置示例](#basic認證配置示例)
4.11.1.1 [定義安全域](#定義安全域)
4.11.1.2 [允許帳號文件中的所有用戶登錄訪問](#允許帳號文件中的所有用戶登錄訪問)
4.11.2 [提供帳號密碼存儲(文本文件)](#提供帳號密碼存儲文本文件)
4.11.3 [基於`組帳號`進行認證](#基於組帳號進行認證)
4.11.3.1 [定義安全域](#定義安全域)
4.11.3.2 [創建用戶帳號和組帳號文件](#創建用戶帳號和組帳號文件)
4.12 [12 VirtualHost](#12-virtualhost)
4.12.1 [站點標誌 socket](#站點標誌-socket)
4.12.2 [VirtualHost 配置方法](#virtualhost-配置方法)
4.12.2.1 [基於IP的VirtualHost示例](#基於ip的virtualhost示例)
4.12.2.2 [基於端口的VirtualHost示例](#基於端口的virtualhost示例)
4.12.2.3 [基於FQDN的虛擬主機](#基於fqdn的虛擬主機)
4.13 [13 status頁面](#13-status頁面)
4.14 [14 user/group](#14-usergroup)
4.15 [15 使用`mod_deflate`模塊壓縮頁面優化傳輸速度](#15-使用moddeflate模塊壓縮頁面優化傳輸速度)
4.16 [16 https configuration](#16-https-configuration)
4.16.1 [`配置httpd支持https`](#配置httpd支持https)
4.17 [17 http自帶的工具程序](#17-http自帶的工具程序)
4.17.1 [http壓力測試工具](#http壓力測試工具)
4.17.1.1 [ab](#ab)
5. [httpd-2.4介紹](#httpd-24介紹)
5.1 [新特性](#新特性)
5.2 [新模塊](#新模塊)
6. [httpd-2.4安裝](#httpd-24安裝)
6.1 [CentOS 6](#centos-6)
6.1.1 [Compiled Installation](#compiled-installation)
6.1.2 [添加環境變量](#添加環境變量)
6.2 [CentOS 7](#centos-7)
6.2.1 [配置應用](#配置應用)
7. [Change Logs](#change-logs)

Expansion Problem

在簡單實現自動創建TOC的基礎上,想按目錄層級自動進行編號,思考量一下,稍微有些繁瑣,以後再說。

Update

已經成功實現按目錄層級自動創建帶有編號的TOC,但對特殊符號的處理還不夠完善。


Change Log

  • 2016.01.29 18:00 Fri Asia/Beijing
    • 初稿完成,上傳至lempstacker部落格
  • 2016.02.02 21:58 Tue Asia/Beijing
    • 實現按目錄層級編號
  • 2016.08.11 16:11 Thu Asia/Shanghai
    • 解決重複出現的標題的索引問題
    • 解決代碼塊中符號#被腳本視爲標題的問題
  • 2017.02.22 11:44 Wed Asia/Shanghai
    • 代碼優化、添加使用格式說明、添加爲Git/Hexo添加對應格式索引、支持數字、非數字列表目錄
  • 2017.04.11 17:57 Tue Asia/Shanghai
    • 解決符號.,/在awk中引起報錯,sed中使用字符類處理特殊符號

  • Note Time: 2016.01.29 18:00 Fri
  • Note Location: Asia/Beijing
  • Writer: lempstacker