awk知识点总结

时间:2021-09-14 05:47:26

find+xargs+grep+sed+awk系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html


0.学习资料推荐

1.awk入门:看视频、找博客或者看《AWK程序设计语言》的第1-3章。

2.awk进阶:《awk程序设计语言》剩余内容(剩余的我也没看过,哈哈),man awk

3.awk编程语言:用于掌控awk的语法和方方面面,推荐书籍《gnu awk: Effective AWK Programming

1. awk简介和基本语法格式

Awk自动地搜索输入文件,并把每一个输入行切分成字段。许多工作都是自动完成的,例如读取每个输入行、字段分割、存储管理、初始化等。在AWK中不需声明变量数据类型,它内置字符串类型和数值类型。

一般来说,在CentOS上安装的awk默认是gawk。它的调用格式为:

awk [OPTIONS] -f program_file [--] filename_list
awk [OPTIONS] [--] program filename_list

program是awk程序的重中之重,称为awk的程序,它的格式为PATTERN{ACTIONS}。awk每读入一行,都会先与PATTERN做匹配比较,当找到符合条件的数据就执行对应的ACTION。

其中PATTERN或ACTIONS二者可省一。省略PATTERN时表示对所有行都执行ACTIONS,省略ACTIONS表示对符合条件的行执行默认的print动作。因为二者可省一,所以用大括号{}将ACTIONS部分包围起来,以区分PATTERN和ACTIONS。

一个简单的例子,输出/etc/passwd中用户shell为/bin/bash的用户名,其中使用"-F"选项指定冒号作为分隔符。

awk -F':' '$7 == "/bin/bash"{print "who use bash shell: ",$1}' /etc/passwd

其中位置变量$1,$2...为该行的第几个字段,"$0"表示整行。

如果要输出多个字段,则字段之间使用逗号","分隔,例如{print $1,$5}但输出时,仍默认以空格分隔各输出字段。

如果action为print $1 $5,则结果会将"$1"和"$5"拼接在一起,因为空格是awk中的拼接字符。例如变量赋值name = "abc" "bcd"等价于name="abcbcd"。其实不算是拼接符,而是因为awk会忽略任何不被引号包围的空白。

2.print和printf格式化输出

awk使用print和printf输出数据,不仅可以输出到标准输出中,还可以重定向到文件中,使用管道传递给另一个命令。

  1. print
    将 $0 打印到标准输出。等价于print $0
  2. print expression, expression, …
    打印各个 expression, expression 之间由 OFS 分开, 由 ORS 终止
  3. print expression,expression,… > filename
    文件名filename必须使用双引号包围,否则被当作变量。且文件只会被打开一次。
  4. print expression,expression,… >> filename
  5. print expression,expression,… | command
    将数据传递给系统命令。命令需要使用双引号包围。
  6. printf(format,expression,expression,…)
  7. printf(format,expression,expression,…) > filename
  8. printf(format,expression,expression,…) >> filename
  9. printf(format,expression,expression,…) | command
  10. close(filename), close( command)
    断开 print 与 filename (或 command) 之间的连接
  11. system(command)
    执行 command; 函数的返回值是 command 的退出状态

如果print或printf的参数列表中含有操作符,则需要使用括号包围,否则容易产生歧义。如:

print($1, $3) > ($3 > 100 ? "bigpop" : "smallpop")
print $1, ($2 > $3)

执行系统命令的方式,可以通过管道的方式,也可以通过system()函数。注意包围命令的引号加的位置。

awk 'BEGIN{name="ma long shuai";print (1,2,3,4) | "echo " name}'
awk 'BEGIN{while (("fdisk -l" | getline) >0){print $0}}'
awk 'BEGIN{system("fdisk -l")}'
awk 'BEGIN{name="ma long shuai";system("echo " name)}'

printf命令可以输出更格式化的数据。

printf(format, value1, value2, ... , valueN)

format是一个字符串,包含按字面输出的纯文本,还包含输出格式,格式使用格式说明符"%"描述,后面跟着几个字符,这些字符控制一个value的输出格式。第一个"%"描述value1的输出格式,第二个"%"描述value2的输出格式,依次类推。因此,"%"的数量应该和被输出的value数量一样多。

例如:

{ printf("total pay for %s is $%.2f\n", $1, $2 * $3) }
{ printf("%-8s $%6.2f\n", $1, $2 * $3) }

