Shell脚本

Shell脚本

一个 shell 脚本就是一个包含一系列命令的普通的文本文件。shell 读取这个文件,然后执行 文件中的所有命令

  • 能看懂
  • 能改
  • 能写
  • 能优化

1607159199173-55b5bd12-6f26-434e-9407-ec62a10692cd.png

shell是C语言编写的一个二进制程序,Shell 不仅是一个功能强大的命令行接口,也是一个脚本语言解释器。内核认识二进制,shell是命令解释器,

shell 会自动地搜索某些目录。为了最大程度的方便,会把脚本放到这些目录当中。

入门案例

脚本文件格式:

  • <font style="color:#3C3C3C;">#!</font>字符序列是一种特殊的结构叫做 shebang。
  • 告诉操作系统将执行此脚本所用的解释器的名字。
  • 每个 shell 脚本都应该把这一文本行 作为它的第一行。
#!/bin/bash
# This is our first script.
echo 'Hello World!'

对于脚本文件,有两个常见的权限设置;权限为755的脚本,则每个人都能执行,和权限为700的 脚本,只有文件所有者能够执行

chmod 755 hello_world

大多数的 Linux 发行版会配置 PATH 变量,让其包含 一个位于用户家目录下的 bin 目录,从而允许用户能够执行他们自己的程序。所以如果我们创建了 一个 bin 目录,并把我们的脚本放在这个目录下,那么这个脚本就应该像其它程序一样开始工作了。如果这个 PATH 变量不包含这个目录,我们能够轻松地添加它,通过在我们的.bashrc 文件中包含下面 这一行文本:

export PATH=~/bin:"$PATH"
# 为了把这个修改应用到当前的终端会话中,必须让 shell 重新读取这个 .bashrc 文件。
# 这可以通过 “sourcing”.bashrc 文件来完成,
# 这个点(.)命令是 source 命令的同义词,一个 shell 内建命令,用来读取一个指定的 shell 命令文件, 并把它看作是从键盘中输入的一样。

. .bashrc

这个 ~/bin 目录是存放为个人所用脚本的好地方。如果我们编写了一个脚本,系统中的每个用户都可以使用它, 那么这个脚本的传统位置是 /usr/local/bin不管是脚本还是编译过的程序,都应该放到 /usr/local 目录下,

而不是在 /bin 或 /usr/bin 目录。这些目录都是由 Linux 文件系统层次结构标准指定,只包含由 Linux 发行商所提供和维护的文件。

语法

通过使用行继续符(反斜杠-回车符序列)和缩进,这个复杂命令的逻辑会被更清楚地描述给读者。

多行字符串

编写的程序是一个报告生成器。它会显示系统的各种统计数据和它的状态,并将产生 HTML 格式的报告。

#!/bin/bash
# Program to output a system information page
echo "<HTML>
    <HEAD>
          <TITLE>System Information Report</TITLE>
    </HEAD>
    <BODY>
          <H1>System Information Report</H1>
    </BODY>
</HTML>"

补充:在命令行里面,输入多行的时候,一个带引号的字符串可能包含换行符

变量和常量

https://www.bilibili.com/video/BV1qi4y137NM?p=3

看到这里了,后面再来改。。。没时间现在

  1. 变量名可由字母数字字符(字母和数字)和下划线字符组成。
  2. 变量名的第一个字符必须是一个字母或一个下划线。
  3. 变量名中不允许出现空格和标点符号。

shell 不会 在乎变量值的类型;它把它们都看作是字符串。通过使用带有-i 选项的 declare 命令,你可以强制 shell 把 赋值限制为整数

#!/bin/bash
# Program to output a system information page
title="System Information Report"
# 惯例是指定大写字母来表示常量,小写字母表示真正的变量。
TITLE="System Information Report For $HOSTNAME"
declare -r TITLE=”Page Title”
# -r(只读)选项的内部命令declare来强制常量的不变性.随后所有给 TITLE 的赋值都会被 shell 阻止
CURRENT_TIME=$(date +"%x %r %Z")
TIME_STAMP="Generated $CURRENT_TIME, by $USER"
echo "<HTML>
        <HEAD>
                <TITLE>$TITLE</TITLE>
        </HEAD>
        <BODY>
                <H1>$TITLE</H1>
                <P>$TIME_STAMP</P>
        </BODY>
</HTML>"

Here Documents与echo

echo 命令通过有引号的字符串可以输入,here document 或者 here script也可以实现echo 的功能。一个 here document 是另外一种 I/O 重定向形式,我们 在脚本文件中嵌入正文文本,然后把它发送给一个命令的标准输入。它这样工作:

command << token
text
token
# command 是一个可以接受标准输入的命令名
# token 是一个用来指示嵌入文本结束的字符串
#!/bin/bash
# Program to output a system information page
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")
TIME_STAMP="Generated $CURRENT_TIME, by $USER"
cat << _EOF_
<HTML>
         <HEAD>
                <TITLE>$TITLE</TITLE>
         </HEAD>
         <BODY>
                <H1>$TITLE</H1>
                <P>$TIME_STAMP</P>
         </BODY>
</HTML>
_EOF_

取代 echo 命令,现在我们的脚本使用 cat 命令和一个 here document。这个字符串_EOF_(意思是“文件结尾”, 一个常见用法)被选作为 token,并标志着嵌入文本的结尾。注意这个 token 必须在一行中单独出现,并且文本行中 没有末尾的空格。

[me@linuxbox ~]$ foo="some text"
[me@linuxbox ~]$ cat << _EOF_
> $foo
> "$foo"
> '$foo'
> \$foo
> _EOF_
some text
"some text"
'some text'
$foo

正如我们所见到的,shell 根本没有注意到引号。它把它们看作是普通的字符。这就允许在一个 here document 中可以随意的嵌入引号。对于我们的报告程序来说,这将是非常方便的。Here documents 可以和任意能接受标准输入的命令一块使用。

#!/bin/bash
# Script to retrieve a file via FTP
FTP_SERVER=ftp.nl.debian.org
FTP_PATH=/debian/dists/lenny/main/installer-i386/current/images/cdrom
REMOTE_FILE=debian-cd_info.tar.gz
ftp -n << _EOF_
open $FTP_SERVER
user anonymous me@linuxbox
cd $FTP_PATH
hash
get $REMOTE_FILE
bye
_EOF_
ls -l $REMOTE_FILE

如果我们把重定向操作符从 “<<” 改为 “<<-”,shell 会忽略在此 here document 中开头的 tab 字符。 这就能缩进一个 here document,从而提高脚本的可读性:

#!/bin/bash
# Script to retrieve a file via FTP
FTP_SERVER=ftp.nl.debian.org
FTP_PATH=/debian/dists/lenny/main/installer-i386/current/images/cdrom
REMOTE_FILE=debian-cd_info.tar.gz
ftp -n <<- _EOF_
    open $FTP_SERVER
    user anonymous me@linuxbox
    cd $FTP_PATH
    hash
    get $REMOTE_FILE
    bye
_EOF_
ls -l $REMOTE_FILE

shell函数

function name {
    commands
    return
}

