强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 3 章:基本用法

第 3 章:基本用法

3.1 生成标签文件

生成标签文件是 Ctags 最核心的操作。一个命令即可为整个项目建立符号索引。

最简用法

# 递归扫描当前目录,生成 tags 文件
ctags -R .

# 等价于
ctags --recurse .

常用命令行参数

# 指定输出文件名
ctags -R -o mytags .

# 只扫描特定语言
ctags -R --languages=c,c++ .

# 排除某些目录
ctags -R --exclude=.git --exclude=node_modules .

# 增量模式:追加新标签到已有文件
ctags -R --append=yes src/new_module.c

# 显示处理过程
ctags -R --verbose=yes . 2>&1 | head -20

完整参数速查

参数简写说明
--recurse-R递归扫描子目录
--output=FILE-o FILE指定输出文件(默认 tags
--languages=LIST只处理指定语言
--exclude=PATTERN排除匹配的文件/目录
--append=yes-a追加模式
--sort=yes-f排序输出(默认按文件)
--sort=yes按符号名排序
--verbose-V详细输出
--fields=LIST指定输出字段
--kinds-LANG=LIST指定语言的 kind 过滤
--help-h显示帮助

3.2 实际操作演示

让我们通过一个完整的例子来学习:

创建示例项目

mkdir -p /tmp/ctags-demo/src
cd /tmp/ctags-demo

# 创建 C 文件
cat > src/math.c << 'EOF'
#include <stdio.h>

#define MAX_BUFFER 1024

typedef struct {
    double x;
    double y;
} Point;

static int count = 0;

double add(double a, double b) {
    return a + b;
}

double multiply(double a, double b) {
    return a * b;
}

int main(int argc, char *argv[]) {
    Point p = {1.0, 2.0};
    double result = add(p.x, p.y);
    printf("Result: %f\n", result);
    return 0;
}
EOF

# 创建头文件
cat > src/math.h << 'EOF'
#ifndef MATH_H
#define MATH_H

typedef struct {
    double x;
    double y;
} Point;

double add(double a, double b);
double multiply(double a, double b);

#endif /* MATH_H */
EOF

# 创建 Python 文件
cat > src/utils.py << 'EOF'
import os
import sys

class ConfigManager:
    """管理配置文件"""

    def __init__(self, path):
        self.path = path
        self._data = {}

    def load(self):
        """加载配置"""
        with open(self.path) as f:
            self._data = parse(f)

    def get(self, key, default=None):
        return self._data.get(key, default)

def parse(text):
    """解析配置文本"""
    result = {}
    for line in text.strip().split('\n'):
        if '=' in line:
            key, value = line.split('=', 1)
            result[key.strip()] = value.strip()
    return result

def main():
    config = ConfigManager('config.ini')
    config.load()
    print(config.get('host', 'localhost'))

if __name__ == '__main__':
    main()
EOF

生成标签文件

cd /tmp/ctags-demo

# 生成标签文件
ctags -R .

# 查看生成的标签文件
wc -l tags
# 输出: 20+ tags

# 查看内容
cat tags

标签文件内容示例

!_TAG_FILE_FORMAT	2	/extended format; --format=1 will not append ;" to lines/
!_TAG_FILE_SORTED	1	/0=unsorted, 1=sorted, 2=foldcase/
!_TAG_OUTPUT_FILESEP	slash	/slash or backslash/
!_TAG_OUTPUT_MODE	u-ctags	/u-ctags or e-ctags/
!_TAG_PATTERN_LENGTH_LIMIT	96	/0 for no limit/
!_TAG_PROC_CWD	/tmp/ctags-demo//
!_TAG_PROGRAM_AUTHOR	Universal Ctags Team	//
!_TAG_PROGRAM_NAME	Universal Ctags	/Derived from Exuberant Ctags/
!_TAG_PROGRAM_URL	https://ctags.io/	/official site/
!_TAG_PROGRAM_VERSION	6.1.0	/6.1.0/
ConfigManager	src/utils.py	/^class ConfigManager:$/;"	c
ConfigManager	src/utils.py	/^    def __init__(self, path):$/;"	m	class:ConfigManager
MAX_BUFFER	src/math.c	/^#define MAX_BUFFER 1024$/;"	d
Point	src/math.c	/^} Point;$/;"	t	typeref:struct:Point
Point	src/math.h	/^} Point;$/;"	t	typeref:struct:Point
add	src/math.c	/^double add(double a, double b) {$/;"	f	typeref:typename:double
add	src/math.h	/^double add(double a, double b);$/;"	prototype
count	src/math.c	/^static int count = 0;$/;"	v	file:
get	src/utils.py	/^    def get(self, key, default=None):$/;"	m	class:ConfigManager
load	src/utils.py	/^    def load(self):$/;"	m	class:ConfigManager
main	src/math.c	/^int main(int argc, char *argv[]) {$/;"	f
main	src/utils.py	/^def main():$/;"	f
multiply	src/math.c	/^double multiply(double a, double b) {$/;"	f	typeref:typename:double
multiply	src/math.h	/^double multiply(double a, double b);$/;"	prototype
parse	src/utils.py	/^def parse(text):$/;"	f

💡 提示:注意标签文件以 !_ 开头的行是伪标签(pseudo tags),包含文件格式、排序方式、程序版本等元信息。


3.3 标签文件格式详解

每行一个标签记录,字段之间用 Tab\t)分隔:

符号名<TAB>文件名<TAB>搜索模式;"<TAB>kind<TAB>[扩展字段...]

字段说明

add	src/math.c	/^double add(double a, double b) {$/;"	f	typeref:typename:double
│   │           │                                      │   │
│   │           │                                      │   └── 扩展字段
│   │           │                                      └── kind (f=function)
│   │           └── 搜索模式(Ex 模式命令,用于跳转)
│   └── 文件路径
└── 符号名称

搜索模式类型

Ctags 支持三种定位方式:

# 1. 正则搜索模式(默认,最常见)
add	src/math.c	/^double add(double a, double b) {$/;"	f

# 2. 行号定位
add	src/math.c	15;"	f

# 3. 混合模式(正则 + 偏移)
add	src/math.c	/^double add(double a, double b) {$/;"	f

Kind(符号类型)

每个标签都有一个 kind 字段,表示符号的类型:

f - function(函数)
v - variable(变量)
c - class(类)
m - member(成员)
d - macro(宏定义)
t - typedef(类型定义)
e - enumerator(枚举值)
s - struct(结构体)
u - union(联合体)
g - enum(枚举类型)
p - prototype(函数原型)
n - namespace(命名空间)
i - import(导入)
x - external(外部变量)

扩展字段

扩展字段以 key:value 的格式出现在 kind 之后:

f	typeref:typename:double	signature:(double a, double b)
│   │                        │
│   └── 类型引用              └── 函数签名
└── kind

常见扩展字段:

字段含义示例
typeref类型引用typeref:typename:double
signature函数签名signature:(int a, char *b)
scope作用域scope:class:MyClass
file:文件局部符号file:
line行号line:42
language语言language:Python

3.4 符号跳转

在 Vim 中使用标签跳转是 Ctags 最经典的用法。

基本跳转命令

在 Vim 中打开项目文件后:

" 跳转到光标下的符号定义
Ctrl-]

" 跳转并选择(多个匹配时)
g]

" 预览跳转(不离开当前窗口)
Ctrl-w ]

" 水平分割窗口跳转
Ctrl-w ]

" 垂直分割窗口跳转
Ctrl-w }

