前言
find和grep是Linux最基本的搜索命令,find用于搜索文件,grep用于搜索文件内容,熟练掌握其用法可以显著提高搜索效率,本文总结了两者的
- 常规用法
- 配合正则表达式使用
- 两者结合使用
- 配合shell脚本自定义搜索函数
1. find 基本用法
搜索包含res
的文件
find "res"
结果中不仅会包含/res文件夹,也会包含res/strings.xml等文件
参数 -name 搜索仅匹配文件名而非相对路径
例:在当前路径搜索名称为strings.xml
的所有文件
find ./ –name strings.xml
参数 -not 非
例:在当前路径搜索除了strings.xml
文件外的所有文件
find ./ -not -name "strings.xml"
参数 -a 与
例:在当前路径搜索除了strings.xml
文件外的所有xml
文件
find ./ -not -name "strings.xml" -a -name "*.xml"
参数 -o 或
例:在当前路径搜索所有的xml
和java
文件
find . -name "*.xml" -o -name "*.java"
参数 -type 指定搜索的文件类型
例:在当前路径搜索所有文件夹
find . -type d
例:在当前路径搜索所有普通文件
find . -type f
2. grep 基本用法
在文件内搜索文件内容
例:在当前路径下的MainActivity.java
文件中搜索onCreate
方法
grep "onCreate" ./MainActivity.java
参数 -n 输出的结果打印行号
参数 -r 递归搜索
例:在当前目录以及子目录中搜索onCreate
方法
grep -nr "onCreate"
使用-r参数后不需要加文件名
参数 -i 搜索忽略大小写
参数 -I 搜索结果只显示文件名
参数 -w 精确匹配结果
参数 -v 输出所有不匹配的行
3. find + 正则表达式(RE)
-name + RE 的使用
-name 是将文件名去匹配而不是文件的输出结果,以下为使用-name时的RE
符号 | 解释 |
---|---|
* | 代表任意字符(可以没有字符) |
? | 代表任意单个字符 |
[] | 代表括号内的任意字符,[abc]可以匹配a\b\c某个字符 |
[a-z] | 可以匹配a-z的某个字母 |
[A-Z] | 可以匹配A-Z的某个字符 |
[0-9] | 可以匹配0-9的某个数字 |
^ | 用在[]内的前缀表示不匹配[]中的字符 |
[^a-z] | 表示不匹配a-z的某个字符 |
例1:在当前目录中搜素不以a、b、c
开头的所有文件
find ./ –name “[^abc]*”
例2:在当前目录中搜索以大写字母或数字开头的所有文件
find ./ -name “[A-Z0-9]*”
-regex + RE 的使用
-regex 是将文件的输出结果进行匹配而不是文件名,使用-regex时必须使用正规的RE,以下为简单的RE使用
符号 | 解释 |
---|---|
[] | 代表括号内的任意字符,[abc]可以匹配a\b\c某个字符 |
[a-z] | 可以匹配a-z的某个字母 |
[A-Z] | 可以匹配A-Z的某个字符 |
[0-9] | 可以匹配0-9的某个数字 |
. | 表示任意单个字符 |
? | 表示前面的字符出现一次或零次 |
+ | 表示前面的字符至少出现一次 |
* | 表示前面的字符出现零次或多次 |
() | 将字符括起来后面跟量词 |
| | 逻辑或,可以搜索两个条件 |
例1:搜索所有输出结果包含res
的文件(即使文件名不包含res,只要该文件在res文件夹中也都可以被搜索到)
find . –regex “.*res.*”
注意此时用的是.*
而不是*
去匹配任意字符
例:在当前目录搜索所有文件名末尾为res或res_ext的文件
find ./ -regex “.*res|.*res_ext”
4. grep + 正则表达式 (RE)
find中的RE使用均可使用,注意有些符号需要用\
转义,其他常用符号如下
符号 | 解释 |
---|---|
< | 单词的开始 |
> | 单词的结束 |
^ | 行的开始,^ 用在 [ ] 内表示不匹配其中的字符,注意区别 |
$ | 行的结束 |
{n} | 表示前面的字符匹配n次 |
{n,m} | 表示前面的字符匹配n-m次 |
{,m} | 表示前面的字符至多匹配的m次 |
{n,} | 表示前面的字符至少匹配n次 |
egrep
egrep是grep的进化版,改进了许多grep中不方便之处如下,egrep使用RE符号 + , ? , | (或) , {}
时不用转义,如果要用其本身则需要转义,因此推荐使用egrep和RE配合使用
例1:搜索精确匹配Dialer单词的行(形如DialerActivity则不会匹配)
egrep –nr “\
例2:当前目录搜索以private开始的行(private前可能有空格)
egrep -nr "^.*private"
5. find和grep结合使用
面对大量代码时仅使用grep搜索会非常耗时,此时就需要将find和grep结合起来使用以提高搜索效率,先看一个例子
function jgrep()
{
find . -name .repo -prune -o -name .git -prune -o
-name out -prune -o -type f -name "*\.java" -print0 | xargs -0 grep --color -n "$@"
}
这个是Android系统源码build/envsetup.sh中的jgrep函数,用于搜索java文件内容,使用jgrep搜索效率远胜于单单使用grep进行搜索,下面对这个函数进行分析
find
在当前目录搜索
-o
或,并列多个条件
-name .repo –prune
忽略.repo目录(git库相关)
-name .git –prune
忽略.git目录(git库相关)
-name out –prune
忽略out目录(编译生成的目录)
-type f
指定文件类型为普通文件
-name "*.java"
指定匹配的文件名为.java文件
-print0 | xargs -0
忽略搜索中可能出现的错误信息,并将搜索到的文件作为结果向后传递并继续执行
grep --color –n
用grep在之前搜索到的文件中进行内容搜索,输出行号并标识颜色
"$@"
表示在使用jgrep函数时输入的参数,这里即为egrep搜索的内容
管道符号以及xargs的使用
上例中的|
为管道符号,作用是将前一个命令的标准输出作为后一个命令的标准输入。
如果仅使用|
,那么前面的结果会作为输入直接传递到后面的命令中,而使用xargs
,就可以使前面的结果作为参数传递到后面的命令中,而这个特性对于find和grep而言十分重要。
例1:在当前目录中搜索所有AndroidManifest.xml
文件并在其中搜索DialtactsActivity
find . –name AndroidManifest.xml | xargs grep –n –color “DialtactsActivity”
该例是xargs最基本的用法,如果将xargs去掉,那么grep搜索的内容是find输出的结果内容而非结果文件中的内容
例2:在当前目录搜索所有的values-zh-rCN
文件目录并在其中搜索所有的strings.xml
文件,然后在搜索到的strings.xml
文件中搜索“通话”字符串
find . –type d –name “values-zh-rCN” |
xargs –i find {} –name “strings.xml” | xargs grep –n –-color 通话
该例中xargs后使用了-i
参数,该参数的作用是可以将后面命令中的 {}
符号视为前面find搜索的结果文件。本例中连续使用了两次xargs
进行结果的传递
例3:在当前目录中的所有mk文件中搜索ro.build.type
find . –type f –name “*.mk” –print0 | xargs -0 grep –n –color “ro.build.type”
本例中和之前提到的jgrep函数都使用了–print0 | xargs -0
进行结果传递而非单纯使用xargs
,这样做的好处是如果find搜索会忽略可能出现的错误,使最终输出的结果更清晰,因此在使用xargs时建议按照–print0 | xargs -0方式
书写命令
6. 编写搜索函数
find和grep的结合使用的命令较长,为方便起见,我们需要使用函数配合使用(形如上述的jgrep函数)。
下例中说明了如何编写搜索函数以及如何在Linux命令行中使用函数,这里可能需要用到简单的shell脚本命令:
- 编写简单的搜索函数
首先创建test.sh
脚本文件并编写下述代码
function mkgrep()
{
find . -name .repo -prune -o -name .git -prune -o -name out -prune
-o -type f -name "*\.mk" -print0 | xargs -0 grep --color -n "$@"
}
函数的使用
#加载函数脚本,只需加载一次
source test.sh
#在当前目录及子目录下的所有.mk文件中搜索Dialer
mkgrep Dialer
- 编写复杂的搜索函数
首先创建test.sh
脚本文件并编写下述代码
#文件内容搜索函数sep
#参数1 必选 搜索内容
#参数2 可选 前缀-t 内容所在的文件类型(即文件后缀名,如java),缺省为所有文件类型
#参数3 可选 前缀-f 指定搜索的目录 缺省为当前目录及所有子目录
#用例 sep "ITelecomService.Stub" -t java aidl -f packages/ frameworks/
#用例解析 在packages、frameworks目录中的所有java、aidl文件中搜索"ITelecomService.Stub"
function sep()
{
#文件内容=第一个参数
se_content=$1
#文件类型和搜索目录暂时=空
se_fileType=""
se_folder=""
#shift的作用是将第一个参数移除,即当前函数输入的第二个参数变成第一个参数,第三个变成第二个,以此类推
shift
#判断当前第一个参数是否为-t,即文件类型是否指定,如果指定就取出文件类型放入se_fileType变量中
if [ "$1" = "-t" ];then
#如果是-t就将这个参数移除
shift
while ( [ "$1" != "-f" ] && [ -n "$1" ] )
do
se_fileType="$se_fileType $1"
shift
done
fi
#判断当前第一个参数是否为-f,即搜索目录是否指定,如果指定就取出搜索目录放入se_folder变量中
if [ "$1" = "-f" ];then
#如果是-f就将这个参数移除
shift
while [ -n "$1" ]
do
se_folder="$se_folder $1"
shift
done
fi
#判断文件类型是否为空,不为空则建立循环分别搜索指定的文件类型
if [ -z $se_fileType ];then
#这里如果搜索目录为空find会自动搜索当前目录及子目录,因此不用再做判断
#这里用到了egrep而不是grep,方便输入搜索内容时直接使用正则表达式
find $se_folder -type f -print0 | xargs -0 egrep -n --color "$se_content"
else
for ft in $se_fileType
do
find $se_folder -type f -name "*.$ft" -print0 | xargs -0 egrep -n --color "$se_content"
done
fi
}
函数的使用
#加载函数脚本,只需加载一次
source sep.sh
#在当前路径下的packages、frameworks目录中的所有.java和.aidl文件中搜索ITelecomService.Stub
sep "ITelecomService.Stub" -t java aidl -f packages/ frameworks/