刘嵩的小站 神様は乗り越えられる試練しか与えない。

Hack Bash 编程 学习笔记

2017-06-05

又一个从入门到放弃的学习笔记。

又找到一本书,叫做《Bash中文文档》,体会到了读文档的重要性。这段时间一直都在看跟技术编程有关的书籍,因为希望以后的工作能够更加的顺畅,更多的受益于这些自动化的手段,让自己从简单的重复劳动中解脱出来。

第一章 总体介绍

1,什么是bash

bash就是一个操作操作系统的shell,命令解释器,现在在linux和mac中广泛应用。

2,什么是shell

shell就是一个能执行各种命令的宏处理器。

第二章 术语定义

基本上就是,各种定义,比如posix,空白符,控制运算符等的定义。

第三章 shell的基本功能

shell的语法

shell在读取输入的时候,要经过一系列的操作,如果在输入中开始了一个注释,shell会把注释符号#以及后面的一整行都忽略掉。概括的说,shell会读取输入并将之分解为一个个单词和运算符,并使用引用规则来决定每个单词和字符的不同的含义。然后shell会把这些解析为命令和其他结构去除一些特定单词的特殊含义,对另外一些进行扩展,根据需要进行重定向,执行指定的命令,等待其退出状态,并让这个状态能用于后续的检查和处理。

  1. 引用,引用在shell中用来除去某些特殊的字符或者单词,它可以用来禁止对特殊字符的特殊处理,使得保留字不再被认为是保留字,或者禁止参数扩展。bash有三种引用机制
    • 转义字符
    • 单引用
    • 双引用
  2. 转义字符,在bash中,没有转义的反斜杠 \ 是转义字符,他能保留其下一个字符的的字面含义,除非这个字符是换行符。如果出现 \ 符号后后面直接跟着新的一行,也就是说,这个符号是一行的最后一个字符,后面没有紧接着的字符,而这个反斜杠也没有被别的引用,那么就表示,这个符号的前面的行和这个符号后面的那行其实是在一行的,连续的。在流输入的时候,这里换行这件事情,会被忽略掉。
  3. 单引用,把字符用单引号引用,能够保留银行内部各个字符的字面含义。但是,在这对单引号内部就不能够再使用别的单引号了,就算已经被转义(被反斜杠转义)过的单引号也不行。
  4. 双引用,把字符串用双引号 “ 引用,能够保留引号内部的各个字符的字面含义,这点和单引用类似。
    • 但是像$ ‘ \ ! 这四个符号不行,他们不能保留字面含义。在双引号中,$和’继续保留它的特殊的功能;
    • 反斜杠只有在后面的字符是$ ‘ \ ! 或者换行符的时候,才保留它的特殊的含义,在双引号中,如果反斜杠后面是这些字符中的任意一个,那么这个反斜杠就会被删除。
    • 而反斜杠后面的字符如果没有特殊的含义,那么它将被保留。
    • 在双引号中,可以出现另一个双引号,只要它在反斜杠后面(也就是说被转义了)。
    • 如果打开了历史扩展,!将导致历史扩展,除非它被反斜杠转义,在!前面的反斜杠不会被删除。

在很多命令中,都需要指定单个字符,这时候应该使用ansi标准c引用,双引号转义就不行。比如:

tr $’\n’ ‘ ‘ file

把一个叫做file的文件里面所有的行,用空格连接起来。

  • 这里面的\n,表示新的一行,前面加上$,表示使用这个变量,加上‘’表示对这个变量进行引用。
  • 类似的符号还有这么多:

\a 警告-响铃

\b 退格删除

\e 转义字符-不属于ansi c

\f 走纸换页

\n 新行

\r 换行

\t 水平-制表符

\v 垂直-制表符

\’ 单引号

\ 反斜杠

\nn 由八进制数nnn代表的一个八位字符

\xHH 由16进制数HH代表的一个八位字符

\cx 一个控制字符 CTROL_X

shell的命令

一个简单的shell 命令例如通过 echo a b c,包含该命令本身,其后还有一些参数,他们都用空格隔开。

复杂一些的命令由简单的命令通过各种方式组合而成,通过管道命令,这时一个命令的输出成为另一个命令的输入,或者通过循环或条件的命令,或者通过其他的组合方式。

  1. 简单的命令使用最频繁,他仅仅包括空白分隔的多个单词,其结尾是一个shell控制运算符,其中第一个单词通过指定要执行的命令,而后面的单词都是这个命令的参数。
  2. 管道,管道是有控制字符,|或者|& 分隔开的一系列的简单指令。

