第 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,但不同工具和场景有不同的命名习惯:
| 文件名 | 使用场景 | 说明 |
|---|---|---|
tags | Vim | Vim 默认读取 |
TAGS | Emacs | Emacs 默认读取(-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 | 交叉引用格式输出 |
扩展阅读
- 📖 Universal Ctags 标签文件格式
- 📖 Exuberant Ctags 手册页
- 📖 Vim :help tagsrch.txt
- 📖 Understanding ctags (Emacs Wiki)
上一章 ← 第 2 章:安装与环境配置 · 下一章 → 第 4 章:语言支持与解析器