Bash编程

一、shell简介

  Shell 语言(通常指的是命令行解释器脚本语言)是用于与操作系统交互的脚本语言,通常通过命令行界面(CLI)与操作系统进行交互。Shell 语言通常用于自动化任务、系统管理、程序控制和文件处理等方面。

  Shell 语言有多种实现,最常见的包括 Unix 和类 Unix 系统(如 Linux 和 macOS)中的 Bash,以及 Windows 系统中的 PowerShell。Linux提供的Shell解析器99%以上都是bash(贝尔实验室升级版)。
在这里插入图片描述

1. 1 Shell 的作用

  Shell 脚本允许用户通过编写脚本来批量执行系统命令、程序或操作,适合用于自动化管理、定时任务、文件操作、网络管理等。其主要功能有三点:

  • 命令行交互:Shell 充当用户与操作系统之间的接口,用户通过 Shell 输入命令与操作系统进行交互。
  • 脚本执行:Shell 脚本是一个文本文件,其中包含一系列命令和控制结构,用户可以通过 Shell 执行这些命令来完成特定任务,如系统管理、自动化操作等。
  • 自动化任务:Shell 脚本常用于自动化和调度任务,比如备份、日志管理、文件整理等。

1.2 常见 Shell 类型

  • Bash (Bourne Again Shell)
    • 最常见的 Unix 和 Linux 系统的 Shell,通常是默认的命令行解释器。
    • 支持强大的功能,如变量、函数、控制流(if/else、while、for 等)、通配符、文件操作等。
  • Zsh (Z Shell)
    • 功能强大,兼容性好,具有很多 Bash 和其他 shell 所没有的特性,比如更灵活的自动补全、主题和插件支持等。
  • Fish (Friendly Interactive Shell)
    • 现代化的 Shell,主要面向交互使用,具有强大的自动补全功能、语法高亮、易用性等特点。
  • PowerShell
    • 主要用于 Windows 系统,但现在也支持 Linux 和 macOS。
    • 与传统的 Shell 不同,PowerShell 是一个面向对象的命令行工具,可以处理对象而不仅仅是文本。

  shell脚本以#!/bin/bash开头(超链接文件,指定解析器,即真正的解释器文件#!bin/sh),可以使用whereis bash来查看bash的位置,或者是输入$ echo $SHELL命令:

[laoxiao@hadoop102 bin]$ echo $SHELL
/bin/bash

shell脚本的常用执行方式:

  1. bash或sh+脚本的相对路径或绝对路径
[laoxiao@hadoop101 datas]$ sh helloworld.sh
Helloworld
  1. 赋予脚本可执行权限,然后直接输路径
[laoxiao@hadoop101 datas]$ chmod 777 helloworld.sh(或chmod +x helloworld.sh )
[laoxiao@hadoop101 datas]$ ./helloworld.sh
Helloworld

  注意:第一种执行方法,本质是bash解析器帮你执行脚本,所以脚本本身不需要执行权限。第二种执行方法,本质是脚本需要自己执行,所以需要执行权限。

二、 Shell中的变量

2.1 系统变量

  1. 系统(环境)变量 :作用域是整个操作系统或者整个用户,注意:临时的用户环境变量只作用到当前bash及它的子bash
[root@localhost shell]vim /etc/profile #(此处编辑系统变量的文件)
[root@localhost shell]vim ~/.bashrc    #(此处编辑的是当前用户的环境变量,波浪线表示当前用户的家目录)
  1. 添加系统变量:export 变量名=值(例如TEST_A=100)。如果是在 /etc/profile或者~/.bashrc中添加,需要source /etc/profile使编辑后的文件生效。此时变量添加完成。
    如果是直接命令界面输入export 变量名=值,是添加临时用户变量,系统重启/切换用户之后就没有了
#打印系统、当前路径、shell、用户等环境变量。
[root@localhost shell]echo $HOME $PWD $SHELL $USER

/root /home/shell /bin/bash root
  • printenv:打印出root用户共享的系统环境变量和用户环境变量($表示变量的引用,其中每个变量都可以用echo打印出来)。
  • set:显示当前Shell中所有变量

2.2 普通变量