time -p command1 [|或者|&] command2

管道里的每个命令的输出都经由管道与下一个命令的输入相连接,也就是说每个命令都去读取上一个命令的输出。这种连接早在命令中指定的任何重定向之前就已经进行了。

如果使用了|&这种管道,command1的标准错误输出将会和command2的标准输出相连接,这是command2>&command1 |的简写形式,这种对标准错误输出的隐藏重定向是在命令中指定任何的重定向之后进行的。

这里面的time指令会在管道执行完毕后输出其执行的时间的统计信息。

  1. 命令的队列,命令的队列是由一个或者管道通过运算符; & && || 连接而成,最后还可以由; &或者换行符结束。
    • 在这些队列运算中,&& 和||具有相同的优先级,其次是&和;,这两个也具有同样的优先级。
    • 在命令队列中可以使用一个或者多个换行符分隔命令,这与分号是等价的。
    • 如果一个命令是由控制字符&结束,则shell会不同步在子shell中执行该命令,我们通常称之为后台。这时shell并不等待命令的结束,而是返回状态0(即逻辑真)。如果我们没有启动作业控制,并且也没有显示指定重定向,那么则在一步执行的命令的标准输入将被重定向到/dev/mull。也就是啥也没有了。
    • 由;分隔的命令,将会相继执行。shell依次等待每个命令的结束,整个返回状态是最后一个要执行的命令的返回状态。
    • “与”或者“和”命令队列是分别由||和&&分隔的一个或者多个管道。
  • 命令1 && 命令2
  • 仅当第一个命令返回值为0的时候,才执行第二个命令

  • 命令2 || 命令2
  • 仅当第一个命令返回非0值的时候,才执行第二个命令
  • “与”或者“和”命令队列返回的值是最后一个被执行的命令的值。
  1. 复合命令

复合命令是shell 编程的结构体,每个结构体都是以保留字或者控制运算符开头,然后以与之对应的保留字或者控制运算符号结束,任何复合命令相关的重定向,都做用于改复合命令里面的所有命令除非显示覆盖。 bash提供:

  • 循环结构

until 循环结构

until 测试命令;do 命令块;done

只要测试命令返回非零,就执行模块,返回值是命令块中执行后的对应的返回值,如果命令块没有被执行,那么返回零。 命令块是指一条或者几条命令的罗列或者组合。 0表示真值,也就是说测试命令通过。

while 循环结构

while 测试命令;do 命令块;done

只要测试命令返回零,就执行模块,返回值是命令块中执行后的对应的返回值,如果命令块没有被执行,那么返回零。

for 循环

for 变量 in [单词];do 命令块;done

将单词扩展成一个列表,然后吧结果中列表的每个元素都赋值给变量,并执行一次命令模块,如果没有in[单词]这个部分,则for一次对每个位置参数都执行一个命令块,好像指定了 in $@ 一样,返回值是命令块中,最后一次执行后的结果,如果对单词的扩展没有得到任何元素,则不执行命令,并返回零。

for 命令还支持另一种格式:

for ((表达式1;表达式2;表达式3));do 命令块 ;done

这个意思就是,按照shell的运算规则,对表达式1进行运算求值,然后不断对表达式2进行求值,直到结果为零,如果不为零,就执行一次命令块,并计算表达式三。如果省略了任何一个表达式,则效果就像该表达式总是返回1,其返回值是命令块中最后一个被执行的命令的返回值。如果表达式的值都是假的,则返回假,即非零。

分节结束

可以使用内置的命令break和continue来控制循环命令的执行。

  • 条件结构

if 条件结构

if 测试命令;then

命令块1

elif 测试命令;then

命令块2

else

其他命令块

fi

这个不用多解释。

case 条件结构

case 单词 in

(第一种模式|第二种模式|第三种模式)

命令块;;

第四种模式|第五种模式) 
> 命令块;;

*)

命令块;;

esac

case命令会选择性执行与单词所匹配的第一个模式对应的命令块。

case的分分句的数量不限,但是每个分句都以;; ,& ;;& 这三种结束,最先匹配的模式决定了那个模块被执行。