第一个程序包含了两个要格式化的value,分别是"$1"和"$2 * $3"。这两个value的输出格式分别被"%s"和"%.2f"描述,前者表示按字符串格式输出"$1",后者表示按小数值格式输出"$2 * $3",且小数位占2位。由于printf不自带尾随换行符,因此手动加一个换行符"\n"。

第二个程序,"%-8s"表示"$1"按字符串格式输出,但短横线"-"表示要左对齐输出,"8"表示占用8个字符宽度,不足之数在右边空格补齐。"%6.2f"表示按小数格式输出"$2 * $3",且小数位占用2位,总字符数占用6位。注意,小数点也占用一个字符宽度。因此,一个可能的输出值为"123.20"。

格式说明符"%"后可跟以下几个常见字符:

  • 格式符:
    • %d,%i:十进制整数;
    • %f:显示浮点数;
    • %s:显示字符串;
    • %u:无符号整数;
    • %%:显示%自身。
  • 修饰符:
    • N:显示宽度;N为数值,宽度不足时若为左对齐则右边空格补足,若右对齐则左边空格补足。
    • -:左对齐;
    • +:显示数值正负号。
    • 0:表示以0填充。

3.输入行的字段分隔符和行分隔符

使用"-F"选项或设置内置变量"FS"可以控制输入行的字段分隔符,默认分隔符为" "。可通过正则表达式指定分隔符,其实可以认为总是以正则方式指定分隔符。

以下是几个示例和需要注意的空格分隔符:

  1. -F " ":默认会压缩所有前导空白,包括制表符和空格。
  2. -F " :":当空格后跟一个冒号时作为分隔符。会压缩前导空格,但不会匹配制表符,更不会压缩制表符。
  3. -F "[ ]':只表示一个空格,不压缩任何空白。
  4. -F "|":指定竖线作为分隔符。
  5. -F ",[ \t]*|[ \t]+":逗号后跟0或多个空白,或者只有1或多个空白时作为分隔符。

也就是说,当空格写在最前面且不被中括号包围限制的时候,总会忽略前导空格,但不一定能匹配制表符。

使用内置变量"RS"可以控制输入行的行分隔符,默认为"\n",只有遇到行分隔符时才作为"一行"记录被读取。

将其读作行分隔符不标准,应该读为"记录分隔符"。例如设置以制表符作为记录分隔符。

RS="\t"

记录分隔符变量"RS"只识别第一个字符,若设置为"\t\t",则第二个"\t"被忽略。但是控制输出记录分隔符的内置变量OFS则可识别多字符。

可通过设置FS="\n";RS=""使得awk能处理多行记录。但此时,原本的每行数据整体变成一个字段。

4.BGEIN和END

BEGIN和END是一个特殊的PATTERN,BEGIN引导的程序是在awk读取第一个文件第一行前要执行的awk程序,END引导的程序是在awk处理完最后一个文件最后一行后要执行的awk程序。通常BEGIN用于输出一个标题,或者初始化一些格式、变量等,END则用于最后的总结性输出。

所以awk稍微完整一点的格式为:

BGEIN{ACTIONS}PATTERN{ACTIONS}END{ACTIONS}

刨去BEGIN和END引导的两个程序,中间处理输入文件的程序PATTER{ACTIONS}称为"主输入循环(main input loop)"。在进入主输入循环之前,可以不用提供输入流,但进入主输入循环后,必须提供输入流。

例如,在开始处理文件前,设置输出报表的头部,在最后输出总共输出了多少行。其中print ""表示输出一个空行。

BEGIN{print "ID NAME GENDER GENDER";print ""}{print $0}END{print "total num: " NR}

5.数组

awk的数组和shell的数组类似,都支持数值index的普通数组和字符串index的关联数组,其实数值index仍然会转换成字符串格式的index,所以awk的数组类型都是关联数组。

数组格式:array_name[index]
数值赋值:array_name[1]=value1
引用数组:array_name[1]

需要注意的是,关联数组的index必须使用双引号包围,例如array_name["ma"],如果写成array_name[ma],则表示使用变量"ma"的值作为index。若"ma"变量未定义,则这会定义一个新的数组array_name[""]

使用index in array_name的方式可以判断数组array_name中是否有index下标对应的数组元素。如果有,它会返回1,否则返回0。所以判断语句可以如下:

if ( "ma" in array_name )

其实,判断某个数组变量的值是否为空也可判断该数组元素是否存在,如下。但这有副作用,当该元素不存在时,会创建它。

if ( array_name["ma"] != "" )

for循环的一种变体:

for (i in array_name){
do something about array_name[i]
}

可以用于遍历数组,其中变量"i"是遍历数组时的index,array_name是数组名。这是以遍历index的方式遍历数组。由于index的顺序随机,所以遍历时顺序也是随机的。当然,遍历数组的方式有多种,以上只是for循环遍历的一种方式。

使用delete语句可以删除数组中的元素或者删除整个数组。如下:

delete array_name["ma"]  # 删除array_name中下标为ma的元素
delete array_name # 删除数组array_name

6.流程控制语句

在ACTION中,可以使用流程控制语句。包括但不限于:

if (expression) statements
if (expression) statements else statements
while (expression) statements
for (expression; expression; expression) statements
for (expression in array) statements
do statements while (expression)

还有几个能影响循环的动作:

break:退出循环。
continue:退出当前循环,进入下一个循环
next:读入下一行,并awk程序的顶端从头开始。这个awk程序是PATTERN{action}这部分,不包括BEGIN{action}。
exit code:直接进入END,若本就在END中,则直接退出awk。如果END中的exit没有定义code,则采用前一个exit的code。

6.1 条件判断语句

if格式:

PATTERN {
if (test_cmd){
cmd1
cmd2
...
}
}

if-else格式为:

PATTERN {
if (test_cmd){
cmd1
cmd2
......
}
else
cmd3
}

当if或else结构中的命令只有一个,则其内可省大括号,如果超过一个,则需要使用大括号。

若采用一行书写格式,则如下:

PATTERN {if (test_cmd){cmd1;cmd2;...}else {cmd3;cmd4...}}

还有if-else if-else格式。

PATTERN {
if (test_cmd){cmd_list1}
else if {cmd_list2}
else if {cmd_list3}
else {cmd_list}
}

还支持多目操作符。

expression ? action1 : action2

其中"?"和":"还可以继续嵌套。

6.2 while循环

结构:

PATTERN {
cmd1
while (test_cmd)
cmd
}

当cmd有多个时,使用大括号包围。

PATTERN {
cmd1
while (test_cmd){
cmd2
cmd3
....
}
}

一行书写格式:

PATTERN{cmd1;while (test_cmd){cmd1;cmd2}}

6.3 do循环

和while循环类似,地位和shell中的until循环一样。都是至少执行一次命令列表。

结构:

PATTERN {
do{
cmd1
cmd2
} while (test_cmd)
}

6.4 for循环

结构大致如下:

PATTERN {
for (i=1;i<=10;++i){
cmd1
cmd2
}
}

for后括号中包括:变量初始值,条件判断和计数器增长表达式。

7.更完整的awk程序格式和表达式

更完整的awk程序的语法格式有以下几种:

BEGIN{actions}
END{actions}
expr{actions}
/regexp/{actions}:可被regexp匹配的行才执行actions
expr1,expr2{actions}:表示范围,从满足expr1的行开始,到满足expr2的行结束

其中:

  • expr是表达式。
    • 比较操作符有:< <= == != >= > ~ !~
    • 算术操作符有:+ - * / % ^(取幂) **(取幂)。其中**非POSIX标准,不可移植。
    • 赋值操作符有:++ -- += -= *= /= %= ^= **=。awk支持复合赋值,例如FS = OFS = "\t"表示字段分隔符和输出字段分隔符都被赋值为制表符。
  • /regexp/为正则匹配模式,表示该行能被regexp匹配则为真。还有以下两种匹配表达式,分别表示给定的字符串能(不能)匹配就为真。
    • string ~ /regexp/
    • string !~ /regexp/
  • 还有复合模式的表达式。使用逻辑操作符"&&"、"||"和"!"连接。如$4 == "Asia" && $3 > 500! (NR > 1 && NF > 3)

awk中字符串和数值数据类型是自动转换的。如果想要得到一个字符串值,可以value ""进行转换,同理,如果想要得到一个数值,可以value + 0

另外,正则表达式可以不用包围在两个斜杠中。可以将正则表达式赋值给一个变量,然后使用该变量取匹配数据。例如:

reg="^[0-9]+$"
$2 ~ reg

甚至直接使用双引号替换斜杠也允许。但不建议使用,因为一个元字符可能需要多个反斜杠来保护,使得看上去极其晦涩。

8.awk中的变量

普通变量:给变量赋值时,如果要赋值字符串,则该字符串应该使用双引号包围,特别是包含特殊字符时。赋值数值时无所谓。 字段变量:$1,$2,$3,...,$NF,还有"$0"表示整行内容。另外,可以直接赋值一个新字段或修改字段值。但这都会影响"$0"。同理,修改"$0"也会影响各字段值。
内置变量:其实可以分为两类,一类是awk内部自动修改的变量,如行数变量NR,一类是内部不会改动的系统变量,如输入字段分隔符变量FS,完全需要手动修改,这类一般都有默认值。

  • ARGV:命令行参数数组。从0开始计数直到ARGC-1。
  • ARGC:ARGV数组元素的个数。
  • FILENAME:当前处理的文件名。
  • FNR:当前处理文件的记录号。(file record num)
  • NR:已处理的总记录数。多个文件时不重置。(record num)
  • NF:当前记录的字段总数。(field num)
  • FS:输入的字段分隔符。默认为空白。(file separate)
  • OFS:输出的字段分隔符。默认为空白。(output record separate)
  • RS:输入流的记录分隔符。默认为"\n"。(record separate)
  • ORS:输出流的记录分隔符。默认为"\n"。(output record separate)
  • OFMT:printf输出数值转换成字符串输出时的格式。默认为"%.6g"。
  • CONVFMT:printf输出数值转换成字符串输出时的格式。会被OFMT覆盖。默认为"%.6g"。
  • RLENGTH:被match函数匹配的字符串的长度。
  • RSTART:被match函数匹配的字符串的开始位置。
  • SUBSEP:下标分隔符。默认为"\034",ASCII中034代表的是双引号'"'。

注意,像NR、FNR、RS等的对象是记录(record),而非行。只有当RS="\n"时,读取了一行才表示读取了一条记录。

9.awk中的内置函数

awk有两类内置函数:算术函数和字符串函数。还支持自定义函数。

算术函数:

  • cos(x):取x的余弦。
  • sin(x):取x的正弦。
  • sqrt(x):取x的平方根。
  • rand():返回一个随机数r,其中0<=r<1
  • srand(x):设置rand()的种子值为x。种子值相同时,rand()的结果相同。可print srand()输出当前种子值。
  • int(x):取x的整数部分。

因此,要生成一个范围[1,n]的随机数,使用int(n*rand() + 1),要四舍五入一个数值,使用int(x + 0.5)

随机数的种子值相同时,rand的结果总是相同。如下两次运行结果,两次结果中,前两个rand()值相同,后两个rand()值不同,因为中间使用了srand()重设种子值。

awk 'BEGIN{print rand();print rand();srand();print rand();print rand();print srand()}'
0.237788
0.291066
0.109925
0.983692
1504560578
awk 'BEGIN{print rand();print rand();srand();print rand();print rand();print srand()}'
0.237788
0.291066
0.96322
0.670495
1504560604

字符串函数:建议下面的所有regexp都使用"//"包围。

  • index(str1,str2):返回子串str2在字符串str1中第一次出现的位置。如果没有指定str1,则返回0。
  • length(str1):返回字符串str1的长度。如果未给定str1,则表示计算"$0"的长度。
  • substr(str1,p):返回str1中从p位置开始的后缀字符串。
  • substr(str1,p,n):返回str1中从p位置开始,长度为n的子串。
  • match(str1,regexp):如果regexp能匹配str1,则返回匹配起始位置。否则返回0。它会设置内置变量RSTART和RLENGTH的值。
  • split(str1,array,sep):使用字段分隔符sep将str1分割到数组array中,并返回数组的元素个数。如果未指定sep则采用FS的值。因此该函数用于切分字段到数组中,下标从1开始。
  • sprintf(fmt,expr):根据printf的格式fmt,返回格式化后的expr。
  • sub(regexp,rep,str2):将str2中第一个被regexp匹配的字符串替换成rep,替换成功则返回1(表示替换了1次),否则返回0。注意是贪婪匹配。
  • sub(regexp,rep):将"$0"中第一个被regexp匹配的字符串替换成rep,替换成功则返回1,否则返回0。注意是贪婪匹配。
  • gsub(regexp,rep,str2):将str2中所有被regexp匹配的内容替换成rep,并返回替换的次数。
  • gsub(regexp,rep):将"$0"中所有被regexp匹配的内容替换成rep,并返回替换的次数。
  • toupper(str):将str转换成大写字母,并返回新串。
  • tolower(str):将str转换成小写字母,并返回新串。