" 返回上一个位置
Ctrl-t

" 跳转到上一个标签
:tprev

" 跳转到下一个标签
:tnext

Vim 标签命令

" 列出所有匹配的标签
:tag /function_name

" 模糊搜索标签
:tselect /math

" 查看标签栈
:tags

" 标签栈示例输出:
"   # TO      FROM
"  1  1 f  main    src/math.c
"  2  1 f  add     src/math.c
" >           src/math.c

跳转流程图

光标在符号上        用户按 <Ctrl-]>
       │                  │
       ▼                  ▼
┌─────────┐        ┌─────────────┐
│ 编辑器   │  ──→   │ 读取 tags    │
│ 当前文件  │        │ 搜索符号名   │
└─────────┘        └──────┬──────┘
                          │
              ┌───────────┼───────────┐
              ▼           ▼           ▼
         1 个匹配     多个匹配      无匹配
              │           │           │
              ▼           ▼           ▼
         直接跳转    :tselect     提示错误
                      选择列表

3.5 在命令行中搜索标签

不必在编辑器中,也可以直接在命令行搜索标签:

使用 ctags 的 -x 选项

# 列出所有标签(交叉引用格式)
ctags -x src/math.c

# 输出示例:
# ConfigManager   class         1 src/utils.py      class ConfigManager:
# MAX_BUFFER      macro         4 src/math.c        #define MAX_BUFFER 1024
# Point           typedef      12 src/math.c        } Point;
# add             function     15 src/math.c        double add(double a, double b) {
# main            function     27 src/math.c        int main(int argc, char *argv[]) {

