TLCL之第四章(3)


第四章


6. 流程控制:while/until循环

在这一章中,我们将看一个叫做循环的程序概念,其可用来使程序的某些部分重复。shell 为循环提供了三个复合命令。 本章我们将查看其中的两个命令,随后章节介绍第三个命令。

1. 循环

while

语法结构:
while commands; do commands; done

#!/bin/bash
# while-count: display a series of numbers
count=1
while [ $count -le 5 ]; do
    echo $count
    count=$((count + 1))
done
echo "Finished."

跳出循环

bash 提供了两个内部命令,它们可以用来在循环内部控制程序流程。 break 命令立即终止一个循环, 且程序继续执行循环之后的语句。 continue 命令导致程序跳过循环中剩余的语句,且程序继续执行 下一次循环。

util

until 命令与 while 非常相似,除了当遇到一个非零退出状态的时候, while 退出循环, 而 until 不退出。一个 until 循环会继续执行直到它接受了一个退出状态零。

#!/bin/bash
# until-count: display a series of numbers
count=1
until [ $count -gt 5 ]; do
    echo $count
    count=$((count + 1))
done
echo "Finished."

使用循环读取文件

while 和 until 能够处理标准输入。这就可以使用 while 和 until 处理文件。

#!/bin/bash
# while-read: read lines from a file
while read distro version release; do
    printf "Distro: %s\tVersion: %s\tReleased: %s\n" \
        $distro \
        $version \
        $release
done < distros.txt

为了重定向文件到循环中,我们把重定向操作符放置到 done 语句之后。循环将使用 read 从重定向文件中读取 字段。这个 read 命令读取每个文本行之后,将会退出,其退出状态为零,直到到达文件末尾。到时候,它的 退出状态为非零数值,因此终止循环。也有可能把标准输入管道到循环中。

#!/bin/bash
# while-read2: read lines from a file
sort -k 1,1 -k 2n distros.txt | while read distro version release; do
    printf "Distro: %s\tVersion: %s\tReleased: %s\n" \
        $distro \
        $version \
        $release
done

这里我们接受 sort 命令的标准输出,然后显示文本流。然而,因为管道将会在子 shell 中执行 循环,当循环终止的时候,循环中创建的任意变量或赋值的变量都会消失,记住这一点很重要。

7. 疑难排解

1. 语法错误

一个普通的错误类型是语法。语法错误涉及到一些shell语法元素的拼写错误,在大多数情况下,这类错误会导致shell拒绝执行此脚本。

2. 逻辑错误

逻辑错误不会阻止脚本运行,虽然脚本会正常运行,但是它不会产生期望的结果,归咎于脚本的逻辑问题。

3. 测试

在各类软件开发中,测试是一个重要的环节。
在之前的讨论中,我们知道了如何使用stubs来验证程序流程。在脚本开发的初级阶段,它们是一项有价值的技术来检测我们的工作进度。

4. 调试

如果测试暴露了脚本中的一个问题,那下一步就是调试了。“一个问题”通常意味着在某种情况下,这个脚本的执行结果不是程序员所期望的结果。若是这种情况,我们需要自己确认这个脚本实际到底要完成什么任务,和为什么要这样做。有时候查找bug要牵涉到许多检测工作。一个设计良好的脚本会对查找错误有帮助。
为了查看真实的程序流,我们使用了一项叫做追踪(tracing)的技术。
一种追踪的方法涉及到在脚本中添加可以显示程序执行位置的提示性信息。我们可以添加提示信息到我们的代码片段中。
bash还提供了一种名为追踪的方法,这种方法可以通过-x选项和set命令加上-x选项两种途径实现。
#! /bin/bash -x
我们可以使用set命令加上-x选项,为脚本中的一块选择区域,而不是整个脚本启用追踪。
我们使用set命令加上-x选项来启动追踪,+x选项关闭追踪。这种技术可以用来检查一个有错误的脚本的多个部分。


8. 流程控制:case分支

1.case

bash的多选符合命令称为case,它的语法如下:

case word in
    [pattern [| pattern]...) commands ;;]...