模式列表前面可以加一个对‘)’对应的‘(’,但是不是必须的。所以说,case里面,括号都是一半的。

select 条件结构

select 名称 in [单词表];do 命令块;done

这个命令的格式几乎和for命令一摸一样。

in后面的单词表被扩展并生成一个项目列表。注意,这个扩展后的列表将会打印到标准错误输出流中,并且每个项目前面都会加上一个序数。如果省略了in [单词表]部分,则打印位置参数,就好像使用了 in $@ 一样。

select结构使得菜单的生成变的简单。

(( )) 条件结构

((算数表达式))

根据shell 的算数运算的规则,对算数表达式求值,如果这个值不是零,则返回状态是0,否则返回1.

这个指令与 let “表达式” 完全等价。

[[条件表达式]]

对于条件表达式求值,并根据其结果返回0或者1,条件表达式是由原子成分组成的,在中间的单词不会进行单词和文件名扩展,但却进行波浪号扩展,参数和变量扩展算数扩展命令替换,进程替换以及引用去除。诸如 -f 等条件运算符不能被引用,否则她们就不是原子表达式了。

如果使用==和!=运算符,则运算符的右边会看成是一个模式,如果使用了==,并且字符串匹配,或者使用了!=,字符串不匹配,则返回值是0,否则返回1。模式的任何部分都可以背引用以强制把其当作字符串来匹配。

还有一个=~,双目运算符,他和==和!=具有同样的优先级,如果字符串和模式匹配,则其右边的字符串就被认为是一个扩展的正则表达式来匹配。

  • 命令分组并整体执行

()

(表达式)

把一列命令放在括号中间,就会创建一个子shell环境,并在这个字shell中执行该列表中的每个值,因为这个命令列表实在子shell中执行的,所以在子shell结束后,其中的变量复制将不再有效。

{}

{表达式;}

把一列命令放在大括号里,这列命令九回在当前的shell中执行,而不是创建子shell,列表后面的顿号是必须的。

在bash的分隔符中,;是使用的最频繁的,他出了具有分隔的作用,没有其他任何含义,所以,在任何命令的末尾,都可以使用它,但如果一个命令单独成行,这个符号完全可以省略。

  • 出了子shell的创建,上述两种结构之间由于历史原因还有微妙的差别,大括号是保留字,所以它与命令列表之间必须用空白符或其他的元字符分开,而圆括号是运算符,所以它既是它们和命令列表直接没有空白符分开,也会被其中的命令当作独立的符号。
  1. 协同进程,协同进程coprocess 是指一个shell命令前面面有coproc保留字;它是在子shell中异步执行的,就好像这个命令后面有控制运算符&一样,协同进程和气父进程之间有双向管道,这个命令的格式是:

coproc [NAME] 命令 [重定向]

上述命令创建了一个名为NAME的协同进程,如果没有指定name,那么默认的名称是COPROC,如果这里的命令指示一个简单的命令,则不能指定NAME,否则它会被当成命令的第一个单词。

coproc被执行的时候,shell会在父进程中创建一个名为那么的变量组,命令的标准输出通过管道和父进程的一个文件描述符相连接,该文件的描述符被付给name[0],类似的,命令的标准输入通过管 道和父进程的一个文件描述符相连;该文件描述符被赋给 NAME[1]。这个管道是在命令当中指定的任何重定向之前就建立了。这些文件描述符可以在shell命令和重定向中通过标准的单词扩展而当作参数使用。

shell的函数

shell 函数把一组命令与单一的名称相关联,以便以后执行,在执行的时候,就像常规的命令一样,如果shell函数的名称被当作一个简单的命令,与他相关联的命令就会被执行,shell 函数是当前shell环境中执行的,而不是创建新的进程。

函数通过一下的语法来定义:

[function] 名称() 复合命令模块 [重定向]

上边定义了一个叫做名称的函数,保留字function是可选的。如果有function这个保留字,则可以省略括号。

复合模块是函数体,它通常是包含在{}之间的命令列表,也可以是上边列出的任何复合命名。每当名称被指定为一个命令的时候,复合命令块就会被执行,当函数被执行的时候,与之相关的重定向也会同时被执行。

可以使用unset -t,来取消函数的定义。

shell的参数

参数是能够储存值的实体,它可以是一个名称,也可以是一个数字或者下面列出的特殊字符之一。

变量是名称所代表的参数,每个变量都有值以及零个或者多个属性,属性通过内部命令declare来设置。

名称 =[值]

复制语句还可以作为内部命令,alias declare typeset export readonly 和local的参数。

  • 位置参数

位置参数,是由除了单个0以外的一个多个数字表示的参数,它是在shell启动是由其他参数赋值的,并且可以由set来重新复制,第n个位置参数可以用$n来表示,而应用内部使用set和shift来设置和删除,在执行sehll函数是,位置参数会被暂时更换。

  • 特殊参数

shell会对一些的参数特殊处理,这些参数只能使用不能赋值。

* 扩展为从1开始的所有位置参数。如果它出现在双引号中,则扩展为一个包含每个参数的单词,参数之间用特殊变量ifs的第一个字符分隔,也就是说,”$*”和“$1c$2c…”是等价的。其中c是特殊变量ifs的第一个字符没有设置,则参数之间用空格分隔,如果ifs为空,则参数直接相连,中间没有分隔。

@ 扩展为从1开始的所有参数,如果它出现在双引号中,则扩展为一个包含每个参数的单词,也就是说,”$@“和“$1c” “$2c” …是等价的。

# 扩展为位置的个数

? 扩展为最近在前台执行的命令的退出状态

- 连字符,扩展为当前的所有选项,,这些选项是启动时给定的,或者通过内部命令set打开的,或者由shell本身打开的,例如-i。

$ 扩展为当前shell的进程好,在子shell()中扩展为启动shell的进程号,而不是子shell的进程号。

! 扩展为最近在后台执行命令的退出状态。

0 扩展为shell或者shell脚本的名称,她是在shell 初始化时设置的,如果bash启动时带有包含命令的文件名参数。$0就被设置为带执行字符串后面的第一个参数。

_ 下划线,在shell启动时,设为启动的绝对路径,或者在执行环境或者在执行环境或者参数列表中所传递的带执行的shell脚本的绝对路径。

shell的扩展

1, 命令行被拆分成符号以后要进行扩展,扩展的方式由其中

  • 大括号扩展
  • 波浪号扩展
  • 参数和变量扩展
  • 命令替换
  • 算数扩展
  • 单词拆分
  • 文件名扩展

扩展过的顺序是,大括号扩展,波浪号扩展,参数和变量扩展,命令替换,算数扩展,单词拆分,文件名扩展。

2, 大括号扩展,大括号扩展是一种能够生成任意字符串的机制,它和文件名扩展是相似的。但是生成的文件名不一定存在,进行大括号扩展的模式在形式上又一个可选的浅醉,其后是一组用逗号分隔的字符串,后者是一个序列表达式,它们都在一对大括号之间,最后一个可行的后缀,前缀部分将放在大括号中的每个字符串钱买呢,后后缀放在每个结果的后面,,它们都是从左到右进行扩展的。

3, 波浪号扩展,如果一个单词以未被引用的~开头,那么其后的所有的字符知道第一个未被一用的斜杠都被看作波浪号的前缀,如果波浪号前缀里面的字符都没有被引用,则其中波浪号后面的所有字符九倍当成一个可能存在的登陆用户名,如果这个登陆名是个空字符串,波浪号就被替换成shell变量中的HOME的值,如果没有设置HOME的值,那么替换成执行该脚本的那个用户的主目录。

4, 参数和变量扩展,字符$引导参数扩展,命令替换和算数扩展。将要扩展的变量名或者符号放在大括号中,大括号虽然是可选的,但是却可以保护带扩展的变量,使得紧跟在大括号后面的部分名称不会被扩展,如果使用了大括号,则与这个匹配的结束半边的是第一个没有用反斜杠转移或者不属于引用字符串的},这个结束符不能嵌入在算数过站、命令替换、或者参数扩展中。