普通变量: 作用域是当前shell(当前的解释器)

  • 定义变量:变量=值
  • 撤销变量:unset 变量
  • 声明静态变量(只读):readonly变量,注意:不能unset
  • 在bash中,命令行直接写的变量默认类型都是字符串类型(临时用户变量)
  • 变量的值如果有空格,需要使用双引号或单引号括起来。在bash中空格表示结束,后面的字符会被bash认为是命令
[laoxiao@hadoop102 ~]$ D=I love banzhang
-bash: world: command not found #(其实就是D=I,结束了,然后有个单独的love,被认为是命令,找不到)
[laoxiao@hadoop102 ~]$ D="I love banzhang"
[laoxiao@hadoop102 ~]$ echo $D
I love banzhang
  • 变量提升为全局环境变量,可供其他Shell程序使用。
    • 新建一个bash脚本,输入echo $D之后执行,发现打印输出变量D的值。因为普通变量: 作用域是当前shell。
    • 重点:用户登录之后会开启一个解释器bash(父bash),当执行某一个sh脚本文件时,其实是重新启动一个bash(二号)去执行脚本。二号bash是父bash的子bash,是父bash环境下启动的bash。
    • 所以父bash定义的普通变量,子bash拿不到。但是子bash可以拿到当前用户的系统变量和环境变量,比如上面的TEST_A。因为其作用域是整个操作系统或者整个用户。
    • 临时的用户环境变量只作用到当前bash及它的子bash(重启或者重开就没有了)。
[root@localhost shell]ps aux|grep bash
root       1484  0.0  0.0 115756  2280 pts/0    Ss   18:39   0:00 -bash#这个就是父bash,一登录linux就启动,bash前面有个-
root       1697  0.0  0.0 112824   984 pts/0    S+   21:19   0:00 grep --color=auto bash
[root@localhost shell] export TEST_ B=20
[root@localhost shell] echo $TEST_ B#(重开一个窗口登录linux,虽然是同一个用户,但是也打印不出TEST_ B)

2.3 特殊变量

2.3.1 特殊变量:$n(类似形参)
  • $n (功能描述:n为数字,$0代表该脚本名称,$1-$9代表第一到第九个参数,十以上的参数需要用大括号包含,如${10})
  • $n可以直接传入参数,中间空格隔开
[root@localhost shell]vim canshu.sh

1 #!bin/bash
2 
3 echo $1 $2 $3

[root@localhost shell]bash canshu.sh 11 22 睡觉
11 22 睡觉
2.3.2 特殊变量:$#

$# (功能描述:获取所有输入参数个数,常用于循环)

[laoxiao@hadoop101 datas]$ vim parameter.sh

#!/bin/bash
echo $0  $1  $2 $3
echo $#

[laoxiao@hadoop101 datas]$ bash parameter.sh cls xz

parameter.sh 12 合适#因为$0为脚本名,三个参数,只传入两个
2
2.3.3 特殊变量:$* 和$@
  • $* :这个变量代表命令行中所有的参数,$*把所有的参数看成一个整体)
  • $@ :功能描述:这个变量也代表命令行中所有的参数,不过$@把每个参数区分对待)