关于替换函数sub和gsub,可以在替换字符串rep中使用"&"符号表示反向引用,引用的是整个被匹配的部分。

awk 'BEGIN{
print index("banana","na")
print length("banana")
print match("banana","na.*")
print toupper("banana")
print substr("banana",3)}'
3
6
3
BANANA
nana
awk 'BEGIN{str1="x&x";str2="banana"
print sub(/a.*n/,str1,str2)
print str2}'
1
bxananxa
awk 'BEGIN{
print match("banana",/a.*n/)
print RSTART,RLENGTH}'
2
2 4
awk 'BEGIN{print sprintf("hello %i world %5s","123","abc")}'
hello 123 world abc
awk 'BEGIN{
name="Ma long shuai"
split(name,myname)
for (i in myname){
print myname[i]}
}'
Ma
long
shuai

纵观上述字符串函数,没有一个函数可以将匹配成功的字符串输出出来。但借助match()和RSTART、RLENGTH可以实现。

例如,取出"Ma:long:shuai"中的"long"并输出。

awk 'BEGIN{
name="Ma:long:shuai"
if (match(name,/:[^:]*:/)){
print substr(name,RSTART+1,RLENGTH-2)}}'
long

10.自定义函数

function name(parameter-list) {
statements
}

函数中的变量不影响函数外的变量,但可以使用外部变量。参数列表使用逗号分隔,这些参数只在函数内部生效。

可以在awk的引号内任意位置处定义函数(即使是BEGIN之前或END之后),且函数的调用位置可以在函数的定义位置之前。但注意,函数必须不能定义在BEGIN或主输入循环或END内部,否则自定义函数的大括号会和包围action的大括号冲突而报错。即如下(1)-(4)处位置可定义定义函数,在任意位置处调用函数。

awk '(1)BEGIN{ACTIONS}(2)PATTERN{ACTIONS}(3)END{ACTIONS}(4)'

在函数的statements中,可以使用return expression语句,表示函数的返回值。

例如,创建一个"向字符串指定位置处插入一个字符"的函数。

awk 'function insert(STRING, POS, INS) {
before_tmp = substr(STRING, 1, POS)
after_tmp = substr(STRING, POS + 1)
return before_tmp INS after_tmp
}
BEGIN{print insert("banana",3,"x")}'

11.getline函数

getline函数用于从文件、标准输入或管道中读取数据,并按情况设置变量的值。getline可以自动不断的加载下一行。如果能读取记录,则getline的返回值为1,遇到输入流的尾部时,返回值为0,不能读取记录(如文件没有读取权限、文件不存在)时,返回值为“-1"。

其中:

  • getline:会从主输入文件中读取记录。会同时设置$0,NF,NR,FNR。
  • getline var:会从主输入文件中读取记录,并将读取的记录赋值给变量var。会同时设置var,NR,FNR。
  • getline <file:从外部文件file中读取记录。同时会设置$0,NF。
  • getline var <file:从外部文件file中读取记录,并将读取的记录赋值给变量var。会同时设置var。
  • cmd | getline:从管道中读取记录。会同时设置$0,NF。
  • cmd | getline var:从管道中读取记录,并将读取的记录赋值给变量var。会同时设置var。

也就是说:

  1. 当getline从非主输入文件读取记录时,不会设置NR和FNR;
  2. 当getline后没有给定变量var时,会将读取的记录赋值给$0,于是会同时设置NF并切分成字段;否则将读取的记录赋值给变量var,不会设置NF切分字段。

仍然注意,从外部文件file中读取记录时,需要使用双引号包围文件名,否则被当成awk中的变量。

例如,执行Linux下的who命令并传递给getline读取,每读取一行记录,变量n自增1。

while ("who" | getline)
n++

将Linux命令date的结果保存到awk的变量date中。

"date" | getline date

当写成循环时,如:

while (getline <"file"){
cmd...
}

这是不安全的,因为当无法读取file时,返回值为"-1",而while循环的判断条件是0和非0,所以"-1"也会进入死循环。所以,安全的写法为:

while (getline <"file" >0){
cmd...
}

12.向awk传递变量

awk很重要且必备的能力是接受外界的变量,例如shell中的变量,shell中命令执行的结果,或者是在开始执行awk前应该初始化的变量。

例如,在shell中定义一个变量name,传递给awk使用。

awk -v awk_name="$name" 'BEGIN{print awk_name}'
Ma longshuai

有三种方式可以向awk传递变量:

1.将待传递变量当作文件名被awk解析。awk识别后发现是赋值语句,就认为其是变量传递。变量赋值语句必须定义awk program之后。此法定义的变量不可在BEGIN中使用,因为它是被当成文件解析的,只有在需要读取主输入文件的时候才会被解析。

awk 'BEGIN{}PATTERN{print var1,var2,var3}' var1=value1 var2=value2 file1 var3=value3 var1=value4 file2

在上面的语句中,当awk执行完BEGIN程序后,准备读取主输入,于是开始解析program后的输入文件。解析时发现,var1和var2都是赋值语句,于是当成变量处理,当读取到file1时,发现只有一个参数,则当作输入文件,于是开始处理该文件。在处理file1时,var1和var2都是有效的,但var3还未赋值,因此var3无效。当处理完file1后,继续解析下一个主输入文件,此时var3被赋值,并开始处理file2。在处理file2时,var1、var2和var3都是有效的,但var1被新值覆盖。

此外,还可以将shell命令的结果赋值给这些预定义变量。如下展示了几种变量定义的方式:

name="Ma longshuai"
awk 'program' OFS=":" var1="$name" var2="`echo Ma longshuai2`" var3="Ma longshuai3" var4=Malongshuai4 filename

不仅可以定义普通变量,还可以定义内置变量(如上OFS)。注意加引号的方式:为了安全,应该对所有赋值语句的value部分加上双引号,除非所赋的值不包含特殊字符。所以,如果上面的var1赋值语句写成var1=$name,将被awk解析成var1=Ma longshuai,于是var1的值为Ma,主输入文件为longshuai。

2.使用"-v"选项传递。变量赋值语句必须定义在awk program之前。这种方法定义的变量可以在BEGIN程序中使用。

除了定义在program之前,定义方式同上。每定义一个变量,都需要使用一个"-v"选项。如:

name="Ma longshuai"
awk -v OFS=":" -v var1="$name" -v var2="`echo Ma longshuai2`" -v var3="Ma longshuai3" 'program' filename

3.通过参数数组ARGV的方式。

ARGV是内置的数组变量。awk内部会将命令行切分,并按规则将各参数存放到ARGV数组中,数组下标从0开始,这是awk中唯一下标从0开始的数组。在存放到ARGV时,所有的选项和program会被忽略。

每存储一个数组变量,特殊变量ARGC的值增加1。因此ARGC的值代表的是参数的个数。所以,数组变量从ARGV[0]到ARGV[ARGC-1]。

可使用类似下面的循环来遍历ARGV数组。

awk -F "\t" -v var1="value1" 'BEGIN{
for(i=0;i<ARGC;++i){
print "ARGV[" i "]: " ARGV[i]
}
print "ARGC: " ARGC
}' "a" "b" "v=1" file
ARGV[0]: awk
ARGV[1]: a
ARGV[2]: b
ARGV[3]: v=1
ARGV[4]: file
ARGC: 5

注意,ARGV[0]存储的是awk命令,"-F"和"-v"选项都没有存储到ARGV中。

ARGC和ARGV数组变量的值都可以手动修改。命令行分割存储完成之后,开始处理BEGIN,再处理主循环输入。因此,在BEGIN中修改ARGV中输入文件对应的值,可以改变awk所读取的输入文件,若将其设置为空,则该数组变量直接被跳过,也就不再读取该输入文件。

需要注意的是,当增加ARGV元素时,必须同时递增ARGC的值,因为awk是根据AGRC来读取ARGV的。同理,只增加ARGC的值,将导致新建ARGV数组元素,且这些新元素的值为空。也因此,如果减小ARGC的值,将导致无法访问超出ARGC-1边界的ARGV元素。