参数扩展的基本形式是:

${参数}

结果用参数值替换,如果参数是包含多个数位的位置参数,或者参数后面的字符不应该当成是整个名称的一部分,则大括号是必须的。

如果参数的第一个字符是个感叹号,就表示某个几杯的简介变量。bash使用后续变量的值作为新的变量的名称,然后扩展这个新的变量,并用它的值进行替换,而不是后续变量的值,这叫做简介扩展。

在很多其他语言中,可以使用$$A来表示以$A为名称的时间变量,而bash是不可以的。

5, 命令替换,命令替换用命令的输出取代命令本身,他的姓氏可以是下面的任意一种模式:

$(command)

command

进行扩展的时候,bash先执行命令,并把该命令的标准输出汇总的最后面的换行符删除,用接轨取代命令替换,这个时候中间的换行符不删除,但是它们可能在单词拆分是被删除,如果使用了旧式的反引号,反斜杠就保留其字面含义,除非他后面是$ ` \这三个符号,

6, 算数扩展,算数扩展可以对算数表达式求值病替换成求值的结果,他的表达是:

$(())

7, 进程替换

如果系统支持命名管道fifo,或者能够以/dev/fd目录的方式打开文件,也就支持进程替换,进程替换的语法形式是下面的任意一种。

<(command list)

>(command list)

在运行时,进程的命令列表的输入和输出与一个命名管道或者/dev/fd目录里的某个文件相关联,扩展的结果就是该文件的名称作为一个参数传递给当前命令

< >这两个符号之间不能有任何空格,否则就会被解释为重定向。

如果系统支持,进程替换就和参数及变量扩展,命令替换,还有算数扩展同时进行。

8, 单词拆分,在单词拆分的时候,shell 会扫描参数扩展命令替换和算数运算的结果,如果它们不是在双引号之间,shell会把$ifs 中的每个字符都当成分隔符,并把这些字符把其他扩展的结果拆分成单词,如果$ifs中没有设置,那么他就用默认值.

9, 文件名扩展,单词拆分了以后,bash会在每个单词的中搜索字符* ?[,除非打开了-f选项,如果找到了其中的一个则把这个单词当作一个模式。关于模式的匹配和正则类似。

重定向

在执行命令之前,shell很可能会用特殊的方式吧输入和输出重定向,重定向还可以用来在当前的shell执行环境中打开和关闭文件。

注意重定下的顺序是很重要的:

ls >目录列表 2>&1

这个命令的含义是把ls的标准输出重定向到目录列表中,并把标准错误输出重定向的标准输出中,这里的1表示“重定向的标准输出”,2表示“重定向的标准错误输出”。

如果数输入重定向< ,那么“标准输入重定向”为0。

命令的执行

  1. 简单的命令扩展
  2. 命令的搜索和执行
  3. 命令的执行的环境
  4. 环境
  5. 退出状态128+N
  6. 信号

shell的脚本

shell脚本是包含shell命令的文本文件,如果bash启动时把这个文件作为第一个不是选项的参数,而没有使用-c或者-s,bash会从该文件读取命令并执行,然后退出。这种方式会产生一个非交互运行的shell,这个shell会首先在目前目录中搜索该文件,如果没有,就继续搜索$PATH.

bash运行shell脚本的时候,会把特殊参数0设为该脚本的命令而不是shell的命令,如果还有其他的参数,就把其余的参数当作位置参数,如果没有其他的参数,就不设置位置参数。

文件名 参数

这个就相当于:

bash 文件名 参数

这个子shell会重新初始化,效果就等同于启动了一个新的shell 来执行该脚本,所不同的是,父进程存储的个命令的饿路径将在子进程中沿用。

大多数unix版本在执行命令时都包含这样的机制,如果脚本的第一行由#!这两个字符开头,则这一行奇遇的内容就制定该脚本的解释器,因此可以指定bash,awk,perl或者其他解释器并在脚本的这行后面用这些语言来写。

在脚本的第一行,解释器名称后面可以包含一个单一的选线,作为该解释器的参数,后面时脚本的名称在后面时奇遇的参数,再不能处理这些参数的操作系统管理面bash会处理它们,注意游戏啊老的unix版本规定,解释器名称和其参数总长不能超过32个字符

bash脚本的开头通常是#!/bin/bash ,嘉定bash安装在/bin下,因为这样能够保证bash来解释,既是他在其他的shell下执行。

第四章 shell的内部命令

bsh(bash的前身)的内部命令

  1. :冒号,除了扩展参数和执行重定向外不做任何操作,返回零。
  2. .点号,在当前的shell 环境中从文件名中读取并执行命令,如果文件名不包含斜杠则使用PATH变量去搜索文件,如果bash不是posix下运行的,则在$PATH中找不到,就会在当前目录中搜索,他就成为执行文件名时的文职参数,否则,位置参数不会被改变,赶回状态时最后一个被执行的命令的退出状态,如果没有命令被执行,那么返回零。如果文件没有找到,或者不能读取,返回状态就是非零。这个命令和source是等价的。
  3. break,用于从for while until select 循环中退出的,如果给定n就退出外围第n层循环,n必须大于等于1,返回状态是0。
  4. cd,切换目录。
  5. continue,继续执行外围的for while until select的下一次循环,与break类似并对应。
  6. eval,把参数连在一起形成一个命令,然后读取行执行这个命令,他的退出状态就是eval的退出状态,如果没有参数,则eval退出状态为零。
  7. exec,如果指定了命令,他就会取代当前的shell 而不是创建新的进程,如果给定了“-l”(login)选项,shell就会在传给命令的第零个参数前加上一个横杠,这就是登陆程序所做的。-c(clear)选项使得命令在空的环境中执行,如果指定-a(alter),shell会把名称作为第零个参数传给命令。
  8. exit,说出shell 并在shell的父进程中所返回态n,如果省略了n,则退出状态是最后被执行的命令的退出状态,exit陷阱是在shell结束前执行。
  9. export,把每个命令都传到子进程环境里,如果给定了-f(function),则命令就是shell函数,否则它是shell变量-n(no),表示不在吧名称导出的子进程里,如果没有给定名称,或者给定了-p(pretty-print)则显示已经导出的名称列表。-p选项能够把输出格式化成可以重❤新作为输入的形式,如果名称后面是“=值”,那么这个值就会被赋值。
  10. getopts,shell脚本用来分析位置参数。
  11. hash,记住参数名称所制定的命令的完整路径。
  12. pwd,显示当前路径。
  13. readonly,把每个名称标志为只读,后续的语句就不可以更改这些名称的值。
  14. shift,把位置参数向左移动n个位置,$1+n至$#被重命名为$1至$#-n,n必须是小于或者等于$#的非负数。如果n是小于零或者比$#大,这时返回0。
  15. test,和[]
  16. times,打印出shell及其子进程所使用的用户时间和系统时间,返回状态是0。
  17. trap
  18. umask
  19. unset,删除哥哥制定的变量或者函数的名称,如果没有指定选项,或者指定了-v选项(variable)则每个名称都是shell变量,如果指定了-f(function)则每个名称都是指shell函数。这个函数的定义将被删除,返回状态零。

bash的内部命令

  1. alias
  2. bind
  3. builtin,运行一个命令,把参数传给他,并返回他的退出的状态,这可以用来定义一个与shell内部命令同名的函数,并在函数内部保留这个命令的功能,如果shell命令不是有效的shell内部命令,则返回状态是0.
  4. caller,返回当前活动的子程序,即shell 函数或者内部命令.,或者source执行的shell 脚本。如果后面没有接上表达式,那么就显示当前程序调用的行号和源文件。
  5. command,把参数传给命令并执行这个命令,而忽略与之同名的shell函数。
  6. declare,声明变量并设置属性。
  7. echo
  8. enable,启用或禁止内部命令。
  9. help
  10. let
  11. local
  12. logout
  13. mapfile,从标准输入读取文本并存入数组变量数组中
  14. printf
  15. read读取键盘输入
  16. readarray读取键盘输入数组
  17. source,是点命令的同义
  18. type返回命令的类型
  19. typeset与declare一样
  20. unalias取消命令别名,如果给定了-a参数,则删除所有命令别名。

改变shell的行为

  • set–内部命令set
  • shopt–内部命令shopt

特殊的内部命令

由于历史原因,posix标准把几个内部命令归入到特殊类别,当bash在posix模式下运行时,这些特殊的内部命令和其他的内部命令不用的方面有三个:

  1. 在搜索时特殊命令限于shell函数
  2. 如果特殊命令返回错误状态,则非交互运行的shell 就会退出
  3. 命令前面的复制语句在命令结束后仍然对环境有影响。当bash不是在posix模式下运行的时候,和其他的内部函数并没有什么不同。
  4. break;:;.;continue;eval;exec;exit;export;readonly;return;set;shift;trap;unset

附录 贴图

在最后贴几张图,这些图用来以后用到的时候能够快速查找。


相似文章

内容导航