例如echo $*、echo $@都可以打印出所有传入的参数。二者区别:(有“”时输出才有区别

  • $*和$@都表示传递给函数或脚本的所有参数,不被双引号“”包含时,都以$1 $2 …$n的形式输出所有参数。
  • 当它们被双引号“”包含时,“$*”会将所有的参数作为一个整体,以“$1 $2 …$n”的形式输出所有参数;“$@”会将各个参数分开,以“$1” “$2”…”$n”的形式输出所有参数。
[root@192 shell]# vim for.sh

#!/bin/bash
# 先测试$*
for i in "$*";do
	echo "传入脚本的参数是:$i"  #$*中的所有参数看成是一个整体,所以这个for循环只会循环一次
done

echo "---------------------"

# 测试$@
for j in "$@";do
	echo "传入脚本的参数是:$j"  #$@中的每个参数都看成是独立的,所以“$@”中有几个参数,就会循环几次
done

[root@192 shell]# bash for.sh 1 2 3
传入脚本的参数是:1 2 3
---------------------
传入脚本的参数是:1
传入脚本的参数是:2
传入脚本的参数是:3
2.3.4 特殊变量:$?
  • $? :最后一次执行命令的返回状态。如果值为0,证明上一个命令正确执行;如果非0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了。
  • 每一个bash命令被执行,都有执行结果和执行状态。$?只有一个,只保存最后一个命令的执行状态。
[laoxiao@hadoop101 datas]$ ./helloworld.sh
hello world
[laoxiao@hadoop101 datas]$ echo $?
0
[root@localhost shell]$?
-bash: 0: 未找到命令
[root@localhost shell]echo $?
127#未正确执行,值为非0
[root@localhost shell]ls
canshu.sh  liuyan.txt  parameter.sh  test1.sh  test2.sh
[root@localhost shell]echo $?
0

三、 运算符

bash语言的运算有三种写法:

  1. “$((运算式))”或“$[运算式]”
  2. expr + , - , *, /, % 。注意:expr表示命令,运算符间要有空格,如果涉及变量需要用$变量名
    反引号为命令替换,可以保留执行结果
  3. let命令接算术表达式
[root@localhost shell]C=12
[root@localhost shell]echo expr 3+C   #echo expr 3 + C这种写法也不对
-bash: 3+8: 未找到命令#因为空格表示结束,3+C成了一个命令

[root@localhost shell]echo `expr 9 + D`
expr: 非整数参数#由于expr是命令,变量C使用时要写作$C

[root@localhost shell]echo `expr 3 + $C`#反引号表示里面的整体作为一个命令执行,并且保存执行结果。
15

[root@localhost shell]echo $[3+C]
15        #$[3+C]是一个运算表达式,C前面不需要加$,但是加了也可以

综合运算:推荐写法是“$[运算式]”,或者let命令。例如:

[root@localhost shell]echo $[(2+3)*5]
#[root@localhost shell]let  C=(C+5)*2
25

[root@localhost shell]echo `expr $[2+3] \* 5`#乘号之前要有反斜杠
25

四、 条件判断

  1. [ 条件表达式 ](注意条件表达式前后要有空格)
    注意:条件非空即为true,[ laoxiao ]返回true,[] 返回false。
  2. 字符串/整数判断条件
判断条件说明
-lt小于(less than)
-le小于等于(less equal)
-eq等于(equal)
-gt大于(greater than)
-ge大于等于(greater equal)
-ne不等于(Not equal)
=字符串比较是否相等
-a -o逻辑与、或
  1. 文件权限/类型判断
判断条件说明
-r文件有读的权限(read)
-w文件有写的权限(write)
-x文件有执行权限(execute)
-f文件存在并且是一个常规的文件(file)
-e文件存在(existence)
-d文件存在并是一个目录(directory)
[root@localhost shell]vim condition.sh

#!/bin/bash

# && 逻辑与,||逻辑或,下面两个式子等价
[ $# -gt 2 ] && echo "参数的个数大于2"
[ $# -le 2 ] || echo "参数的个数大于2----"

[root@localhost shell]vim condition.sh
[root@localhost shell]bash condition.sh 1 2 
[root@localhost shell]bash condition.sh 1 2 3
参数的个数大于2
参数的个数大于2----

说明:

  • 逻辑与中,前面条件为False,后面无论是啥最终结果都是False,所以后一个不会执行。所以[ $# -gt 2 ] &&中条件表达式错误时,后面不会打印那句话。只有参数>2才会打印。
  • 逻辑或中,前面条件为True,后面无论是啥最终结果都是True,所以后一个不会执行。所以[ $# -le 2 ] &&中条件表达式正确时,后面不会打印那句话。只有参数>2才会打印。

示例2:任何命令的执行状态为0则为True,否则为False

[root@localhost shell]# [ -r test2.sh ]
[root@localhost shell]# echo $?
0   #上一个命令状态为0表示True

示例3:命令状态作为条件判断,判断时只看真假,不看数字
目标:如果用户zhangsan不存在,则添加用户zhangsan

[root@localhost shell]id zhangsan
id: zhangsan: no such user#用户zhangsan不存在,命令状态为非0

[root@localhost shell]id root
uid=0(root) gid=0(root)=0(root)

[root@localhost shell]vim condition2.sh

#!/bin/bash
# 如果用户zhangsan不存在,则添加用户zhangsan
#&> /dev/null表示不要命令的结果,只要其状态。True为0,False为非0

id zhangsan &> /dev/null && echo "zhangsan用户存在,不用添加"  #True与。。。
id zhangsan &> /dev/null || useradd zhangsan                #False或。。。

[root@localhost shell]bash condition2.sh
[root@localhost shell]id zhangsan
uid=1001(zhangsan) gid=1001(zhangsan)=1001(zhangsan)
[root@localhost shell]bash condition2.sh
zhangsan用户存在,不用添加

五、流程控制

针对列表的判断可以考虑用for语句,针对条件判断可以考虑用while语句

5.1 if 判断

if条件成立才执行程序,格式1:

if [ 条件判断式 ];then
	程序
fi

或者格式2:

if [ 条件判断式 ]
	then
		程序
fi

Tips:if 后面必须有空格,结尾必须是fi

[root@localhost shell]# vim if.sh

#!/bin/bash

if [ $1 -lt 18 ];then  #$1为传入的参数,这里指年龄
	echo "未成年"
elif [ $1 -ge 18 -a $1 -lt 30 ];then #-a表示and
	echo "青年"
else
	echo "中老年"
fi

[root@localhost shell]# bash if.sh 12
未成年

5.2 case语句

case $变量名 in
"value1")      #只有半边小括号)
 	程序1       #(变量值=value1才执行)
;;		       #程序后面有两个;;
"value2")
 	程序2       #(变量值=value2才执行)
;;
…省略其他分支…
*)			   #*表示以上都不是
 	程序n       #(变量值不是以上value时执行
;;
esac
  1. case行尾必须为单词“in”,每一个模式匹配必须以右括号“)”结束。
  2. 双分号“;;”表示命令序列结束,相当于java中的break。
  3. 最后的“*)”表示默认模式,相当于java中的default。

