简介
awk 是一种编程语言,用于在 linux/unix 下对文本和数据进行处理。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是 linux/unix 下的一个强大编程工具。它在命令行中使用,但更多是作为脚本来使用。awk 有很多内建的功能,比如数组、函数等,这是它和 C 语言的相同之处,灵活性是 awk 最大的优势。 尽管操作可能会很复杂,但命令的语法始终是:
awk ‘{pattern + action}’ 或者 awk ‘pattern {action}’
grep 、sed、awk 被称为 linux 中的”三剑客”。
- grep 更适合单纯的查找或匹配文本
- sed 更适合编辑匹配到的文本
- awk 更适合格式化文本,对文本进行较复杂格式处理
awk 命令
awk 命令格式和选项
语法格式
awk [options] 'script' var=value file(s)
awk [options] -f scriptfile var=value file(s)
常用命令选项
-
-F fs fs 指定输入分隔符,fs 可以时字符串或正则表达式
-
-v var=value 赋值一个用户定义变量,将外部变量传递给 awk
-
-f scriptfile 从脚本文件中读取 awk 命令
awk 脚本
awk 脚本是由模式和操作组成的。
一个 awk 脚本通常由 BEGIN, 通用语句块,END 语句块组成,三部分都是可选的。 脚本通常是被单引号或双引号包住。
awk 'BEGIN{ i=0 } { i++ } END{ print i }' filename
awk "BEGIN{ i=0 } { i++ } END{ print i }" filename
模式与操作
模式
模式可以是以下任意一种:
-
正则表达式:使用通配符的扩展集
-
关系表达式:使用运算符进行操作,可以是字符串或数字的比较测试
-
模式匹配表达式:用运算符~(匹配)和~!不匹配
-
BEGIN 语句块, pattern 语句块, END 语句块
操作
操作由一个或多个命令、函数、表达式组成,之间由换行符或分号隔开,并位于大刮号内,主要部分是:变量或数组赋值、输出命令、内置函数、控制流语句。
awk 'BEGIN{ commands } pattern{ commands } END{ commands }' file
awk 执行过程分析
- 第一步: 执行 BEGIN { commands } pattern 语句块中的语句
BEGIN 语句块:在 awk 开始从输入输出流中读取行之前执行,在 BEGIN 语句块中执行如变量初始化,打印输出表头等操作。
- 第二步:从文件或标准输入中读取一行,然后执行 pattern{ commands }语句块。它逐行扫描文件,从第一行到最后一行重复这个过程,直到全部文件都被读取完毕。
pattern 语句块:pattern 语句块中的通用命令是最重要的部分,它也是可选的。如果没有提供 pattern 语句块,则默认执行{ print },即打印每一个读取到的行。{ }类似一个循环体,会对文件中的每一行进行迭代,通常将变量初始化语句放在 BEGIN 语句块中,将打印结果等语句放在 END 语句块中。
- 第三步:当读至输入流末尾时,执行 END { command }语句块
END 语句块:在 awk 从输入流中读取完所有的行之后即被执行,比如打印所有行的分析结果这类信息汇总都是在 END 语句块中完成,它也是一个可选语句块。
AWK 内置变量
-
$n : 当前记录的第 n 个字段,比如 n 为 1 表示第一个字段,n 为 2 表示第二个字段。
-
$0 : 这个变量包含执行过程中当前行的文本内容。
-
ARGC : 命令行参数的数目。
-
ARGIND : 命令行中当前文件的位置(从 0 开始算)。
-
ARGV : 包含命令行参数的数组。
-
CONVFMT : 数字转换格式(默认值为%.6g)。
-
ENVIRON : 环境变量关联数组。
-
ERRNO : 最后一个系统错误的描述。
-
FIELDWIDTHS : 字段宽度列表(用空格键分隔)。
-
FILENAME : 当前输入文件的名。
-
NR : 表示记录数,在执行过程中对应于当前的行号
-
FNR : 同 NR :,但相对于当前文件。
-
FS : 字段分隔符(默认是任何空格)。
-
IGNORECASE : 如果为真,则进行忽略大小写的匹配。
-
NF : 表示字段数,在执行过程中对应于当前的字段数。 print $NF 答应一行中最后一个字段
-
OFMT : 数字的输出格式(默认值是%.6g)。
-
OFS : 输出字段分隔符(默认值是一个空格)。
-
ORS : 输出记录分隔符(默认值是一个换行符)。
-
RS : 记录分隔符(默认是一个换行符)。
-
RSTART : 由 match 函数所匹配的字符串的第一个位置。
-
RLENGTH : 由 match 函数所匹配的字符串的长度。
-
SUBSEP : 数组下标分隔符(默认值是 34)。
将外部变量值传递给 awk
- 借助 -v 选项,可以将来自外部值(非 stdin)传递给 awk
VAR=10000
echo | awk -v VARIABLE=$VAR '{ print VARIABLE }'
- 定义内部变量接收外部变量
var1="aaa"
var2="bbb"
echo | awk '{ print v1,v2 }' v1=$var1 v2=$var2
- 当输入来自文件时
awk '{ print v1,v2 }' v1=$var1 v2=$var2 filename
awk 运算
- 算术运算:(+,-,*,/,&,!,……,++,–)
所有用作算术运算符进行操作时,操作数自动转为数值,所有非数值都变为 0
-
赋值运算:(=, +=, -=,*=,/=,%=,……=,**=)
-
逻辑运算符:(||, &&)
-
关系运算符:(<, <=, >,>=,!=, ==)
-
正则运算符:(~,~!)(匹配正则表达式,与不匹配正则表达式)
awk 'BEGIN{a="100testa";if(a ~ /^100*/){print "ok";}}'
ok
awk 高级输入输出
读取下一条记录:next 语句
awk 中 next 语句使用:在循环逐行匹配,如果遇到 next,就会跳过当前行,直接忽略下面语句。而进行下一行匹配。net 语句一般用于多行合并:
awk ‘NR%2==1{next}{print NR,$0;}’ text.txt
说明: 当记录行号除以 2 余 1,就跳过当前行。下面的 print NR,$0 也不会执行。下一行开始,程序有开始判断 NR%2 值。这个时候记录行号是:2 ,就会执行下面语句块:print NR,$0
读取一行记录:getline 语句
- 用法:输出重定向需用到 getline 函数。getline 从标准输入、管道或者当前正在处理的文件之外的其他输入文件获得输入。它负责从输入获得下一行的内容,并给 NF,NR 和 FNR 等内建变量赋值。如果得到一条记录,getline 函数返回 1,如果到达文件的末尾就返回 0,如果出现错误,例如打开文件失败,就返回-1。
- 语法格式:getline var 变量 var 包含了特定行的内容
- 用法说明:
-
当其左右无重定向符时|, <时:getline 作用于当前文件,读入当前文件的第一行给其后跟的变量 var 或$0(无变量),应该注意到,由于 awk 在处理 getline 之前已经读入了一行,所以 getline 得到的返回结果是隔行的。
-
当其左右有重定向符时|, <时:getline 则作用于定向输入文件,由于该文件是刚打开,并没有被 awk 读入一行,只是 getline 读入,那么 getline 返回的是该文件的第一行,而不是隔行。
循环结构
for 循环
for(变量 in 数组)
{语句}
for(变量;条件;表达式)
{语句}
while 循环
while(表达式)
{语句}
do…while 循环
do
{语句} while(条件)
其他相关语句
-
break:退出程序循环
-
continue: 进入下一次循环
-
next:读取下一个输入行
-
exit:退出主输入循环,进入 END,若没有 END 或 END 中有 exit 语句,则退出脚本。
数组
在 awk 中数组叫做关联数组(associative arrays)。awk 中的数组不必提前声明,也不必声明大小。数组元素用 0 或空字符串来初始化,这根据上下文而定。
awk 'BEGIN{
Array[1]="sun"
Array[2]="kai"
Array["first"]="www"
Array["last"]="name"
Array["birth"]="1987"
info = "it is a test";
lens = split(info,tA," ");
for(item in tA)
{print tA[item];}
for(i=1;i<=lens;i++)
{print tA[i];}
print length(tA[lens]);
} {
print "item in array";
for(item in Array) {print Array[item]};
print "print in i++";
for(i=1;i<=length(Array);i++) {print Array[i]};
}'
- 输出数组内容
- 有序输出 for…in
因为数组时关联数组,默认是无序的
- 无序输出 for(i=1;i<l=ens;i++)
数组下标从 1 开始
- 判断键值是否存在
#错误的判断方法,awk数组是关联数组,只要通过数组引用它的KEY,就会自动创建。
awk 'BEGIN{
tB["a"]="a1";
tB["b"]="b1";
if(tB["c"]!="1"){ #tB["c"]没有定义,但是循环的时候会输出
print "no found";
};
for(k in tB){
print k,tB[k];
}}'
#正确的判定方法:使用 if ( key in array) 判断数组中是否包含 键值
awk 'BEGIN{
tB["a"]="a1";
tB["b"]="b1";
if( "c" in tB){
print "ok";
};
for(k in tB){
print k,tB[k];
}}'
- 删除键值
awk 'BEGIN{
tB["a"]="a1";
tB["b"]="b1";
delete tB["a"];
for(k in tB){
print k,tB[k];
}}'
内置函数
算数函数
atan2( y, x )
返回 y/x 的反正切。
$ awk 'BEGIN {
PI = 3.14159265
x = -10
y = 10
result = atan2 (y,x) * 180 / PI;
printf "The arc tangent for (x=%f, y=%f) is %f degrees\n", x, y, result
}'
输出结果为:
The arc tangent for (x=-10.000000, y=10.000000) is 135.000000 degrees
cos( x )
返回 x 的余弦;x 是弧度。
$ awk 'BEGIN {
PI = 3.14159265
param = 60
result = cos(param * PI / 180.0);
printf "The cosine of %f degrees is %f.\n", param, result
}'
输出结果为:
The cosine of 60.000000 degrees is 0.500000.
sin( x )
返回 x 的正弦;x 是弧度。
$ awk 'BEGIN {
PI = 3.14159265
param = 30.0
result = sin(param * PI /180)
printf "The sine of %f degrees is %f.\n", param, result
}'
输出结果为:
The sine of 30.000000 degrees is 0.500000.
exp( x )
返回 x 幂函数。
$ awk 'BEGIN {
param = 5
result = exp(param);
printf "The exponential value of %f is %f.\n", param, result
}'
输出结果为:
The exponential value of 5.000000 is 148.413159.
log( x )
返回 x 的自然对数。
$ awk 'BEGIN {
param = 5.5
result = log (param)
printf "log(%f) = %f\n", param, result
}'
输出结果为:
log(5.500000) = 1.704748
sqrt( x )
返回 x 平方根。
$ awk 'BEGIN {
param = 1024.0
result = sqrt(param)
printf "sqrt(%f) = %f\n", param, result
}'
输出结果为:
sqrt(1024.000000) = 32.000000
int( x )
返回 x 的截断至整数的值。
$ awk 'BEGIN {
param = 5.12345
result = int(param)
print "Truncated value =", result
}'
输出结果为:
Truncated value = 5
rand( )
返回任意数字 n,其中 0 <= n < 1。
$ awk 'BEGIN {
print "Random num1 =" , rand()
print "Random num2 =" , rand()
print "Random num3 =" , rand()
}'
输出结果为:
Random num1 = 0.237788
Random num2 = 0.291066
Random num3 = 0.845814
srand( [Expr] )
将 rand 函数的种子值设置为 Expr 参数的值,或如果省略 Expr 参数则使用某天的时间。返回先前的种子值。
$ awk 'BEGIN {
param = 10
printf "srand() = %d\n", srand()
printf "srand(%d) = %d\n", param, srand(param)
}'
输出结果为:
srand() = 1
srand(10) = 1417959587
字符串函数
gsub( Ere, Repl, [ In ] )
gsub 是全局替换( global substitution )的缩写。除了正则表达式所有具体值被替代这点,它和 sub 函数完全一样地执行。
$ awk 'BEGIN{info="this is a test2012test!";gsub(/[0-9]+/,"||",info);print info}'
输出结果为:
this is a test test!
sub(regex,sub,string)
sub 函数执行一次子串替换。它将第一次出现的子串用 regex 替换。第三个参数是可选的,默认为 $0。
$ awk 'BEGIN {
str = "Hello, World"
print "String before replacement = " str
sub("World", "Jerry", str)
print "String after replacement = " str
}'
输出结果为:
String before replacement = Hello, World
String after replacement = Hello, Jerry
substr(str, start, l)
substr 函数返回 str 字符串中从第 start 个字符开始长度为 l 的子串。如果没有指定 l 的值,返回 str 从第 start 个字符开始的后缀子串。
$ awk 'BEGIN {
str = "Hello, World !!!"
subs = substr(str, 1, 5)
print "Substring = " subs
}'
输出结果为:
Substring = Hello
index( String1, String2 )
在由 String1 参数指定的字符串(其中有出现 String2 指定的参数)中,返回位置,从 1 开始编号。如果 String2 参数不在 String1 参数中出现,则返回 0(零)。
$ awk 'BEGIN {
str = "One Two Three"
subs = "Two"
ret = index(str, subs)
printf "Substring \"%s\" found at %d location.\n", subs, ret
}'
输出结果为:
Substring “Two” found at 5 location.
length [(String)]
返回 String 参数指定的字符串的长度(字符形式)。如果未给出 String 参数,则返回整个记录的长度($0 记录变量)。
$ awk 'BEGIN {
str = "Hello, World !!!"
print "Length = ", length(str)
}'
输出结果为:
Substring “Two” found at 5 location.
blength [(String)]
返回 String 参数指定的字符串的长度(以字节为单位)。如果未给出 String 参数,则返回整个记录的长度($0 记录变量)。
substr( String, M, [ N ] )
返回具有 N 参数指定的字符数量子串。子串从 String 参数指定的字符串取得,其字符以 M 参数指定的位置开始。M 参数指定为将 String 参数中的第一个字符作为编号 1。如果未指定 N 参数,则子串的长度将是 M 参数指定的位置到 String 参数的末尾 的长度。
$ awk 'BEGIN {
str = "Hello, World !!!"
subs = substr(str, 1, 5)
print "Substring = " subs
}'
输出结果为:
Substring = Hello
match( String, Ere )
在 String 参数指定的字符串(Ere 参数指定的扩展正则表达式出现在其中)中返回位置(字符形式),从 1 开始编号,或如果 Ere 参数不出现,则返回 0(零)。RSTART 特殊变量设置为返回值。RLENGTH 特殊变量设置为匹配的字符串的长度,或如果未找到任何匹配,则设置为 -1(负一)。
$ awk 'BEGIN {
str = "One Two Three"
subs = "Two"
ret = match(str, subs)
printf "Substring \"%s\" found at %d location.\n", subs, ret
}'
输出结果为:
Substring “Two” found at 5 location.
split( String, A, [Ere] )
将 String 参数指定的参数分割为数组元素 A[1], A[2], . . ., A[n],并返回 n 变量的值。此分隔可以通过 Ere 参数指定的扩展正则表达式进行,或用当前字段分隔符(FS 特殊变量)来进行(如果没有给出 Ere 参数)。除非上下文指明特定的元素还应具有一个数字值,否则 A 数组中的元素用字符串值来创建。
$ awk 'BEGIN {
str = "One,Two,Three,Four"
split(str, arr, ",")
print "Array contains following values"
for (i in arr) {
print arr[i]
}
}'
输出结果为:
Array contains following values
One
Two
Three
Four
tolower( String )
返回 String 参数指定的字符串,字符串中每个大写字符将更改为小写。大写和小写的映射由当前语言环境的 LC_CTYPE 范畴定义。
$ awk 'BEGIN {
str = "HELLO, WORLD !!!"
print "Lowercase string = " tolower(str)
}'
输出结果为:
Lowercase string = hello, world !!!
toupper( String )
返回 String 参数指定的字符串,字符串中每个小写字符将更改为大写。大写和小写的映射由当前语言环境的 LC_CTYPE 范畴定义。
$ awk 'BEGIN {
str = "hello, world !!!"
print "Uppercase string = " toupper(str)
}'
输出结果为:
Uppercase string = HELLO, WORLD !!!
strtonum(str)
strtonum 将字符串 str 转换为数值。 如果字符串以 0 开始,则将其当作十进制数;如果字符串以 0x 或 0X 开始,则将其当作十六进制数;否则,将其当作浮点数。
$ awk 'BEGIN {
print "Decimal num = " strtonum("123")
print "Octal num = " strtonum("0123")
print "Hexadecimal num = " strtonum("0x123")
}'
输出结果为:
Decimal num = 123
Octal num = 83
Hexadecimal num = 291
sprintf(Format, Expr, Expr, . . . )
根据 Format 参数指定的 printf 子例程格式字符串来格式化 Expr 参数指定的表达式并返回最后生成的字符串。
$ awk 'BEGIN {
str = sprintf("%s", "Hello, World !!!")
print str
}'
输出结果为:
Hello, World !!!
格式符 | 说明 |
---|---|
%d | 十进制有符号整数 |
%u | 十进制无符号整数 |
%f | 浮点数 |
%s | 字符串 |
%c | 单个字符 |
%p | 指针的值 |
%e | 指数形式的浮点数 |
%x | %X 无符号以十六进制表示的整数 |
%o | 无符号以八进制表示的整数 |
%g | 自动选择合适的表示法 |
时间函数
mktime( YYYY MM DD HH MM SS[ DST])
生成时间格式
$ awk 'BEGIN {
print "Number of seconds since the Epoch = " mktime("2014 12 14 30 20 10")
}'
输出结果为:
Number of seconds since the Epoch = 1418604610
strftime([format [, timestamp]])
格式化时间输出,将时间戳转为时间字符串. 具体格式,见下表.
$ awk 'BEGIN {
print strftime("Time = %m/%d/%Y %H:%M:%S", systime())
}'
输出结果为:
Time = 12/14/2014 22:08:42
序号 | 描述 |
---|---|
%a | 星期缩写(Mon-Sun)。 |
%A | 星期全称(Monday-Sunday)。 |
%b | 月份缩写(Jan)。 |
%B | 月份全称(January)。 |
%c | 本地日期与时间。 |
%C | 年份中的世纪部分,其值为年份整除 100。 |
%d | 十进制日期(01-31) |
%D | 等价于 %m/%d/%y. |
%e | 日期,如果只有一位数字则用空格补齐 |
%F | 等价于 %Y-%m-%d,这也是 ISO 8601 标准日期格式。 |
%g | ISO8610 标准周所在的年份模除 100(00-99)。比如,1993 年 1 月 1 日属于 1992 年的第 53 周。所以,虽然它是 1993 年第 1 天,但是其 ISO8601 标准周所在年份却是 1992。同样,尽管 1973 年 12 月 31 日属于 1973 年但是它却属于 1994 年的第一周。所以 1973 年 12 月 31 日的 ISO8610 标准周所在的年是 1974 而不是 1973。 |
%G | ISO 标准周所在年份的全称。 |
%h | 等价于 %b. |
%H | 用十进制表示的 24 小时格式的小时(00-23) |
%I | 用十进制表示的 12 小时格式的小时(00-12) |
%j | 一年中的第几天(001-366) |
%m | 月份(01-12) |
%M | 分钟数(00-59) |
%n | 换行符 (ASCII LF) |
%p | 十二进制表示法(AM/PM) |
%r | 十二进制表示法的时间(等价于 %I:%M:%S %p)。 |
%R | 等价于 %H:%M。 |
%S | 时间的秒数值(00-60) |
%t | 制表符 (tab) |
%T | 等价于 %H:%M:%S。 |
%u | 以数字表示的星期(1-7),1 表示星期一。 |
%U | 一年中的第几个星期(第一个星期天作为第一周的开始),00-53 |
%V | 一年中的第几个星期(第一个星期一作为第一周的开始),01-53。 |
%w | 以数字表示的星期(0-6),0 表示星期日 。 |
%W | 十进制表示的一年中的第几个星期(第一个星期一作为第一周的开始),00-53。 |
%x | 本地日期表示 |
%X | 本地时间表示 |
%y | 年份模除 100。 |
%Y | 十进制表示的完整年份。 |
%z | 时区,表示格式为+HHMM(例如,格式要求生成的 RFC 822 或者 RFC 1036 时间头) |
%Z | 时区名称或缩写,如果时区待定则无输出。 |
systime()
得到时间戳,返回从 1970 年 1 月 1 日开始到当前时间(不计闰年)的整秒数
awk 'BEGIN{now=systime();print now}'
输出结果为:
1343210982
位操作函数
and
compl
按位求补。
lshift
左移位操作
rshift
右移位操作
or
按位或操作
xor
按位异或操作
其他函数
close(expr)
delete
exit
flush
getline
next
nextfile
return
system
执行特定的命令然后返回其退出状态。返回值为 0 表示命令执行成功;非 0 表示命令执行失败
$ awk 'BEGIN { ret = system("date"); print "Return value = " ret }'
输出结果为:
Sun Dec 21 23:16:07 IST 2014
Return value = 0
举例子
输出整行
awk '{print $0 }' col_print.txt
输出最后一列
cat col_print.txt | awk '{print $NF }'
输出倒数第二列
cat col_print.txt | awk '{print $(NF-1)}'
输出共有多少列
cat col_print.txt | awk '{print NF}'
输出多列
cat col_print.txt | awk '{print $2,$3}'
我的脚本
#!/bin/bash
echo "用户修改区-开始"
#要替换的源代码所在的根目录,该脚本文件与根目录处于同级文件夹
ROOTFOLDER="MyCode" # KYSecurityDefenseDemo
echo "当前工程名:"$ROOTFOLDER
BUILD_VERSION="3.8.1"
echo "当前版本:"$BUILD_VERSION
#要排除的文件夹,例如demo中用到的第三方库AFNetworking,pods的第三方库等
EXCLUDE_DIR=" --exclude-dir=Pods --exclude-dir=buildAppstore --exclude-dir=Carthage --exclude-dir=Images.xcassets --exclude-dir=Assets.xcassets --exclude-dir=Certificates --exclude-dir=fastlane --exclude-dir=fastlanelog --exclude-dir=${ROOTFOLDER}Tests --exclude-dir=${ROOTFOLDER}Tests"
echo "要排除的文件夹 "${EXCLUDE_DIR}
ROOT_NEW_DIR=$ROOTFOLDER"_"$BUILD_VERSION
if [ ! -d $ROOT_NEW_DIR ]; then
mkdir -p -m 755 $ROOT_NEW_DIR
echo "创建工程目录 "$ROOT_NEW_DIR" 成功"
fi
cp -Rf ${ROOTFOLDER}/* ${ROOT_NEW_DIR}/
ROOTFOLDER=$ROOTFOLDER"_"$BUILD_VERSION
echo "用户修改区-结束"
#自定义的保留关键字,相当与白名单,添加到该文件中,一行一个,加入该文件的关键字将不被混淆;如工程中自定义的文件夹名称
RESCUSTOM="resCustom.txt"
#保留关键字文件不可删除
RESERVEDKEYWORDS="./reskeys.txt"
#最终的保留关键字=保留关键字+文件名
RESKEYSALL="./reskeysall.txt"
#提取的所有关键字
SOURCECODEKEYWORDS="./srckeys.txt"
#过滤后,最终要替换的关键字,混淆结束后,不删除,用于bug分析
REPLACEKEYWORDS="./replacekeys.txt"
#过滤后,最终要替换的文件名,混淆结束后,不删除,用于bug分析
REPLACEFILES="./replacefiles.txt"
#删除已经存在的临时文件
rm -f $SOURCECODEKEYWORDS
rm -f $REPLACEKEYWORDS
rm -f $REPLACEFILES
rm -f $RESKEYSALL
rm -f temp.res
#提取文件名列表
echo '' > f.list
find $ROOTFOLDER -type f | sed "/\/\./d" >f.list
#根据要排除的文件目录,将文件列表分离
#Exclude=$(echo $EXCLUDE_DIR | sed "s/--exclude-dir\=//g" |sed "s/ $//g" | sed "s/[*.]//g" | sed "s/ /\\\|/g")
Exclude=$(echo $EXCLUDE_DIR | sed "s/--exclude-dir\=//g" |sed "s/ $//g" | sed "s/ /\\\|/g")
#保留文件列表
if [ $Exclude ]; then
rm -f f_res.list
cat f.list | grep "$Exclude" >f_res.list
fi
#混淆文件列表
rm -f f_rep.list
cat f.list | grep -v "$Exclude" >f_rep.list
#rm -f f.list
echo "[step1] - 提取文件名"
rm -f filter_file.txt
AssignSrcKeys="my.txt"
if [ $AssignSrcKeys ]; then
echo "[step2] - 指定关键字"
cat `echo $AssignSrcKeys` | sed "/^$/d"| sort | uniq >$SOURCECODEKEYWORDS
else
echo "[step2] - 提取要过滤的函数关键字"
#提取文件名
cat f_rep.list | awk -F/ '{print $NF;}'| awk -F. '{print $1;}' | sed "/^$/d" | sort | uniq >filter_file.txt
#从源代码目录中提取要过滤的函数关键字
rm -f filter_fun.txt
grep -h -r -I "^[-+]" $FIND_DIR $EXCLUDE_DIR $INCLUDE_DIR --include '*.[mh]' |sed "s/[+-]//g"|sed "s/[();,: *\^\/\{]/ /g"|sed "s/[ ]*</</"|awk '{split($0,b," ");print b[2];}'| sort|uniq |sed "/^$/d"|sed "/^init/d" >filter_fun.txt
echo "[step3] - 提取要过滤的属性关键字"
#从源代码目录中提取要过滤的属性关键字
rm -f filter_property.txt
grep -r -h -I ^@property $FIND_DIR $EXCLUDE_DIR $INCLUDE_DIR --include '*.[mh]' | sed "s/(.*)/ /g" | sed "s/<.*>//g" |sed "s/[,*;]/ /g" | sed "s/IBOutlet/ /g" |awk '{split($0,s," ");print s[3];}'|sed "/^$/d" | sort |uniq >filter_property.txt
echo "[step4] - 提取要过滤的类关键字"
#从源代码目录中提取要过滤的类关键字
rm -f filter_class.txt
grep -h -r -I "^@interface" $FIND_DIR $EXCLUDE_DIR $INCLUDE_DIR --include '*.[mh]' | sed "s/[:(]/ /" |awk '{split($0,s," ");print s[2];}'|sort|uniq >filter_class.txt
echo "[step5] - 提取要过滤的协议关键字"
#从源代码目录中提取要过滤的协议关键字
grep -h -r -I "^@protocol" $FIND_DIR $EXCLUDE_DIR $INCLUDE_DIR --include '*.[mh]'| sed "s/[\<,;].*$//g"|awk '{print $2;}' | sort | uniq >>filter_class.txt
echo "[step6] - 合并要过滤的关键字"
#合并要过滤的关键字,并重新排序过滤
rm -f $SOURCECODEKEYWORDS
cat filter_fun.txt filter_property.txt filter_class.txt filter_file.txt |sed "/^$/d" | sort | uniq >$SOURCECODEKEYWORDS
rm -f filter_fun.txt
rm -f filter_class.txt
rm -f filter_file.txt
fi
echo "[step7] - 自动获取保留字,工程名等"
#自动获取保留字,工程名等
rm -f temp.res
cat `cat f_rep.list | grep project.pbxproj` | grep -w productName | sed "s/;//g"|awk '{print $NF;}'>temp.res
#提取要保留的文件名
cat f_res.list | awk -F/ '{print $NF;}'| awk -F. '{print $1;}' | sed "/^$/d" | sort | uniq >>temp.res
rm -f f_res.list
#合并自定义保留字
#判断自定义保留字文件是否存在,不存在即创建一个空的
if [ ! -f "$RESCUSTOM" ]; then
touch "$RESCUSTOM"
fi
cat $RESERVEDKEYWORDS $RESCUSTOM temp.res | sort |uniq >$RESKEYSALL
#rm -f temp.res
echo "[step8] - 过滤保留字,将需要混淆的关键字加密后写入文件"
#过滤保留字,将需要混淆的关键字加密后写入文件
rm -f $REPLACEKEYWORDS
echo "REPLACEKEYWORDS"$REPLACEKEYWORDS
cat $SOURCECODEKEYWORDS |
while read line
do
if grep $line $RESKEYSALL
then
echo filter1: $line
else
#使用md5对关键字进行加密
md5 -r -s $line | sed s/\"//g >> $REPLACEKEYWORDS
fi
done
rm -f $SOURCECODEKEYWORDS
echo "[step9] - 开始混淆,替换源代码中的关键字为加密后的"
#开始混淆,替换源代码中的关键字为加密后的,防止开头为数字的情况
cat $REPLACEKEYWORDS |
while read line
do
var1=$(echo "$line"|awk '{print "z"$1"m"}')
var2=$(echo "$line"|awk '{print $2}')
underline1=$(echo "$line"|awk '{print "_z"$1"m"}')
underline2=$(echo "$line"|awk '{print "_"$2}')
rm -f rep.tmp
assignDir="./ZAInsurance_3.8.1/ZAInsurance/ZAIFoundation/ZAIManagers/SignInWithAppleID/ZAISignInWithAppleIDManager.m"
# 查找带关键字的那一行代码,输出到rep.tmp
if grep -r -n -I -w "[_]\{0,1\}$var2" $ROOTFOLDER $EXCLUDE_DIR $INCLUDE_DIR --include="*.[mhc]" --include="*.mm" --include="*.pch" --include="*.storyboard" --include="*.xib" --include="*.nib" --include="contents" --include="*.pbxproj" >rep.tmp
then
cat rep.tmp |
while read -r l
do
#获取文件路径
v1=$(echo "$l"|cut -d: -f 1 )
#获取行号
v2=$(echo "$l"|cut -d: -f 2 )
#获取指定行数据
v3=$(sed -n "$v2"p "$v1")
##sed自带文件文本替换功能,不符合我们的期望,故放弃使用;有无适合的脚本命令,还希望脚本高手予以指点~
#sed -i '' ''"$v2"'s/'"$var2"'/'"$var1"'/g' $v1
#特殊字符转义替换,echo中 输出的变量 一定要加双引号!!!
v4=$(echo "$v3" | awk '{gsub(/"/, "\\\"", $0);gsub(/</, "\\\<", $0);gsub(/>/, "\\\>", $0);gsub(/\*/, "\\\*", $0);gsub(/\//, "\\\/", $0);gsub(/\[/, "\\\[", $0);gsub(/\]/, "\\\]", $0);gsub(/\{/, "\\\{", $0);gsub(/\}/, "\\\}", $0);gsub(/\&/, "\\\\\&", $0); print $0;}')
#判断是否包含双引号
quoteRes=$(echo $v4 | grep "\"")
if [[ "$quoteRes" != "" ]]; then
#包含引号,引号中的不处理
idx=0
newV3=$(echo "$v3"|sed 's/\\n/|line|/'|sed 's/ /|nbsp|/')
echo "$newV3" | awk -F '\"' '{for(i=1;i<=NF;i++) print $i;}' |
while read line
do
let idx+=1
if [ $idx -gt 1 ]; then
newStr=$newStr"\\\""
fi
#echo -E "line:$idx$line"
if [ $(($idx%2)) -eq 1 ]; then
newStr=$newStr$(./KYReplacewords.run "$line" "$var2" "$var1")
newStr=$(./KYReplacewords.run "$newStr" "$underline2" "$underline1")
else
newStr=$newStr$line
fi
#整行替换
sed -i '' "$v2"'s/.*/'"$newStr"'/g' "$v1"
sed -i '' "$v2"'s/|nbsp|/ /g' "$v1"
sed -i '' "$v2"'s/|line|/\\n/g' "$v1"
done
else
#单词替换
var3=$(./KYReplacewords.run "$v4" "$var2" "$var1")
var3=$(./KYReplacewords.run "$var3" "$underline2" "$underline1")
#整行替换
sed -i '' "$v2"'s/.*/'"$var3"'/g' "$v1"
fi
sed -i '' "$v2"'s/|_|/_/g' "$v1"
echo "step2:$l"
done
else
echo "step2:do not find:$var2"
fi
done
rm -f tmp.txt
echo "[step10] - 过滤保留字,用于属性设置函数混淆"
#过滤保留字,用于属性设置函数混淆,将需要混淆的关键字加密后写入文件
rm -f repProperty.txt
cat filter_property.txt |
while read line
do
if grep $line $RESKEYSALL
then
echo filter1: $line
else
md5 -r -s $line | sed s/\"//g >> repProperty.txt
fi
done
rm -f filter_property.txt
echo "[step11] - 开始混淆,替换属性前带下划线的地方"
#开始混淆,替换属性前带下划线的地方
cat repProperty.txt |
while read line
do
ar=(`echo "$line"|cut -f 1-2 -d " "`)
lastFind=`echo _${ar[1]}`
lastRep=`echo _z${ar[0]}m`
rm -f rep.tmp
if grep -r -n -I -w "$lastFind" $ROOTFOLDER $EXCLUDE_DIR $INCLUDE_DIR --include="*.[mhc]" --include="*.mm" --include="*.storyboard" --include="*.xib" >rep.tmp
then
cat rep.tmp |
while read l
do
v1=$(echo "$l"|cut -d: -f 1 )
v2=$(echo "$l"|cut -d: -f 2 )
sed -i '' ''"$v2"'s/'"$lastFind"'/'"$lastRep"'/g' $v1
echo "step3:"$l
done
else
echo "step3:do not find:"$lastFind
fi
done
rm -f rep.tmp
echo "[step12] - 开始混淆,替换属性设置函数"
#开始混淆,替换属性设置函数
cat repProperty.txt |
while read line
do
ar=(`echo "$line"|cut -f 1-2 -d " "`)
first=`echo ${ar[1]}|cut -c -1| sed "y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/"`
second=`echo ${ar[1]}|cut -c 2-`
lastFind=`echo set$first$second`
lastRep=`echo setZ${ar[0]}m`
rm -f rep.tmp
if grep -r -n -I -w "$lastFind" $ROOTFOLDER $EXCLUDE_DIR $INCLUDE_DIR --include="*.[mhc]" --include="*.mm" --include="*.storyboard" --include="*.xib" >rep.tmp
then
cat rep.tmp |
while read l
do
v1=$(echo "$l"|cut -d: -f 1 )
v2=$(echo "$l"|cut -d: -f 2 )
sed -i '' ''"$v2"'s/'"$lastFind"'/'"$lastRep"'/g' $v1
echo "step3:"$l
done
else
echo "step3:do not find:"$lastFind
fi
done
rm -f rep.tmp
rm -f repProperty.txt
echo "########################### 恭喜您,代码混淆完成! ###########################"
echo "########################### 运行混淆后的工程! ###########################"
exit