name () {
    commands
    return
}
#!/bin/bash
# Program to output a system information page
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")
TIME_STAMP="Generated $CURRENT_TIME, by $USER"
report_uptime () {
  cat <<- _EOF_
  <H2>System Uptime</H2>
  <PRE>$(uptime)</PRE>
  _EOF_
  return
}
report_disk_space () {
  cat <<- _EOF_
  <H2>Disk Space Utilization</H2>
  <PRE>$(df -h)</PRE>
  _EOF_
  return
}
report_home_space () {
  cat <<- _EOF_
  <H2>Home Space Utilization</H2>
  <PRE>$(du -sh /home/*)</PRE>
  _EOF_
  return
}
cat << _EOF_
<HTML>
    <HEAD>
        <TITLE>$TITLE</TITLE>
    </HEAD>
    <BODY>
        <H1>$TITLE</H1>
        <P>$TIME_STAMP</P>
        $(report_uptime)
        $(report_disk_space)
        $(report_home_space)
    </BODY>
</HTML>
_EOF_

正如我们所看到的,通过在变量名之前加上单词<font style="color:#3C3C3C;">local</font>,来定义局部变量。这就创建了一个只对其所在的 shell 函数起作用的变量。

#!/bin/bash
foo=0 
funct_1 () {
    local foo 
    foo=1
    echo "funct_1: foo = $foo"
}
echo "global:  foo = $foo"
funct_1
echo "global:  foo = $foo"

Shell 函数完美地替代了别名,并且实际上是创建个人所用的小命令的首选方法。别名 非常局限于命令的种类和它们支持的 shell 功能,然而 shell 函数允许任何可以编写脚本的东西。 例如,如果我们喜欢 为我们的脚本开发的这个 report_disk_space shell 函数,我们可以为我们的 .bashrc 文件 创建一个相似的名为 ds 的函数:

Shell 函数中使用位置参数

如果一个包含 shell 函数 file_info 的脚本调用该函数,且带有一个文件名参数,那这个参数会传递给 file_info 函数。这些函数不仅能在脚本中使用,也可以用在 .bashrc 文件中。

位置参数 $0 总是包含命令行中第一项的完整路径名(例如,该程序的名字), 但不会包含这个我们可能期望的 shell 函数的名字。
file_info () {
  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
}

read - 从标准输入读取单词

基本上,read 会把来自标准输入的字段赋值给具体的变量。如果没有提供变量名,shell 变量 REPLY 会包含数据行。

read [-options] [variable...]

读取单个参数

#!/bin/bash
echo -n "Please enter an integer -> "
#  -n 选项会删除输出结果末尾的换行符,来显示提示信息,然后使用read来读入变量 int 的数值。
read int
if [[ "$int" =~ ^-?[0-9]+$ ]]; then
    if [ $int -eq 0 ]; then
        echo "$int is zero."
    else
        if [ $int -lt 0 ]; then
            echo "$int is negative."
        else
            echo "$int is positive."
        fi
        if [ $((int % 2)) -eq 0 ]; then
            echo "$int is even."
        else
            echo "$int is odd."
        fi
    fi
else
    echo "Input value is not an integer." >&2
    exit 1
fi

读取多个参数

#!/bin/bash
# read-multiple: read multiple values from keyboard
echo -n "Enter one or more values > "
read var1 var2 var3 var4 var5
echo "var1 = '$var1'"
echo "var2 = '$var2'"
echo "var3 = '$var3'"
echo "var4 = '$var4'"
echo "var5 = '$var5'"

如果 read 命令接受到变量值数目少于期望的数字,那么额外的变量值为空,而多余的输入数据则会 被包含到最后一个变量中。如果 read 命令之后没有列出变量名,则一个 shell 变量,REPLY,将会包含 所有的输入

#!/bin/bash
# read-single: read multiple values into default variable
echo -n "Enter one or more values > "
read
echo "REPLY = '$REPLY'"

read 选项

选项 说明
-a array 把输入赋值到数组 array 中,从索引号零开始。我们 将在第36章中讨论数组问题。
-d delimiter 用字符串 delimiter 中的第一个字符指示输入结束,而不是一个换行符。
-e 使用 Readline 来处理输入。这使得与命令行相同的方式编辑输入。
-n num 读取 num 个输入字符,而不是整行。
-p prompt 为输入显示提示信息,使用字符串 prompt。
-r Raw mode. 不把反斜杠字符解释为转义字符。
-s Silent mode. 不会在屏幕上显示输入的字符。当输入密码和其它确认信息的时候,这会很有帮助。
-t seconds 超时. 几秒钟后终止输入。若输入超时,read 会返回一个非零退出状态。
-u fd 使用文件描述符 fd 中的输入,而不是标准输入。
#!/bin/bash
read -p "Enter one or more values > "
echo "REPLY = '$REPLY'"
#!/bin/bash
if read -t 10 -sp "Enter secret pass phrase > " secret_pass; then
    echo "\nSecret pass phrase = '$secret_pass'"
else
    echo "\nInput timed out" >&2
    exit 1
fi

多个由一个或几个空格 分离开的单词在输入行中变成独立的个体,并被 read 赋值给单独的变量。这种行为由 shell 变量__IFS__ (内部字符分隔符)配置。IFS 的默认值包含一个空格,一个 tab,和一个换行符,每一个都会把 字段分割开。可以调整 IFS 的值来控制输入字段的分离。例如,这个 /etc/passwd 文件包含的数据行 使用冒号作为字段分隔符。通过把 IFS 的值更改为单个冒号,我们可以使用 read 读取 /etc/passwd 中的内容,并成功地把字段分给不同的变量。

#!/bin/bash
# read-ifs: read fields from a file
FILE=/etc/passwd
read -p "Enter a user name > " user_name
file_info=$(grep "^$user_name:" $FILE)
#  grep 命令的输入结果赋值给变量 file_info
# grep 命令使用的正则表达式 确保用户名只会在 /etc/passwd 文件中匹配一行
if [ -n "$file_info" ]; then
    IFS=":" read user pw uid gid name home shell <<< "$file_info"
    # 这一行由三部分组成:对一个变量的赋值操作,一个带有一串参数的 read 命令,和重定向操作符。
    # Shell 允许在一个命令之前给一个或多个变量赋值。这些赋值会暂时改变之后的命令的环境变量。 
    # 在这种情况下,IFS 的值被改成一个冒号
    #  <<< 操作符指示一个 here 字符串。一个 here 字符串就像一个 here 文档,只是比较简短,由 单个字符串组成。
    # 来自 /etc/passwd 文件的数据发送给 read 命令的标准输入。
    echo "User = '$user'"
    echo "UID = '$uid'"
    echo "GID = '$gid'"
    echo "Full Name = '$name'"
    echo "Home Dir. = '$home'"
    echo "Shell = '$shell'"
else
    echo "No such user '$user_name'" >&2
    exit 1
fi

不能把管道用在 read 上

校正输入

#!/bin/bash
# read-validate: validate input
invalid_input () {
    echo "Invalid input '$REPLY'" >&2
    exit 1
}
read -p "Enter a single item > "
# input is empty (invalid)
[[ -z $REPLY ]] && invalid_input
# input is multiple items (invalid)
(( $(echo $REPLY | wc -w) > 1 )) && invalid_input
# is input a valid filename?
if [[ $REPLY =~ ^[-[:alnum:]\._]+$ ]]; then
    echo "'$REPLY' is a valid filename."
    if [[ -e $REPLY ]]; then
        echo "And file '$REPLY' exists."
    else
        echo "However, file '$REPLY' does not exist."
    fi
    # is input a floating point number?
    if [[ $REPLY =~ ^-?[[:digit:]]*\.[[:digit:]]+$ ]]; then
        echo "'$REPLY' is a floating point number."
    else
        echo "'$REPLY' is not a floating point number."
    fi
    # is input an integer?
    if [[ $REPLY =~ ^-?[[:digit:]]+$ ]]; then
        echo "'$REPLY' is an integer."
    else
        echo "'$REPLY' is not an integer."
    fi
else
    echo "The string '$REPLY' is not a valid filename."
fi

输出一个菜单

#!/bin/bash
# read-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] > "
if [[ $REPLY =~ ^[0-3]$ ]]; then
    if [[ $REPLY == 0 ]]; then
        echo "Program terminated."
        exit
    fi
    if [[ $REPLY == 1 ]]; then
        echo "Hostname: $HOSTNAME"
        uptime
        exit
    fi
    if [[ $REPLY == 2 ]]; then
        df -h
        exit
    fi
    if [[ $REPLY == 3 ]]; then
        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
        exit
    fi
else
    echo "Invalid entry." >&2
    exit 1
fi

读取命令行参数

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
"

结果

$ 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 =

获取命令行参数个数

<font style="color:#3C3C3C;">$#</font>,可以得到命令行参数个数的变量

#!/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
"
$ 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 - 访问多个参数的利器

执行一次 shift 命令, 就会导致所有的位置参数 “向下移动一个位置”。

#!/bin/bash
count=1
while [[ $# -gt 0 ]]; do
    echo "Argument $count = $1"
    count=$((count + 1))
    shift
done

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

只要参数个数不为零就会继续执行的 while 循环。 我们显示当前的位置参数,每次循环迭代变量 count 的值都会加1,用来计数处理的参数数量, 最后,执行 shift 命令加载 $1,其值为下一个位置参数的值。这里是程序运行后的输出结果:
$ posit-param2 a b c d
Argument 1 = a
Argument 2 = b
Argument 3 = c
Argument 4 = d

PROGNAME 变量。它的值就是 basename $0 命令的执行结果。basename 命令清除一个路径名的开头部分,只留下一个文件的基本名称。程序中,basename 命令清除了包含在 $0 位置参数中的路径名的开头部分,$0 中包含着示例程序的完整路径名。当构建提示信息正如程序结尾的使用信息的时候, basename $0 的执行结果就很有用处。按照这种方式编码,可以重命名该脚本,且程序信息会自动调整为包含相应的程序名称。

#!/bin/bash
# file_info: simple file information program
PROGNAME=$(basename $0)
if [[ -e $1 ]]; then
    echo -e "\nFile Type:"
    file $1
    echo -e "\nFile Status:"
    stat $1
else
    echo "$PROGNAME: usage: $PROGNAME file" >&2
    exit 1
fi

处理集体位置参数

创建一个脚本或 shell 函数,来简化另一个程序的执行。包裹程序提供了一个神秘的命令行选项 列表,然后把这个参数列表传递给下一级的程序。为此 shell 提供了两种特殊的参数。他们二者都能扩展成完整的位置参数列表,但以相当微妙的方式略有不同。

* 和 @ 特殊参数

参数 描述
$* 展开成一个从1开始的位置参数列表。当它被用双引号引起来的时候,展开成一个由双引号引起来 的字符串,包含了所有的位置参数,每个位置参数由 shell 变量 IFS 的第一个字符(默认为一个空格)分隔开。
$@ 展开成一个从1开始的位置参数列表。当它被用双引号引起来的时候, 它把每一个位置参数展开成一个由双引号引起来的分开的字符串。
#!/bin/bash
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"

在这个相当复杂的程序中,我们创建了两个参数: “word” 和 “words with spaces”,然后把它们传递给 pass_params 函数。这个函数,依次,再把两个参数传递给 print_params 函数, 使用了特殊参数 $* 和 $@ 提供的四种可用方法。脚本运行后,揭示了这两个特殊参数存在的差异:

$ posit-param
 $* :
$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 =

通过我们的参数,$* 和 $@ 两个都产生了一个有四个词的结果:

word words with spaces
"$*" produces a one word result:
    "word words with spaces"
"$@" produces a two word result:
    "word" "words with spaces"

这个结果符合我们实际的期望。我们从中得到的教训是尽管 shell 提供了四种不同的得到位置参数列表的方法, 但到目前为止, “$@” 在大多数情况下是最有用的方法,因为它保留了每一个位置参数的完整性。

一个更复杂的应用

  • 输出文件。 我们将添加一个选项,以便指定一个文件名,来包含程序的输出结果。 选项格式要么是 -f file,要么是 --file file
  • 交互模式。这个选项将提示用户输入一个输出文件名,然后判断指定的文件是否已经存在了。如果文件存在, 在覆盖这个存在的文件之前会提示用户。这个选项可以通过 -i 或者 --interactive 来指定。
  • 帮助。指定 -h 选项 或者是 --help 选项,可导致程序输出提示性的使用信息。
usage () {
    echo "$PROGNAME: usage: $PROGNAME [-f file | -i]"
    return
}
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

首先,我们添加了一个叫做 usage 的 shell 函数,以便显示帮助信息,当启用帮助选项或敲写了一个未知选项的时候。当位置参数 $1 不为空的时候,这个循环会持续运行。在循环的底部,有一个 shift 命令, 用来提升位置参数,以便确保该循环最终会终止。

处理 -f 参数的方式很有意思。当监测到 -f 参数的时候,会执行一次 shift 命令,从而提升位置参数 $1 为伴随着 -f 选项的 filename 参数。

我们下一步添加代码来实现交互模式:

if [[ -n $interactive ]]; then
    while true; do
        read -p "Enter name of output file: " filename
        if [[ -e $filename ]]; then
            read -p "'$filename' exists. Overwrite? [y/n/q] > "
            case $REPLY in
            Y|y)    break
                    ;;
            Q|q)    echo "Program terminated."
                    exit
                    ;;
            *)      continue
                    ;;
            esac
        elif [[ -z $filename ]]; then
            continue
        else
            break
        fi
    done
fi

若 interactive 变量不为空,就会启动一个无休止的循环,该循环包含文件名提示和随后存在的文件处理代码。 如果所需要的输出文件已经存在,则提示用户覆盖,选择另一个文件名,或者退出程序。如果用户选择覆盖一个 已经存在的文件,则会执行 break 命令终止循环。

为了实现这个输出文件名的功能,首先我们必须把现有的这个写页面(page-writing)的代码转变成一个 shell 函数,

write_html_page () {
    cat <<- _EOF_
        <HTML>
            <HEAD>
                <TITLE>$TITLE</TITLE>
            </HEAD>
            <BODY>
                <H1>$TITLE</H1>
                <P>$TIMESTAMP</P>
                $(report_uptime)
                $(report_disk_space)
                $(report_home_space)
            </BODY>
        </HTML>
    _EOF_
    return
}
# output html page
if [[ -n $filename ]]; then
    if touch $filename && [[ -f $filename ]]; then
        write_html_page > $filename
    else
        echo "$PROGNAME: Cannot write file '$filename'" >&2
        exit 1
    fi
else
    write_html_page
fi

解决 -f 选项逻辑的代码出现在以上程序片段的末尾。在这段代码中,我们测试一个文件名是否存在,若文件名存在, 则执行另一个测试看看该文件是不是可写文件。为此,会运行 touch 命令,紧随其后执行一个测试,来决定 touch 命令 创建的文件是否是个普通文件。这两个测试考虑到了输入是无效路径名(touch 命令执行失败),和一个普通文件已经存在的情况。程序调用 write_html_page 函数来生成实际的网页。函数输出要么直接定向到标准输出(若 filename 变量为空的话)要么重定向到具体的文件中。

posted on 2025-10-12 20:14  chuchengzhi  阅读(23)  评论(0)    收藏  举报

导航

杭州技术博主,专注分享云计算领域实战经验、技术教程与行业洞察, 打造聚焦云计算技术的垂直博客,助力开发者快速掌握云服务核心能力。

褚成志 云计算 技术博客