输入一个数字,如果是1,则输出banzhang,如果是2,则输出cls,如果是其它,输出renyao。

[laoxiao@hadoop101 datas]$ touch case.sh
[laoxiao@hadoop101 datas]$ vim case.sh

!/bin/bash
case $1 in
"1")
    echo "banzhang"
;;
"2")
   echo "cls"
;;
*)
    echo "renyao"
;;
esac

5.3 for语句

  1. 基本语法(一)
for (( 初始值;循环控制条件;变量变化 ))
do
程序
done#done表示for语句结束,类似if语句以fi结束

案例:从1加到100

[laoxiao@hadoop101 datas]vim for1.sh

#!/bin/bash
s=0
for((i=0;i<=100;i++));do
    s=$[$s+$i]
done
echo $s

[laoxiao@hadoop101 datas]bash for1.sh
5050
  1. 基本语法(二)
for 变量 in123#这里可以是一个列表
do
程序
done

案例1:从1加到100

#!/bin/bash
#for i in {1..100};do    #{1..100}表示从1到100的列表
for i in `seq 1 100`;do  #seq是一个命令,需要用反引号括起来
	s=$[$s+$i]
done

echo "从1到100的和是$s"

案例2:打印root目录下所有文件名

#!/bin/bash

for i in `ls /root`;do
	echo $i
done

  ls 命令得到的结果是目录下所有文件名,且以空格隔开。bash中,所有以空格隔开或者换行符隔开的都可以当做列表。

5.4 while循环

while [ 条件判断式 ]
do
程序
done

案例:从1累加到100

#!/bin/bash
s=0
i=1
while [ $i -le 100 ];do
   s=$[$s+$i]
   i=$[$i+1]
done
echo $s

或者使用let命令可以写成:(let文档可以使用help let查看)。

i=1
sum=0
while [ $i -le 100 ];do
	let sum=sum+i  # 这行代码也可以写成: sum=$[$sum+$i]
    let i++
done

echo "从1到100的和是:$sum"

六、 read读取控制台输入

