[Linux] Bourne Again Shell

1. Shell基础


1.1 初始化文件

当启动shell,它将运行初始化文件,具体文件取决于该shell是一个登录shell还是一个非登录shell的交互式shell(如通过命令bash),又或者是一个非交互式shell(用来执行shell脚本)。

登录shell或带"--login"选项的shell包含如下初始化文件:

1) /etc/profile: shell首先执行/etc/profile中的命令,通过设置这个文件,超级用户可以为全系统内的所有bash用户建立默认特征。

2) ~/.bash_profile、~/.bash_login、~/.profile: 然后shell依次查找这三个文件,并执行它找到的首个文件中的命令。可以将命令放置在这些文件中以覆盖掉/etc/profile文件中的默认设置。

3) ~/.bash_logout: 当用户注销时,bash执行文件~/.bash_logout中的命令。这件文件中包含了退出会话时常用的命令,如删除临时文件等。

在交互式非登录shell中并不执行前面提到的初始化文件中的命令。然而,交互式非登录shell从登录shell继承了由这些初始化文件设置的shell变量。交互式非登录shell包含如下初始化文件:

1) ~/etc/bashrc: 尽管不是通过bash直接调用,许多~/.bashrc文件还是调用/etc/bashrc,这使得超级用户可以为全系统内的非登录shell建立默认特性。

2) .bashrc: 交互式非登录shell执行~/.bashrc文件中的命令,而登录shell的初始化文件通常会运行这个文件。

非交互式shell并不执行前面描述的初始化文件中的命令。然而,这些shell从登录shell那里继承了由这些初始化文件设置的shell变量。非交互式shell查找环境变量BASH_ENV(当作为sh调用时为ENV),并执行由该变量命名的文件中的命令。

在编辑如.bashrc这类的初始化文件之后,没月收入要注销再次登录,可以使用内置命令" . "或者source命令,如". ~/.bashrc",在命令行上," . "后面必须有一个空格。这两个命令类似于运行一个shell脚本,但这些命令将该脚本作为当前进程的一部分运行。因此,当使用" . "或者source运行脚本的时候,在脚本中改变的变量也将影响到运行该脚本的shell。


1.2 符号命令

Bourne Again Shell以多种方式使用符号(、)、[、]和$。

内置的符号命令
符号
命令
()子shell。
$()命令替换。
(())算术表达式计算。
$(())算术扩展。
[]test命令。
[[]]条件表达式,类似于[],但添加了字符串比较。


1.3 重定向标准错误输出

除了标准输出之外,命令还可以将输出发送到标准错误输出。 与处理标准输出一样,默认情况下,shell将命令的标准错误输出发送到屏幕上。除非重定向标准输出和标准错误输出中的某一个,否则不能区分命令的输出到底是标准输出还是标准错误输出。

文件描述符是程序发送输出和获取输入的地方。当执行一个程序时,运行该程序的进程打开了3个文件描述符,分别是:0(标准输入)、1(标准输出)和2(标准错误输出)。重定向输出符号">"是"1>"的简写,它通知shell重定向标准输出。类似的,"<"是"0<"的简写,表示重定向标准输入。符号"2>"将重定向标准错误输出,如下所示:

191518299.jpg

如果是"2>&1",则是声明文件描述符2为文件描述符1的副本,这样做的结果是,标准输出和标准错误输出均被重定向到new1中,如下所示:

191818354.jpg

在这里,"1>new1"放在了"2>&1"前面,如果将顺序颠倒,在标准输出重定向到文件hold之前,标准错误输出就已经复制了标准输出的一个副本。这样,就只有标准输出被重定向到文件new1。


1.4 简单的shell脚本

shell脚本是包含shell可执行命令的文件。shell脚本中的命令可以是用户在shell提示符后面输入的任何命令,此外,shell脚本还可以用控制流命令(控制结构)。shell脚本是逐条解释并执行脚本中的命令的。

在shell脚本文件的第1行可以放置一行特殊的字符串,告诉操作系统使用哪个shell来执行这个文件。如果脚本的前两个字符是"#!",那么系统将这两个字符后面的那些字符作为用来执行该脚本的命令解释器的绝对路径名。它可以是任何程序的路径名,而并不仅仅是shell,如下所示:

193529888.jpg

如果"#"号在脚本的第1行出现并且其后没有感叹号,或者脚本中其他任意位置上出现了"#",那么shell将其视为注释的开始,shell将忽略"#"号到该行末之间的所有内容。

用户在命令行上输入一条命令后,shell将fork一个新的进程,以创建当前shell进程的一个副本(子shell)。这个新进程将试图exec该命令。与fork一样,exec例程也是由操作系统执行。如果该命令是一个二进制可执行程序,那么exec执行成功,系统使用该可执行程序覆盖新创建的子shell,如果这个命令是一个shell脚本,exec执行失败。


1.5 命令分隔和命令分组

换行符是一个独特的命令分隔符,因为它将启动该字符前面的命令执行。分号";"也是一种命令分隔符,但是它并不立即启动命令执行,也不改变命令的任何功能,通过输入RETURN键启动这些命令,如下所示:

194827789.jpg

当输入一个比较长的命令行时,光标已经到达屏幕右端,这时候可以使用反斜杠字符"\"在下一行继续这条命令。反斜杠引用了换行符,这样shell就不会将这个换行符作为一个命令终结符对待,如下所示:

195729386.jpg

管道符号" | "和后台任务符号"&"也是命令分隔符,它们并不开始执行命令而是在某些方面改变命令的功能。管道符号改变标准输入的源或者是标准输出的目的地。而后台任务符号使shell在后台执行该任务,这样用户就可以立即得到一个提示符并继续其他工作。


1.6 作业控制

一个作业是一个命令流水线。无论何时向Linux输入一个命令,其实都是在运行一个简单的作业。可以在单个命令行里创建多个包含数条命令的作业,如下所示:

202921251.jpg

在第1个"&"符号之前的命令行部分是一个作业,第2个作业是grep的单个进程。两个作业均被结尾的"&"符号放置到后台执行。使用作业控制可以将命令从前台移到后台,或是从后台移到前台,还可以临时停止命令。

内置命令jobs将列出所有后台作业,如下所示:

203819630.jpg

shell为那些运行在后台的命令指派了作业编号,当作业完成后,作业编号就被丢弃,这样作业编号就可以重用。在开始一个后台作业或者将一个作业置于后台时,shell为该作业分配一个作业编号,该编号大于当前正在使用中的最大编号。

为了将一个作业放置到前台执行,可以将其作业编号告诉内置命令fg,或在百分号后面跟着该作业编号作为一条命令,如下所示:

204420824.jpg

也可以通过百分号后跟字符串来引用作业,其中的字符串能够唯一标识该作业的命令行起始部分。例如"fg %tail"。

在将一个前台作业转移到后台搪行之前,必须首先按下挂起键(通常是CONTROL+Z),然后可以使用内置命令bg将该任务放置到后台运行,如下所示:

205258504.jpg


1.7 目录栈

内置命令dirs显示目录栈的内容,如果当目录栈是空的时候调用dirs,将显示工作目录的名字,如下所示:

205801965.jpg

使用内置命令pushd可以改变目录,同时添加一个新的目录到栈顶,除改变目录外,内置命令pushd还将显示栈的内容,如下所示:

205959448.jpg

当不带参数使用pushd时,pushd交换栈顶最上面的两个目录,并将新的栈顶目录作为新的工作目录。为了访问目录栈中的其他目录,可以在调用pushd时,带上一个以加号开头的数字作为参数。栈中目录的编号从栈顶开始算起,栈顶目录的编号是0。

使用内置命令popd可将目录从栈中移出,不带参数的popd将栈顶目录从栈中移出,并将工作目录改变到新的栈顶目录,如下所示:

210507132.jpg

为了移出除栈顶之外的其他目录,可以使用一个加号开头的数字作为参数传递给popd。


2. 参数和变量


2.1 变量

在shell中,shell参数与用户可访问的某个值相关。参数的名字由字母、数字和下划线组成,常称为shell变量,或简称变量。变量名必须以字母或下划线开头,而不能是数字。

用户命名并赋值的变量称为用户创建的变量。用户可以在任何时候修改用户创建的变量的值,或者将其设置为只读,还可以将变量变成全局变量。全局变量可以被任何shell和从最初shell创建的其他程序访问,语法如下:

VARIABLE=value

等号两边没有空格符。Borune Again Shell允许在命令行放置变量赋值语句,这些赋值语句只是该命令shell的局部行为,它们只适用于该命令。

关键字shell变量具有特殊意义,它们的名字一般比较短。当用户启动shell时,shell将从环境中继承几个关键字变量,比如HOME和PATH。其中,HOME表示用户的主目录,PATH决定了shell在哪些目录下搜索命令,同时还决定搜索顺序。

有一组参数的名字并不像变量名,其中大多数参数名字都只由一个字符组成,比如"1"、"?"和"#"等。像其他所有变量一样,在引用它们时一般在其名字前面加上美元符号"$"。

无论如何,用户输入的一行命令中的每个参数都将成为位置参数的值。用户使用位置参数可以访问命令行参数,内置命令set可以用来对位置参数赋值。


2.2 用户变量

只有在变量名字前加上美元符号"$"时,shell才会将变量的值代入,如下所示:

201400147.jpg

如果在开头的"$"用单引号引用起来,就可以阻止shell代入变量的值,双引号不能阻止代入,而单引号和反斜杠都可以阻止代入。

当引用一个包含未被引号引起来的特殊字符的变量时,所有shell均被这些字符解释为特殊字符,如下所示:

201949393.jpg

除非变量被删除,否则它将伴随着着创建它的shell一直存在,使用值null可以将变量的值删除,但是不删除该变量。可以使用内置命令unset删除变量,如下所示:

202135160.jpg


2.3 变量属性

可以使用内置命令readonly确保某个变量的值不被改变。在将变量声明为只读之前,必须为该变量赋一个值,在声明之后,就不能改变它的值了。如果尝试删除或改变只读变量的值,shell将显示一条错误消息,如下所示:

202525856.jpg

内置命令declare和typeset(这是同一个命令的两个名字)可用来设置shell变量的属性和值。

变量属性
属性
含义
-a声明一个数组变量。
-f声明一个函数名变量。
-i声明一个整型变量。
-r声明变量为只读,也可用readonly。
-x输出变量,也可用export。

如下所示:

203152618.jpg

将该命令行中的连字符换成字符"+",可以为变量删除某个属性。但是,用户不能删除只读属性。

如果不带任何参数或者选项,那么内置命令declare将列出所有shell变量,不带任何参数运行set也会得到同样的结果。如果内置命令declare带有选项,但是没有变量名作为参数,那么该命令将列出所有具有指定属性集合的shell变量,如下所示:

203756478.jpg

默认情况下,变量的值作为字符串存放,当对某个字符串变量进行算术运算的时候,shell将该变量转换为一个数字,计算完之后,再将其转换为字符串。


2.4 关键字变量

默认情况下,用户主目录就是用户登录之后的工作目录。当用户登录之后,shell继承了用户的主目录的路径名并将其赋给变量HOME,如下所示:

203343798.jpg

如果使用简单的文件名作为命令,shell将搜索某些目录,以查找用户想要执行的程序。shell变量PATH则控制着这些搜索路径,每个目录之间必须使用冒号隔开,如下所示:

203705420.jpg

变量MAIL包含了保存用户邮件文件的路径名。用户邮件文件就是该用户的mailbox,通常是"/var/spool/mail/NAME",其中NAME是用户的登录名。如果设置了MAIL但没有设置MAILPATH,那么当邮件到达MAIL指定的文件时,shell将提醒用户。变量MAILPATH包了一个用冒号隔开的文件名列表,如果设置了这个变量,那么当这个列表中任何一个文件发生变化时,shell都将提醒用户。变量MAILCHECK规定了shell以多大的频度检查新邮件,如下所示:

204531220.jpg

默认的Bourne Again Shell提示符是一个美元符号"$",当以root身份运行bash,提示符是"#"号。变量PS1保存了shell用来提示用户输入命令的提示符串。当用户修改PS1或者prompt的值时,用户的提示符就会发生改变,如下所示:

204935167.jpg

shell用PS2存放辅助提示符,例如命令未结束在第2行给出的即是辅助提示符,如下所示:

205349926.jpg

PS3和PS4分别保存了用于select控制结构的菜单提示符,和bash调试提示符,如下所示:

205540184.jpg

变量IFS指定了在命令行中用来分隔参数的字符,默认值为空格符、制表符和换行符。无论IFS的值是什么,用户一定可以使用空格符或者制表符分隔命令行中的不同参数,如下所示:

210607427.jpg

变量CDPATH使用户可以用一个简单的文件名作为参数传递给内置命令cd,就将工作目录改变到某个目录,而这个目录并不是工作目录的子目录,如下所示:

211154979.jpg

bash关键字变量
变量
BASH_ENV用于非交互式sehll的初始化文件的路径名。
CDPATHcd命令的搜索路径。
COLUMNSselect命令使用的显示宽度。
FCEDITfc默认使用的编辑器名称。
HISTFILE保存历史列表文件的路径名。
HISTFILESIZE保存在HISTFILE中的最大项数。
HISTSIZE保存在历史列表中的最大项数。
HOME用户主目录的路径名。
IFS内部字段分隔符,用于分词。
INPUTRCReadline初始化文件的路径名。
LANG没有用LC_*变量特别设置时的区域目录。
LC_*指定的区域目录的一组变量。
LINESselect命令使用的显示高度。
MAIL保存用户邮件的文件的路径名。
MAILCHECK以秒为单位定义了bash检查邮件的频度。
MAILPATHbash检查邮件文件的路径名列表。
PATHbash查找命令的路径名列表。
PROMPT_COMMANDbash在显示主提示符之前要执行的命令。
PS1主提示符。
PS2辅助提示符。
PS3select发出的提示符。
PS4bash调试符。
REPLY保存read接受的行。


3. 进程


3.1 进程结构

进程结构类似文件结构,也是一个层次式机构,有父进程、子进程,甚至根进程。父进程可以创建子进程,子进程又可以创建其他进程,创建一个新的进程的操作系统调用叫做fork。

当系统启动时,Linux开始执行,它首先启动init进程。这个进程称为自发进程,其PID编号为1,它是系统进程和所有用户进程的祖先。当系统处于多用户模式时,init运行getty或者mingetty进程,这些进程将在终端和虚拟控制台上显示"login:"提示符。当某个用户响应该提示符并输入RETURN时,getty将控制权转交给名为login的程序。在用户登录后,login进程将成为该用户的shell进程。

在每个进程开始的时候,Linux为其分配一个唯一的PID编号。在进程存在期间,它将一直持有这个PID编号。当用户创建一个新的进程,新进程的PID编号会不同于它的父进程(PPID代表父进程的编号),如下所示:

214541670.jpg


3.2 执行命令

当用户向shell中输入一行命令里,shell同时创建一个子进程来执行这条命令。当子进程执行该命令期间,父进程转入睡眠状态,子进程命令执行完毕之后,它将通过其退出状态通知其父进程自己执行成功或失败,然后消失,父进程被唤醒,提示用户输入另一个命令。

当运行后台进程时,shell创建一个子进程,但是shell并不进入睡眠状态,也不用等待子进程去运行完毕。执行shell的父进程报告作业编号和子进程的PID编号,并提示输入另一个命令,子进程在后台执行,独立于它的父进程。

shell运行内置命令时,并不需要创建新的进程。在给定进程内,比如用户的登录shell或者子shell,用户可声明、初始化、读取和改变变量。然而在默认情况下,变量只适用于进程本地范围,在进程创建一个子进程时,父进程并不会将变量的值传递给它的子进程。使用bash下的export内置命令可使这些变量也可以被子进程访问(此时为全局变量)。


4. 命令历史


4.1 历史机制

历史机制维护了用户最近发出的命令行的列表,用户可以使用这套机制来执行前面命令的变种,并重用它们的参数。历史列表还记录用户使用过程,当发生错误又不确定出错位置时,这套机制非常有用。

内置命令history显示了历史列表的内容,如下所示:

201631954.jpg

HISTSIZE变量的值决定了在某次会话期间历史列表中保存的事件的数目,该值的范围正常情况下是100~1000。当从shell中退出时,最近执行的命令将保存在HISTFILE变量执行的文件中(默认是~/.bash_history)。下一次启动shell时,将使用这个文件来初始化历史列表。变量HISTFILESIZE的值决定了保存在HISTFILE中的历史行数,如下所示:

202217770.jpg

Bourne Again shell连续地为每行命令指派了一个事件编号。如果在PS1中包含"\!",则可以将事件编号作为bash提示符的一部分显示出来。


4.2 重新执行

可以重新执行历史列表中的任何事件。可以采用3种方式来浏览、修改和重新执行前面已执行的命令,即使用内置命令fc、使用感叹号命令以及Readline库,该库使用单行类vi编辑器和类emacs编辑器来编辑和执行事件。

内置命令fc可用来显示历史列表内容,并可以编辑和重新执行前面的命令。带上"-l"选项调用fc时,fc显示历史列表中的命令,如果不带任何参数,那么"fc -l"将在编号列表中显示最近的16个命令,如下所示:

202951140.jpg

内置命令fc的"-l"选项可以带有零个、一个或者两个参数,这些参数指定了历史列表中将要显示的部分,格式如下:

fc -l [first [last]]

内置命令fc列出了以匹配first参数的最近事件为开头的命令。这个参数可是一个事件编号,或者是命令行开头的几个字符,还可以是一个负数,表示当前fc命令之前的第n条命令。如果给出last参数,fc命令将显示从匹配first的最近事件到匹配last的最近事件的命令,如下所示:

203420341.jpg

可用fc编辑和重新执行前面的命令,格式如下:

fc [-e editor] [first [last]]

当带有"-e"选项调用fc时,如果后面还带有某个编辑器的名字,那么fc将调用该编辑器,并将事件放在工作缓冲区中,如下所示:

203938297.jpg

如果调用fc时,带上"-s"选项,那么它将跳过编辑阶段,并重新执行该命令,如下所示:

204432488.jpg

bash中可以使用感叹号来引用事件,"!!"可以重新执行前一个事件,如下所示:

204641471.jpg

可以使用事件的绝对编号、相对编号或者事件中包含的文本来引用该事件,所以有事件引用均以感叹号"!"开头。

事件标志符
标志符
含义
!立即开始某个历史事件。
!!前一个命令。
!n历史列表中编号为n的命令。
!-n往前第n条命令。
!string最近以string开头的命令行。
!?string[?]包含string的最近命令行,最后的"?"是可选的。
!#当前命令(目前输入的部分)。
!{event}event为事件标志符,比如"!{-3}3"表示后面跟着3的第3个最近命令。


5. 别名和函数


5.1  别名

别名是一种名称,shell将其翻译成另一个名字或者命令,这些别名通常放在初始化文件"~/.bashrc"中,这样在交互式子shell中就可以使用这些别名,内置命令alias的语法如下:

alias [name[=value]]

在bash语法中,等号左右没有空格符。如果values中包含空格符或者制表符,必须将引号之间的value括起来。别名并不替换自己,这避免了在处理别名时可能存在的无限递归。

要查看某个特殊名称的别名,可以在alias命令后面加上该名称,用unalias内置命令可以删除别名,如下所示:

203124309.jpg

使用命令alias可以列出当前所有别名的列表,如下所示:

201805773.jpg

如果value用双引号引起来,那么当创建该别名时,value中的任何变量都将扩展。如果将value用单引号引起来,在使用该别名之前变量都不会被扩展。


5.2 函数

shell函数类拟于shell脚本,里面存放了一系列可在后期执行的命令。然而,因为函数存放在内存中,因此比访问脚本的速度快得多。shell还对函数预处理,因此其启动速度也要比脚本快得多。

shell函数的声明可以放在"~/.bash_profile"文件中,或者放在使用该函数的脚本中,又或者直接放在命令行中。可以使用内置命令unset删除函数。声明一个shell函数的语法如下:

[function] function-name() {
  commands
}

其中,关键字function是可选的,function-name为调用该函数使用的函数名,commands由调用该函数时将要执行的命令列表构成。commands可是在shell脚本中包含的任意内容,包括调用其他函数。

如下示例中,给出了如何创建及调用一个函数:

204839783.jpg


6. 特性和选项


6.1 命令行选项

有两种类型的命令行选项:短选项和长选项。短选项由一个连字符后跟着一个字母构成,而长选项由两个连字符后面跟着多个字母构成。在bash中,同一个命令行上的长选项必须放在短选项之前。