esac
#!/bin/bash
# case-menu: a menu driven system information program
clear
echo "
Please Select:
1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
"
read -p "Enter selection [0-3] > "
case $REPLY in
    0)  echo "Program terminated."
        exit
        ;;
    1)  echo "Hostname: $HOSTNAME"
        uptime
        ;;
    2)  df -h
        ;;
    3)  if [[ $(id -u) -eq 0 ]]; then
            echo "Home Space Utilization (All Users)"
            du -sh /home/*
        else
            echo "Home Space Utilization ($USER)"
            du -sh $HOME
        fi
        ;;
    *)  echo "Invalid entry" >&2
        exit 1
        ;;
esac

2. 模式

这里case语句使用的模式和路径展开中使用的那些是一样的。模式以一个“)”为终止符。这里有一些有效的模式。

case模式实例

模式描述
a)若单词为“a”,则匹配
[[:alpha:]]若单词为一个字符字符,则匹配
???)若单词只有3个字符,则匹配
*.txt)若单词以".txt"字符结尾,则匹配
*)匹配任意单词,把这个模式做为case命令的最后一个模式,是一个很好的做法,可以捕捉到任意一个与先前模式不匹配的数值,也就是说,捕捉到任何可能的无效值
#!/bin/bash
read -p "enter word > "
case $REPLY in
    [[:alpha:]])        echo "is a single alphabetic character." ;;
    [ABC][0-9])         echo "is A, B, or C followed by a digit." ;;
    ???)                echo "is three characters long." ;;
    *.txt)              echo "is a word ending in '.txt'" ;;
    *)                  echo "is something else." ;;
esac

还可以使用竖线字符作为分隔符,把多个模式结合起来,这就创建了一个“或”条件模式。

3. 执行多个动作

case语法只允许执行与一个成功匹配的模式相关联的动作。匹配成功之后,命令将会 终止。但若输入的字符不止于一个POSIX字符集匹配的话。现在的bash版本,添加“;;&”表达式来终止每个行动。

#!/bin/bash
# case4-2: test a character
read -n 1 -p "Type a character > "
echo
case $REPLY in
    [[:upper:]])    echo "'$REPLY' is upper case." ;;&
    [[:lower:]])    echo "'$REPLY' is lower case." ;;&
    [[:alpha:]])    echo "'$REPLY' is alphabetic." ;;&
    [[:digit:]])    echo "'$REPLY' is a digit." ;;&
    [[:graph:]])    echo "'$REPLY' is a visible character." ;;&
    [[:punct:]])    echo "'$REPLY' is a punctuation symbol." ;;&
    [[:space:]])    echo "'$REPLY' is a whitespace character." ;;&
    [[:xdigit:]])   echo "'$REPLY' is a hexadecimal digit." ;;&
esac

当我们运行这个脚本的时候,我们得到这些:

[me@linuxbox ~]$ case4-2
Type a character > a
'a' is lower case.
'a' is alphabetic.
'a' is a visible character.
'a' is a hexadecimal digit.

添加的";;&"的语法允许,case语法继续执行下一条测试,而不是简单地终止运行。


9.位置参数

1. 访问命令行

shell提供了一个称为位置参数的变量集合,这个集合包含了命令行中所有独立的单词。这些变量按照从0到9给予命令。可以以这种方式将明白:

#!/bin/bash
# posit-param: script to view command line parameters
echo "
\$0 = $0
\$1 = $1
\$2 = $2
\$3 = $3
\$4 = $4
\$5 = $5
\$6 = $6
\$7 = $7
\$8 = $8
\$9 = $9
"

一个非常简单的脚本,显示从$0到$9所有变量的值,当不带命令行参数执行该脚本时,输出结果为:

[me@linuxbox ~]$ posit-param
$0 = /home/me/bin/posit-param
$1 =
$2 =
$3 =
$4 =
$5 =
$6 =
$7 =
$8 =
$9 =

即使不带命令行参数,位置参数$0总会包含命令行中出现的第一个单词,也就是已执行程序的路径名。当带参数执行的脚本时,我们看看输出结果:

[me@linuxbox ~]$ posit-param a b c d
$0 = /home/me/bin/posit-param
$1 = a
$2 = b
$3 = c
$4 = d
$5 =
$6 =
$7 =
$8 =
$9 =

注意: 实际上通过参数展开方式你可以访问的参数个数多于9个。只要指定一个大于9的数字,用花括号把该数字括起来就可以。 例如 ${10}、 ${55}、 ${211}等等。

确定参数个数

另外shell还提供了一个名为$#的变量,可以得到命令行参数的个数。

#!/bin/bash
# posit-param: script to view command line parameters
echo "
Number of arguments: $#
\$0 = $0
\$1 = $1
\$2 = $2
\$3 = $3
\$4 = $4
\$5 = $5
\$6 = $6
\$7 = $7
\$8 = $8
\$9 = $9
"

结果是

[me@linuxbox ~]$ posit-param a b c d
Number of arguments: 4
$0 = /home/me/bin/posit-param
$1 = a
$2 = b
$3 = c
$4 = d
$5 =
$6 =
$7 =
$8 =
$9 =

shift - 访问多个参数的利器

如果我们给一个程序添加大量的命令行参数,如:

[me@linuxbox ~]$ posit-param *
Number of arguments: 82
$0 = /home/me/bin/posit-param
$1 = addresses.ldif
$2 = bin
$3 = bookmarks.html
$4 = debian-500-i386-netinst.iso
$5 = debian-500-i386-netinst.jigdo
$6 = debian-500-i386-netinst.template
$7 = debian-cd_info.tar.gz
$8 = Desktop
$9 = dirlist-bin.txt

在这个例子运行的环境下,通配符*展开成82个参数,执行一次shift命令,就会导致所有的位置参数“向下移动一个位置”。事实上,用shift命令也可以处理只有一个参数的情况(除了其值永远不会改变的变量$0):

#!/bin/bash
# posit-param2: script to display all arguments
count=1
while [[ $# -gt 0 ]]; do
    echo "Argument $count = $1"
    count=$((count + 1))
    shift
done

每次执行shift命令的时候,变量$2的值就会移动到变量$1中,变量$3的值会移动到变量$2中,以此类推,变量$#的值也会相应的减1。

shell函数中使用位置参数

正如位置参数被用来给shell脚本传递参数一样,它们也能够被用来给shell函数传递参数,为了说明这一点,我们将把file_info脚本转变成一个shell函数:

file_info () {
  # file_info: function to display file information
  if [[ -e $1 ]]; then
      echo -e "\nFile Type:"
      file $1
      echo -e "\nFile Status:"
      stat $1
  else
      echo "$FUNCNAME: usage: $FUNCNAME file" >&2
      return 1
  fi
}

现在,如果一个包含shell函数file_info的脚本调用该函数,且带有一个文件名参数,那这个参数会传递给file_info函数。
通过此功能,我们可以写出许多有用的shell函数,这些函数不仅能在脚本中使用,也可以用在.bashrc文件中。

2. 处理集体位置参数

有时候把所有的位置参数作为一个集体来管理是很有用的。例如,我们可能想为另一个程序编写一个“包裹程序”。这意味着我们会创建一个脚本或shell函数来简化另一个程序的执行。包裹程序提供了一个神秘的命令行选项列表。然后把这个参数列表传递给下一级的程序。
为此,shell提供了两种特殊的参数。它们两者都能扩展成完整的位置参数列表,但以相当微妙的方式略有不同。它们是:

*和@特殊参数

参数描述
$*展开成一个从1开始的位置参数列表。当它被用双引号引起来的时候,展开成一个由双引号引起来的字符串,包含了所有的位置参数,每个位置参数由shell变量IFS的第一个字符(默认为一个空格)分隔开
$@展开成一个从1开始的位置参数列表,当它被用双引号引起来的时候,它把每一个位置参数展开成一个由双引号引起来的分开的字符串
#!/bin/bash
# posit-params3 : script to demonstrate $* and $@
print_params () {
    echo "\$1 = $1"
    echo "\$2 = $2"
    echo "\$3 = $3"
    echo "\$4 = $4"
}
pass_params () {
    echo -e "\n" '$* :';      print_params   $*
    echo -e "\n" '"$*" :';    print_params   "$*"
    echo -e "\n" '$@ :';      print_params   $@
    echo -e "\n" '"$@" :';    print_params   "$@"
}
pass_params "word" "words with spaces"
[me@linuxbox ~]$ posit-param3
 $* :
$1 = word
$2 = words
$3 = with
$4 = spaces
 "$*" :
$1 = word words with spaces
$2 =
$3 =
$4 =
 $@ :
$1 = word
$2 = words
$3 = with
$4 = spaces
 "$@" :
$1 = word
$2 = words with spaces
$3 =
$4 =

3. 一个更复杂的应用

我们要给程序添加如下几个命令行选项:

  • 输出文件。添加一个选项,以便指定一个文件名,来包含程序的输出结果。选项格式要么是-f file,要么是–file file
  • 交互模式。这个选项提示用户输入一个输出文件名,然后判断指定的文件是否已经存在了。如果文件存在,在覆盖这个存在的文件之前会提示用户,这个选项可以通过-i或者–interactive来指定。
  • 帮助。指定-h选项或者是–help选项,可导致程序输出提示性的使用信息。
usage () {
    echo "$PROGNAME: usage: $PROGNAME [-f file | -i]"
    return
}
# process command line options
interactive=
filename=
while [[ -n $1 ]]; do
    case $1 in
    -f | --file)            shift
                            filename=$1
                            ;;
    -i | --interactive)     interactive=1
                            ;;
    -h | --help)            usage
                            exit
                            ;;
    *)                      usage >&2
                            exit 1
                            ;;
    esac
    shift
done

10.流程控制:for循环

1. for:传统shell格式

for命令语法是:

for variable [in words]; do
    commands
done

variable是一个变量的名字,这个变量在循环执行期间会增加,words是一个可选的条目列表,其值会按顺序赋值给variable,commands是在每次循环迭代中要执行的命令。

[me@linuxbox ~]$ for i in A B C D; do echo $i; done
A
B
C
D

for命令真正强大的功能是我们可以通过许多有趣的方式创建words列表:

[me@linuxbox ~]$ for i in {A..D}; do echo $i; done
A
B
C
D

或者按路径名展开:

[me@linuxbox ~]$ for i in distros*.txt; do echo $i; done
distros-by-date.txt
distros-dates.txt
distros-key-names.txt
distros-key-vernums.txt
distros-names.txt
distros.txt
distros-vernums.txt
distros-versions.txt

如果省略掉for命令的可选项words部分,for命令会默认处理位置参数。

#!/bin/bash
# longest-word2 : find longest string in a file
for i; do
    if [[ -r $i ]]; then
        max_word=
        max_len=0
        for j in $(strings $i); do
            len=$(echo $j | wc -c)
            if (( len > max_len )); then
                max_len=$len
                max_word=$j
            fi
        done
        echo "$i: '$max_word' ($max_len characters)"
    fi
done

2. C语言格式

最新版本的bash已经添加了第二种格式的for命令语法,该语法相似于C语言中的for语法格式。其它许多编程语言也支持这种格式:

for (( expression1; expression2; expression3 )); do
    commands
done

这里的 expression1、expression2和 expression3 都是算术表达式,commands 是每次循环迭代时要执行的命令。 在行为方面,这相当于以下构造形式:

(( expression1 ))
while (( expression2 )); do
    commands
    (( expression3 ))
done

expression1 用来初始化循环条件,expression2 用来决定循环结束的时间,还有在每次循环迭代的末尾会执行 expression3。

10. 字符串和数字

本章将查看几个用来操作字符串和数字的shell功能。shell提供了各种执行字符串操作的参数展开功能。除了算术展开,还有一个常见的命令行程序叫做bc,能执行更高级别的数字运算。

1. 参数展开

  • 基本参数

最简单的参数展开形式反映在平常使用的变量上。
例如:
$a
当这个展开后,会变成变量a所包含的值,简单参数也可能用花括号引起来:
${a}
虽然这对展开没有影响,但若该变量a与其它的文本相邻,可能会把shell搞糊涂了。
前面提到,通过把数字包裹在花括号中,可以访问大于9的位置参数
${11}

  • 管理空变量的展开

几种用来处理不存在和空变量的参数展开形式,这些展开形式对于解决丢失的位置参数和参数指定默认值的情况很方便。

${parameter:-word}

若parameter没有设置或者为空,展开结果是word的值。若parameter不为空,则展开结果是parameter的值。

[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo ${foo:-"substitute value if unset"}
substitute value if unset

${parameter:=word}

若parameter没有设置或为空,展开结果是word的值。另外,word的值会赋值给parameter。若parameter不为空,展开结果是parameter的值。
注意,位置参数或其他的特殊参数不能以这种方式赋值。

${parameter:?word}

若parameter没有设置或为空,这种展开导致脚本带有错误退出,并且word的内容会发送到标准错误。若parameter不为空,展开结果是parameter的值。

${parametere:+word}

若parameter没有设置或为空,展开结果为空,若parameter不为空,展开结果是word的值,然而,parameter的值不会改变。

3. 返回变量名的参数展开

shell具有返回变量名的能力。这会用在一些相当独特的情况下。

  • ${!prefix*}
  • ${!prefix@}

这种展开会返回以prefix开头的已有的变量名。根据bash文档,这两种展开形式的执行结果相同。
这里,我们列出了所有以BASH开头的环境变量:

[me@linuxbox ~]$ echo ${!BASH*}
BASH BASH_ARGC BASH_ARGV BASH_COMMAND BASH_COMPLETION
BASH_COMPLETION_DIR BASH_LINENO BASH_SOURCE BASH_SUBSHELL
BASH_VERSINFO BASH_VERSION

字符串展开

有大量的展开形式可用于操作字符串,其中许多展开形式尤其适用于路径名的展开。

  • ${#parameter}

展开成由parameter所包含的字符串的长度。
通常parameter是一个字符串,然而,如果parameter是@或者是*的话,则展开结果是位置参数的个数。

[me@linuxbox ~]$ foo="This string is long."
[me@linuxbox ~]$ echo "'$foo' is ${#foo} characters long."
'This string is long.' is 20 characters long.
  • ${parameter:offset}
  • ${parameter:offset:length}

这些展开用来从parameter所包含的字符串中提取一部分字符。提取的字符始于第offset个字符(从字符串开头算起)直到字符串的末尾,除非指定提取的长度。

[me@linuxbox ~]$ foo="This string is long."
[me@linuxbox ~]$ echo ${foo:5}
string is long.
[me@linuxbox ~]$ echo ${foo:5:6}
string

若offset的值为负数,则认为offset值是从字符串的末尾开始算起,而不是从开头。注意负数前面必须有一个空格,为防止与${parameter:-word}展开形式混淆。若出现length则不能小于0。
如果parameter是@,展开结果是length个位置参数,从第offset个位置参数开始。

[me@linuxbox ~]$ foo="This string is long."
[me@linuxbox ~]$ echo ${foo: -5}
long.
[me@linuxbox ~]$ echo ${foo: -5:2}
lo
  • ${parameter#pattern}
  • ${parameter##pattern}

这些展开会从parameter所包含的字符串中清除开头一部分文本,这些字符要匹配定义的pattern。pattern是通配符模式,就如哪些用在路径名展开中的模式。这两种形式的差异之处是该#形式清除最短的匹配结果,而该##模式清除最长的匹配结果。

[me@linuxbox ~]$ foo=file.txt.zip
[me@linuxbox ~]$ echo ${foo#*.}
txt.zip
[me@linuxbox ~]$ echo ${foo##*.}
zip
  • ${parameter%pattern}
  • ${parameter%%pattern}

这些展开和上面的展开一样,不过这里清除的是parameter所包含字符串的末尾开始。

  • ${parameter/pattern/string}
  • ${parameter//pattern/string}
  • ${parameter/#pattern/string}
  • ${parameter/%pattern/string}

这种形式的展开对parameter的内容执行查找和替换操作。如果找到了匹配通配符pattern的文本,则用string的内容替换它。在正常形式下,只有第一个匹配项会替换掉。在该//形式下,所有的匹配项都会被替换掉。该/#要求匹配项出现在字符串的开头,而/%要求匹配项出现字符串的末尾。/string可能会省略掉,这样会导致删除匹配的文本。

[me@linuxbox~]$ foo=JPG.JPG
[me@linuxbox ~]$ echo ${foo/JPG/jpg}
jpg.JPG
[me@linuxbox~]$ echo ${foo//JPG/jpg}
jpg.jpg
[me@linuxbox~]$ echo ${foo/#JPG/jpg}
jpg.JPG
[me@linuxbox~]$ echo ${foo/%JPG/jpg}
JPG.jpg

大小写转换

最新的bash版本已经支持字符串的大小写转换了,bash有四个参数展开和declare命令的两个选项来支持大小写转换。
declare命令可以用来把字符串规范成大写或小写字符。declare能强制一个变量总是包含所需的格式。

#!/bin/bash
# ul-declare: demonstrate case conversion via declare
declare -u upper
declare -l lower
if [[ $1 ]]; then
    upper="$1"
    lower="$1"
    echo $upper
    echo $lower
fi
me@linuxbox ~]$ ul-declare aBc
ABC
abc

在上面的脚本中,我们使用declare命令来创建两个变量,upper和lower。

大小写转换参数展开

格式含义
${parameter,}把 parameter 的值全部展开成小写字母。
${parameter,}仅仅把 parameter 的第一个字符展开成小写字母。
${parameter^^}把 parameter 的值全部转换成大写字母。
${parameter^}仅仅把 parameter 的第一个字符转换成大写字母(首字母大写)。
#!/bin/bash
# ul-param - demonstrate case conversion via parameter expansion
if [[ $1 ]]; then
    echo ${1,,}
    echo ${1,}
    echo ${1^^}
    echo ${1^}
fi

4. 算术求值和展开

数基

在算术表达式中,shell支持任意进制的整型常量

指定不同的数基

表示法描述
number默认情况下,没有任何表示法的数字被看做是十进制数(以10为底)
onumber在算术表达式中,以零开头的数字被认为是八进制数。
oxnumber十六进制表示法
base#numbernumber 以 base 为底
[me@linuxbox ~]$ echo $((0xff))
255
[me@linuxbox ~]$ echo $((2#11111111))
255

一元运算符

有两个一元运算符,+ 和 -,它们被分别用来表示一个数字是正数还是负数。例如,-5。

简单算术
算术运算符

运算符描述
+
-
*
/整除
**乘方
%取模(余数)

赋值运算符

尽管它的使用不是那么明显,算术表达式可能执行赋值运算。

赋值运算符

表示法描述
parameter = value简单赋值。给 parameter 赋值。
parameter += value
parameter -= value
parameter *= value
parameter /= value整除
parameter %= value取模
parameter ++后缀自增
parameter –后缀自减
++ parameter前缀自增
–parameter

位运算符
位运算符

运算符描述
~按位取反
<<位左移
>>位右移
&位与
|位或
^位异或

注意除了按位取反运算符之外,其它所有位运算符都有相对应的赋值运算符(例如,<<=)

逻辑运算符
比较运算符

运算符描述
<=小于或等于
>=大于或等于
<小于
>大于
==等于
!=不等于
&&逻辑与
||逻辑或
expr1?expr2:expr3条件三元运算符

bc - 一种高精度计算器语言

为了执行更高级的数学运算拿或仅使用浮点数,我们可以使用一种专业的计算器程序bc。
该bc程序读取一个用它自己的类似c语言的语法编写的脚本文件,一个bc脚本可能是一个分离的文件或者是从标准输入读入。bc语言支持相当少的功能,包括变量,循环和程序员定义的函数。
一个bc脚本foo.bc:

/* A very simple bc script */
2 + 2
[me@linuxbox ~]$ bc foo.bc
bc 1.06.94
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software
Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
4

可以通过-q(quiet)选项禁止这些版权信息,bc也能够交互使用:

[me@linuxbox ~]$ bc -q
2 + 2
4
quit

[me@linuxbox ~]$ bc < foo.bc
4
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值