read(选项)(参数)

  • -p:指定读取值时的提示符;
  • -t:指定读取值时等待的时间(秒)。
  • 参数:指定读取值的变量名,前面不需要加$
#!/bin/bash
# 从1开始到n所有数字的和,n可以由用户输入

read -t 3 -p "请在3秒内输入一个大于1的数字:" n  #这里的参数直接就是变量,前面不能加$,否则报错

i=1
sum=0
while [ $i -le $n ];do   #注意,条件判断里面的变量前面要加$
	let sum=sum+i
	let i++
done
echo "从1到$n的和是:$sum"

七、函数

7.1 系统函数

  1. 获取文件名:basename [string / pathname][suffix]
    删掉最后一个(‘/’)字符和之前的前缀,只显示后面的字符串(文件名)。
    suffix为后缀,如果suffix被指定了,basename会将pathname或string中的suffix去掉。

  2. 获取文件目录:dirname 文件绝对路径
    获取绝对文件的绝对目录部分

[laoxiao@hadoop101 datas]$ basename /home/laoxiao/banzhang.txt
banzhang.txt
[laoxiao@hadoop101 datas]$ basename /home/laoxiao/banzhang.txt .txt
banzhang
[laoxiao@hadoop101 datas]$ basename msb-laoxiao -laoxiao
msb

[laoxiao@hadoop101 ~]$ dirname /home/laoxiao/banzhang.txt
/home/laoxiao

示例2:把/opt/test/目录下面所有的后缀为.txt文件,改成后缀为.sh

#!/bin/bash

dir="/opt/test/"
for f in `ls /opt/test/*.txt`;do  #第一次的时候写成引号,结果一直不对
	#*txt前面不能有空格,否则输出不是一个列表(可以加空格试试结果)
	# f就是其中的一个txt文件
    filename=`basename $f .txt`
    dist_filename=$filename".sh"  #变量前面都要加$
    
    mv $f $dir$dist_filename     #$f已经是绝对路径

done

在这里插入图片描述

7.2 自定义函数

function funname(){
Action;
return int;#可选
}

funame#直接调用传参就行
  • 必须在调用函数地方之前,先声明函数。前面的function可以不写
  • return是可选的,实际上是返回函数的执行状态,所以只能通过$?系统变量获得,且只能返回0-255范围的整数
  • 可以采用标准输出:echo来作为函数的返回值

示例一:两数之和

[laoxiao@hadoop101 datas]$ vim fun.sh

#!/bin/bash

function sum()
{
  s=0#这行可不写
  let s=$1+$2
  echo "$s"
}
read -p "请输入第一个整数: " n1;
read -p "请输入第二个整数: " n2;
sum $n1 $n2;#分号表示结束,可不写

如果想使用return语句,可写成:

#!bin/bash
sum()
{
     let s=$1+$2
     return $s
}
read -p "请输入第一个整数: " n1;
read -p "请输入第二个整数: " n2;
sum $n1 $n2
echo $?    #return返回值必须使用$?获取,结果超过255会重新从0开始计数

示例二:阶乘

#!/bin/bash