使用 grep 搜索标签文件

# 搜索精确匹配的符号
grep '^add\t' tags

# 搜索包含 pattern 的标签
grep -i 'main' tags

# 搜索某个文件中的所有标签
grep 'src/math\.c' tags

# 搜索某种 kind 的标签(如所有函数)
grep ';"\tf' tags

# 搜索某个类的成员
grep 'class:ConfigManager' tags

使用 awk 提取信息

# 列出所有函数名和所在文件
awk '/"\tf/ {print $1, $2}' tags

# 输出:
# add src/math.c
# main src/math.c
# main src/utils.py
# multiply src/math.c
# parse src/utils.py

# 列出 Python 类的成员
awk '/class:ConfigManager/ {print $1}' tags

3.6 标签文件命名约定

虽然默认输出文件名是 tags,但不同工具和场景有不同的命名习惯:

文件名使用场景说明
tagsVimVim 默认读取
TAGSEmacsEmacs 默认读取(-e 格式)
.tags隐藏文件保持项目目录整洁
tags.project多项目区分不同项目的标签
# 为 Vim 生成
ctags -R -o tags .

# 为 Emacs 生成(使用 etags 格式)
ctags -e -R -o TAGS .

# 生成隐藏文件
ctags -R -o .tags .

💡 提示:如果使用隐藏文件名 .tags,需要在 Vim 中显式指定:

set tags=.tags

3.7 处理特殊文件

符号链接

# 默认跟随符号链接
ctags -R .

# 不跟随符号链接
ctags -R --links=no .

标准输入

# 从标准输入读取
cat src/*.c | ctags -L - -o tags

# 等价于列出所有文件
find . -name "*.c" | ctags -L - -o tags

文件列表

# 从文件列表读取
find . -name "*.c" -o -name "*.h" > filelist.txt
ctags -L filelist.txt -o tags

# 常用:只索引 Git 跟踪的文件
git ls-files | ctags -L - -o tags

💡 提示:使用 git ls-files 可以避免索引 node_modules.git 等目录,比 --exclude 更精确。详见第 10 章


3.8 输出到标准输出

# 将标签输出到标准输出而非文件
ctags -f - src/math.c

# 管道处理
ctags -f - src/math.c | sort -t$'\t' -k3

# 查看特定语言的标签
ctags -f - --languages=python src/utils.py

3.9 本章小结

操作命令说明
生成标签ctags -R .递归扫描当前目录
指定输出-o file自定义输出文件名
语言过滤--languages=c,python只扫描指定语言
追加标签-a增量添加
Vim 跳转<Ctrl-]>跳转到定义
Vim 返回<Ctrl-t>返回上一位置
命令行搜索grep '^symbol\t' tags搜索特定符号
列出标签ctags -x file交叉引用格式输出

扩展阅读


上一章 ← 第 2 章:安装与环境配置 · 下一章 → 第 4 章:语言支持与解析器