一、find . -type f -name ".cpp" -o -name " .hpp" -o -name ".h" -o -name ".c" | xargs ls -lt | head -n 20
这条命令的作用大致是:
在当前目录及子目录中查找 C/C++ 源码文件,然后按修改时间从新到旧排序,显示最新的 20 个文件。
1. find .
bash
find .
表示从当前目录 . 开始递归查找。
例如当前目录下面有:
text
.
├── main.cpp
├── include/foo.h
└── src/bar.c
find . 会遍历这些文件和目录。
2. -type f
bash
-type f
表示只匹配普通文件,排除目录、软链接、设备文件等。
3. -name "*.cpp" -o -name "*.hpp" ...
bash
-name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.c"
这里是在匹配文件名:
*.cpp:C++ 源文件*.hpp:C++ 头文件*.h:C/C++ 头文件*.c:C 源文件
其中:
bash
-o
表示逻辑"或"。
所以意图是:
text
名字是 .cpp 或 .hpp 或 .h 或 .c
4. | xargs ls -lt
bash
| xargs ls -lt
前面的 find 会输出匹配到的文件路径,例如:
text
./main.cpp
./src/foo.c
./include/bar.h
然后通过管道 | 传给 xargs。
xargs ls -lt 会把这些文件作为参数传给:
bash
ls -lt 文件1 文件2 文件3 ...
其中:
bash
ls -lt
含义是:
-l:长格式显示,包括权限、所有者、大小、时间等-t:按修改时间排序,新的在前面
5. | head -n 20
bash
| head -n 20
只显示前 20 行。
结合 ls -lt,就是显示修改时间最新的 20 个文件。
命令的意图
bash
find 当前目录下所有 .cpp/.hpp/.h/.c 文件
然后按修改时间从新到旧排序
最后显示最新的 20 个
但这条命令有一个重要问题
原命令:
bash
find . -type f -name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.c"
在 find 里,-o 的优先级容易导致结果不是你想的那样。
它实际更接近于:
bash
find . \( -type f -name "*.cpp" \) -o -name "*.hpp" -o -name "*.h" -o -name "*.c"
也就是说,-type f 只严格作用到了 *.cpp,后面的 *.hpp、*.h、*.c 不一定都受 -type f 限制。
更稳妥的写法应该加括号:
bash
find . -type f \( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.c" \) | xargs ls -lt | head -n 20
注意括号前面要加反斜杠 \( 和 \),避免被 shell 解释。
更推荐的安全写法
如果文件名里可能有空格、换行等特殊字符,原来的 xargs 可能会出问题。
更安全的写法是:
bash
find . -type f \( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.c" \) -print0 | xargs -0 ls -lt | head -n 20
这里:
bash
-print0
用空字符分隔文件名。
bash
xargs -0
按空字符读取文件名。
这样可以正确处理类似这种文件名:
text
./src/my file.cpp
小结
原命令的作用是找出最近修改的 C/C++ 文件:
bash
find . -type f -name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.c" | xargs ls -lt | head -n 20
但更推荐改成:
bash
find . -type f \( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.c" \) -print0 | xargs -0 ls -lt | head -n 20
这样逻辑更准确,也更能处理复杂文件名。
二、find . -type f ( -name ".cpp" -o -name " .hpp" -o -name ".h" -o -name " .c" -o -name ".sh" -o -name "Makefile " ) -not -path "/." -not -name │ │
│ │ "comment_by_audit.txt" -exec grep -rnE "(//|/*|#).*\s+by\s+" {} + | tee comment_by_audit.txt | wc -l
这条命令的作用可以概括为:
在当前目录下递归查找 C/C++、Shell、Makefile 等源码文件,排除隐藏目录/隐藏文件以及
comment_by_audit.txt,然后在这些文件中搜索类似"注释里包含by某人"的行,把匹配结果写入comment_by_audit.txt,同时统计匹配行数。
命令如下,整理一下格式:
bash
find . -type f \
\( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.c" -o -name "*.sh" -o -name "Makefile*" \) \
-not -path "*/.*" \
-not -name "comment_by_audit.txt" \
-exec grep -rnE "(//|/\*|#).*\s+by\s+" {} + \
| tee comment_by_audit.txt \
| wc -l
下面逐段解释。
1. find . ## 2. -type f
3. 文件类型过滤
bash
\( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.c" -o -name "*.sh" -o -name "Makefile*" \)
这一段表示根据文件名查找:
- C++ 源文件:
*.cpp - C++ 头文件:
*.hpp - C/C++ 头文件:
*.h - C 源文件:
*.c - Shell 脚本:
*.sh - Makefile 文件:
Makefile、Makefile.xxx等
bash
-o
表示逻辑"或"。
括号:
bash
\( ... \)
是为了把这些 -name 条件组合起来,避免 find 的逻辑优先级导致匹配错误。
4. 排除隐藏路径
bash
-not -path "*/.*"
这一段表示排除路径中包含隐藏目录或隐藏文件的内容。
例如这些会被排除:
text
./.git/config
./.vscode/settings.json
./src/.cache/foo.c
因为它们的路径里包含:
text
/.
也就是某一级目录或文件名以 . 开头。
这个条件通常用于跳过:
text
.git
.svn
.cache
.vscode
.idea
等隐藏目录。
5. 排除输出文件本身
bash
-not -name "comment_by_audit.txt"
表示排除名为:
text
comment_by_audit.txt
的文件。
这是很重要的,因为后面会把结果写入这个文件。如果不排除它,下一次执行命令时可能会把上一次的结果文件也拿来搜索,造成污染或重复匹配。
6. 对找到的文件执行 grep
bash
-exec grep -rnE "(//|/\*|#).*\s+by\s+" {} +
这一段是核心。
-exec ... {} +
bash
-exec command {} +
表示对 find 找到的文件执行某个命令。
这里执行的是:
bash
grep -rnE "(//|/\*|#).*\s+by\s+" 文件列表
其中:
bash
{}
代表 find 找到的文件。
bash
+
表示把多个文件尽量合并成一次 grep 调用,而不是每个文件执行一次。这样效率更高。
7. grep -rnE 的含义
bash
grep -rnE
分别表示:
-r
递归搜索。
不过这里其实有点多余,因为文件已经由 find 找出来了,传给 grep 的是具体文件。
-n
显示匹配行的行号。
输出大概类似:
text
./src/main.cpp:42:// modified by Alice
其中 42 就是行号。
-E
使用扩展正则表达式。
这样可以使用更方便的正则语法,比如:
regex
a|b
表示匹配 a 或 b。
8. 正则表达式解释
regex
(//|/\*|#).*\s+by\s+
这段正则用于查找"注释里出现 by"的行。
拆开看:
(//|/\*|#)
匹配三种注释开头:
regex
//
C/C++ 单行注释,例如:
cpp
// changed by Tom
regex
/\*
C/C++ 块注释开头,例如:
cpp
/* created by Jerry */
这里写成:
regex
/\*
是因为 * 在正则中有特殊含义,需要用反斜杠转义。
regex
#
Shell 或 Makefile 注释,例如:
bash
# updated by Alice
.*
表示注释符号后面可以有任意字符。
例如:
cpp
// TODO: fixed by Bob
\s+by\s+
表示匹配:
text
空白字符 + by + 空白字符
也就是说,它希望 by 是一个独立单词,而不是别的单词的一部分。
会匹配:
cpp
// fixed by Alice
不会匹配:
cpp
// standby mode
因为 standby 中的 by 前面不是空白,后面也不一定是空白。
9. tee comment_by_audit.txt
bash
| tee comment_by_audit.txt
tee 的作用是:
- 把前面
grep的输出显示到终端; - 同时写入文件
comment_by_audit.txt。
也就是说,匹配结果会保存到:
text
comment_by_audit.txt
如果这个文件已存在,会被覆盖。
例如输出内容可能是:
text
./src/main.cpp:42:// modified by Alice
./scripts/build.sh:10:# generated by build system
./Makefile:5:# written by Bob
这些内容会同时出现在终端和 comment_by_audit.txt 中。
10. wc -l
bash
| wc -l
统计前面输出的行数。
因为前面是 grep 匹配结果,所以这里统计的是:
匹配到的注释行数量。
例如如果有 37 行注释匹配了这个模式,最终输出:
text
37
整条命令的最终效果
这条命令会查找类似下面的内容:
cpp
// modified by Alice
cpp
/* created by Bob */
c
// added by John
bash
# generated by script
makefile
# maintained by team
然后生成一个报告文件:
text
comment_by_audit.txt
里面记录所有匹配位置,例如:
text
./src/main.cpp:42:// modified by Alice
./include/foo.h:18:/* created by Bob */
./build.sh:7:# generated by build system
./Makefile:12:# maintained by build team
最后终端会输出匹配总行数。
一个小问题:grep -r 在这里不太必要
因为前面 find 已经递归找文件了:
bash
find . ...
后面的 grep 接收到的是具体文件列表,所以:
bash
grep -rnE
里的 -r 可以省略。
更简洁:
bash
find . -type f \
\( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.c" -o -name "*.sh" -o -name "Makefile*" \) \
-not -path "*/.*" \
-not -name "comment_by_audit.txt" \
-exec grep -nE "(//|/\*|#).*\s+by\s+" {} + \
| tee comment_by_audit.txt \
| wc -l
另一个注意点:\s 的兼容性
你的正则里用了:
regex
\s
在很多 GNU grep -E 环境中可以工作,但严格来说,POSIX ERE 里更标准的空白写法是:
regex
[[:space:]]
所以更兼容的写法是:
bash
find . -type f \
\( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.c" -o -name "*.sh" -o -name "Makefile*" \) \
-not -path "*/.*" \
-not -name "comment_by_audit.txt" \
-exec grep -nE "(//|/\*|#).*[[:space:]]+by[[:space:]]+" {} + \
| tee comment_by_audit.txt \
| wc -l
这个版本更适合跨 Linux/macOS/Unix 环境使用。
再注意:它只匹配小写 by
当前命令只会匹配:
text
by
不会匹配:
text
By
BY
如果你想忽略大小写,可以加 -i:
bash
grep -niE "(//|/\*|#).*[[:space:]]+by[[:space:]]+"
完整版本:
bash
find . -type f \
\( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.c" -o -name "*.sh" -o -name "Makefile*" \) \
-not -path "*/.*" \
-not -name "comment_by_audit.txt" \
-exec grep -niE "(//|/\*|#).*[[:space:]]+by[[:space:]]+" {} + \
| tee comment_by_audit.txt \
| wc -l
总结一句话
这条命令是在做一次"注释审计":
扫描当前工程中的 C/C++、Shell、Makefile 文件,找出注释里包含类似
by xxx的行,保存到comment_by_audit.txt,并统计总共有多少条匹配。
三、grep "@" comment_by_audit.txt > temp_audit.txt && mv temp_audit.txt comment_by_audit.txt && wc -l comment_by_audit.txt
这条命令的作用可以概括为:
从
comment_by_audit.txt中筛选出包含@的行,覆盖原来的comment_by_audit.txt,然后统计剩余行数。
命令是:
bash
grep "@" comment_by_audit.txt > temp_audit.txt && mv temp_audit.txt comment_by_audit.txt && wc -l comment_by_audit.txt
逐段解释
1. grep "@" comment_by_audit.txt
bash
grep "@" comment_by_audit.txt
表示在文件 comment_by_audit.txt 中查找包含 @ 的行。
例如原文件内容是:
text
./src/a.cpp:10:// modified by alice@example.com
./src/b.cpp:20:// modified by Bob
./src/c.cpp:30:# generated by build@system
执行后会筛出包含 @ 的行:
text
./src/a.cpp:10:// modified by alice@example.com
./src/c.cpp:30:# generated by build@system
这通常是在进一步筛选包含邮箱地址或类似账号标识的审计结果。
2. > temp_audit.txt
bash
> temp_audit.txt
表示把 grep 的输出重定向到临时文件:
text
temp_audit.txt
也就是说,不直接显示到终端,而是写入这个临时文件。
为什么不直接写回原文件?
不要这样写:
bash
grep "@" comment_by_audit.txt > comment_by_audit.txt
因为 shell 会先清空 comment_by_audit.txt,然后 grep 再去读它,结果可能导致原文件内容丢失。
所以这里使用临时文件:
bash
grep "@" comment_by_audit.txt > temp_audit.txt
然后再用 mv 覆盖原文件。
3. &&
bash
&&
表示"前一个命令成功后,才执行下一个命令"。
所以这里的执行逻辑是:
text
如果 grep 成功生成 temp_audit.txt
才执行 mv
如果 mv 成功覆盖 comment_by_audit.txt
才执行 wc -l
4. mv temp_audit.txt comment_by_audit.txt
bash
mv temp_audit.txt comment_by_audit.txt
表示把临时文件重命名为:
text
comment_by_audit.txt
如果原来的 comment_by_audit.txt 已经存在,会被覆盖。
执行后,comment_by_audit.txt 里就只剩下包含 @ 的行。
5. wc -l comment_by_audit.txt
bash
wc -l comment_by_audit.txt
统计文件的行数。
输出类似:
text
25 comment_by_audit.txt
表示筛选后文件中还有 25 行。
整条命令的执行流程
可以理解成:
text
1. 从 comment_by_audit.txt 里找出包含 @ 的行
2. 把这些行写入 temp_audit.txt
3. 如果第 1 步成功,则用 temp_audit.txt 覆盖 comment_by_audit.txt
4. 如果覆盖成功,则统计新的 comment_by_audit.txt 有多少行
举个例子
假设原来的 comment_by_audit.txt 是:
text
./main.cpp:12:// changed by alice@example.com
./util.c:33:// fixed by Bob
./build.sh:8:# generated by ci@company.com
./Makefile:5:# updated by release team
执行:
bash
grep "@" comment_by_audit.txt > temp_audit.txt && mv temp_audit.txt comment_by_audit.txt && wc -l comment_by_audit.txt
筛选后,新的 comment_by_audit.txt 变成:
text
./main.cpp:12:// changed by alice@example.com
./build.sh:8:# generated by ci@company.com
终端输出:
text
2 comment_by_audit.txt
一个重要注意点
这条命令有一个细节:如果 grep "@" comment_by_audit.txt 没有找到任何包含 @ 的行,grep 的退出码通常是 1。
因为用了 &&,所以后面的:
bash
mv temp_audit.txt comment_by_audit.txt
不会执行。
结果是:
temp_audit.txt可能会被创建成空文件;- 原来的
comment_by_audit.txt不会被覆盖; wc -l也不会执行。
也就是说,如果没有匹配项,原文件会保持不变。
如果你希望"没有匹配项时也清空原文件"
可以改成:
bash
grep "@" comment_by_audit.txt > temp_audit.txt; mv temp_audit.txt comment_by_audit.txt; wc -l comment_by_audit.txt
或者更安全一点:
bash
grep "@" comment_by_audit.txt > temp_audit.txt || true
mv temp_audit.txt comment_by_audit.txt
wc -l comment_by_audit.txt
这样即使没有任何包含 @ 的行,comment_by_audit.txt 也会被更新为空文件。
总结一句话
这条命令是在对之前的审计结果做二次过滤:
只保留
comment_by_audit.txt中包含@的匹配行,用过滤结果覆盖原文件,然后输出过滤后的总行数。