常用命令行选项
选项
解释语法
help显示用法信息。--help
no edit阻止用户在交互式shell中使用Readline库编辑命令行。--noediting
no profile阻止读取初始化文件"~/.bash_login"与"xxx profile"。--noprofile
no rc阻止读取初始化文件"~/.bashrc"。--norc
POSIX在POSIX模式下运行bash。--posix
version显示bash版本信息并退出。--version
login使bash像登录shell一样运行。-l
shopt带上shopt选项opt运行shell,-O设置选项,+O删除。[+-]O[opt]
end of options在命令行中发信号表示选项结束,后面的字将作为参数。--


6.2 shell特性

可以通过打开或者关闭Bourne Again Shell的功能的方式来控制它的行为。内置命令set控制着一组功能,而内置命令shopt控制另一组命令。

通过使用"-o"或者"+o"选项,内置命令set可开启、关闭并列出某些bash特性,如下所示:

202921485.jpg

如果命令"set -o"不带任何选项,那么它将列出所有由set控制的每一项特性及其状态。不带选项的"set +o"以一种特殊的形式列出了这些特性。

内置命令shopt开启、关闭和列出那些控制着shell行为的某些bash特性("-s"表示设置),如下所示:

203230865.jpg

命令shopt不带任何选项或者参数时,将列出由shopt控制的那些特性及其状态。命令"shopt -s"不带参数时,将列出那些由shopt控制并设置为开启的特性。命令"shopt -u"将列出那些被取消或者关闭的bash特性。


7. 处理命令行


7.1 历史扩展

历史扩展是bash将历史命令转换到可执行命令行的过程。例如"!!"将改变命令行,使其内容与前一个命令行一致。默认情况下,对于交互式shell,历史扩展特性是开启的,使用"set +o histexpand"可将其关闭。


7.2 别名替换

别名将一个简单命令的第1个字替换为一个字符串。默认情况下,交互式shell的别名特性是开启的,在非交互式shell中则是关闭的。使用命令"shopt -u expand_aliases"可将别名关闭。


7.3 花括号扩展

花括号扩展在交互式shell和非交互式shell中都是默认开启的,可以使用命令"set +o braceexpand"关闭它。shell还使用花括号来分隔变量名。

shell将花括号中以逗号分隔开的字符串扩展成一个以空格符隔开的字符串列表,花括号中字符串的从左至右的顺序在扩展过程中仍然会保持。花括号扩展可以嵌套,如下所示:

204807296.jpg


7.4 代字符扩展

代字符"~"出现在命令行中某字的起始处时,它就属于一个特殊字符。当bash在这个位置上看到代字符时,它将后面的字符串作为一个可能的登录名。如果这个可能的登录名是空的,那么shell将用HOME变量的值取代这个代字符。如果后面的字符串构成了一个合法的登录名,那么shell将用与该登录名相对应的主目录路径名来替换这个代字符和名字,如下所示:

205417905.jpg


7.5 变量扩展

在命令行中,后面没有开放的圆括号的美元符号"$"将引入参数或者变量扩展。参数包括命令行,或者位置参数和特殊参数。


7.6 算术扩展

shell计算算术表达式的值并用该值替换表达式,这就是算术扩展,语法如下:

$((expression))

expression的构成规则与C编程语言的规则类似人。所有标准C算术操作符都可用。bash中的算术使用整数进行计算,然而,除非使用整数类型的变量或者真正的整娄和,shell必须将字符串值转换到整数,以便于整数计算。expression中的变量名称不需要加前面加上美元符号"$",如下所示:

210546272.jpg


7.7 命令替换

命令替换指的是用命令的输出来代替该命令,如果要引用变量,shell不需要在变量名前加上美元符号"$",语法如下:

$(command)

shell在子shell执行command,然后用command的标准输出取代commadn,连同左右两边的标点,如下所示:

211012927.jpg


7.8 路径名扩展

路径名扩展又称为文件名生成或者文件名补全,它表示解释模糊文件引用以及替换合适文件名的列表的过程。当shell遇到*、?、[或者]中的任何一个时,它将执行这项功能,如下所示:

211611672.jpg



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值