if [ $# -ne 1 ];then
	echo "参数个数错误,程序退出"
    exit 3
fi

# 任何一个大于1的数字,它的阶乘=n * (n-1)的阶乘
function jiecheng(){
	n=$1
	if [ $n -le 1 ];then
		echo 1 #  当成n=1的时候,函数的返回值
		return 0 # 返回的是执行状态
    elif [ $n -gt 1 ];then
		let pre_n=n-1
		temp=`jiecheng $pre_n`
		let result=n*temp
        echo $result
		return 0
	fi
}

八、 Shell工具

8.1 cut剪切

cut [选项参数] filename

  • 功能:从文件的每一行剪切字节、字符和字段,并输出
  • -d:分隔符,可不写“”。默认分隔符是制表符
  • -f:提取第几列
[root@192 shell]cp /etc/passwd .
[root@192 shell]more passwd

root:x:0:0:root:/root:/bin/bash#用户名:密码:用户编号:用户组编号:用户组名:家目录:m
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
.....

[root@192 shell]cut -d ':' -f1,6 passwd#提取1到6列写1-6
root:/root
bin:/bin
daemon:/sbin

#获取当前操作系统所有可登录用户的用户名。
#用户默认shell是bin/bash的表示是可登录用户。
#bin/bash$表示查找以bin/bash结尾。|表示前一个命令结果给后一个命令使用
[root@192 shell]grep 'bin/bash$' passwd|cut -d: -f1
root
user
zhangsan

8.2 sed命令

sed [选项参数] ‘command’ filename

  • sed是一种流编辑器,作用在内存中(或者存储在临时缓存区),而不是直接修改磁盘中的文件。
  • 逐行处理,把缓冲区处理完的内容送往屏幕,直到文件末尾。文件内容并没有改变,除非你使用重定向存储输出。
选项说明
-e即执行多个sed命令之间用-e隔开
-i磁盘上文件内容也一并被修改报存
a新增。增加的字串可以在下一行出现
d删除
s查找并替换
#sed.txt文件第二行插入mei nv
[laoxiao@hadoop102 datas]$ sed '2a mei nv' sed.txt
#删除sed.txt文件所有包含wo的行。/表示查找,被查找字符结尾以/分割
[laoxiao@hadoop102 datas]$ sed '/wo/d' sed.txt

#将sed.txt文件中的第二行删除并将wo替换为ni
#用sed修改时,s需要在前面,末尾的g表示全局替换,否则只会替换每一行匹配到的第一个
 [laoxiao@hadoop102 datas]$ sed -e '2d' -e 's/wo/ni/g' sed.txt

8.3 awk

  强大的文本分析工具,把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理
awk [选项参数] ‘pattern1{action1} pattern2{action2}...’ filename

  • pattern:表示AWK在数据中查找的内容,就是匹配模式
  • action:在找到匹配内容时所执行的一系列命令,可以理解为shell语言下一个小型C语言编辑器
选项说明
-F指定分隔符,默认空格分割
-v赋值一个用户定义变量
内置变量名说明
FILENAME文件名
NR已读的记录数
NF浏览记录的域的个数(切割后,列的个数)

示例:编辑passwd文件,原passwd文件内容如下

[root@192 shell]more passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
  1. 提取第1和第2列,用,隔开并打印
[root@192 shell]awk -F: '{print $1","$2}' passwd
#必须是“,”才行。估计是命令的{}包含在单引号里面
#$1、$2表示第一列、第二列。
root,x
bin,x
daemon,x
adm,x
  1. 查找passwd文件中以root关键字开头的所有行,输出该行的第1列和第7列,并用制表符隔开
[root@192 shell]awk -F: '/^root/ {print $1"\t" $7}' passwd
#
root	/bin/bash
  1. 在所有行前面添加列名user,shell。在最后一行添加"dahaige,/bin/zuishuai"
#BEGIN表示awk命令还没有读取文件时就执行的操作,这里相当于加了一个表头
#END是文件读完之后的操作
awk -F : 'BEGIN{print "user, shell"} {print $1","$7}
END{print "结束"}' passwd
  1. 将passwd文件中的用户id增加数值1并输出
#下面两种都可以
[laoxiao@hadoop102 datas]awk -v i=1 -F: '{print $3+i}' passwd
[laoxiao@hadoop102 datas]awk -F: '{print $3+1}' passwd
  1. 统计passwd文件名,每行的行号,每行的列数
 [laoxiao@hadoop102 datas]$ awk -F: '{print "filename:" FILENAME ", linenumber:" NR
",columns:" NF}' passwd

8.4 sort

sort(选项)(参数):将文件进行排序,并将排序结果标准输出

选项说明
-n依照数值的大小排序
-r反序排列
-t设置排序时所用的分隔字符
-k指定需要排序的列
[laoxiao@hadoop102 datas]$ vim sort.sh
bb:40:5.4
bd:20:4.2
xz:50:2.3
cls:10:3.5
ss:30:1.6

按照“:”分割后的第三列倒序排序

[laoxiao@hadoop102 datas]sort -t: -nrk 3  sort.sh
bb:40:5.4
bd:20:4.2
cls:10:3.5
xz:50:2.3
ss:30:1.6
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神洛华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值