第七章:测试
每一个完善的编程语言都应该能测试一个条件。然后依据测试的结果做进一步的动作。Bash有test命令,各种括号及内嵌的操作符,还有if/then结构来完成上面的功能。
7.1. 测试结构
一个if/then结构测试一列命令的退出状态是否为0(因为依照惯例,0意味着命令执行成功),如果是0则会执行一个或多个命令。
有一个命令 [ (左方括是特殊字符). 它和test是同义词,因为效率的原因,它被内建在shell里。这个命令的参数是比较表达式或者文件测试,它会返回一个退出状态指示比较的结果(0表示真,1表示假)。
在版本2.02,Bash引入了[[ ... ]] 扩展的测试命令,它使熟悉其他语言中这种比较测试的程序员也能很快熟悉比较操作。注意[[是一个关键字 ,不是一个命令。
Bash把[[
如果 (( ... )) 和 let ... 构造计算的算术表达式扩展为非零值,则它们也返回退出状态 0。 因此,这些算术扩展结构可用于执行算术比较。
1 let "1<2" returns 0 (as "1<2" expands to "1")
2 (( 0 && 1 )) returns 1 (as "0 && 1" expands to "0")
if 命令不仅能测试由方括号括起来的条件,也能测试任何命令。
1 if cmp a b &> /dev/null # 禁止输出.
2 then echo "Files a and b are identical."
3 else echo "Files a and b differ."
4 fi
5
6 # 非常有用的"if-grep"组合:
7 # -----------------------------------
8 if grep -q Bash file
9 then echo "File contains at least one occurrence of Bash."
10 fi
11
12 word=Linux
13 letter_sequence=inu
14 if echo "$word" | grep -q "$letter_sequence"
15 # 选项"-q"使grep禁止输出.
16 then
17 echo "$letter_sequence found in $word"
18 else
19 echo "$letter_sequence not found in $word"
20 fi
21
22
23 if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED
24 then echo "Command succeeded."
25 else echo "Command failed."
26 fi
一个if/then结构能包含嵌套的比较和测试。
1 if echo "Next *if* is part of the comparison for the first *if*."
2
3 if [[ $comparison = "integer" ]]
4 then (( a < b ))
5 else
6 [[ $a < $b ]]
7 fi
8
9 then
10 echo '$a is less than $b'
11 fi
例子 7-1. 事实是什么?
1 #!/bin/bash
2
3 # 小技巧:
4 # 如果你不确定某一条件怎么被求值,
5 #+ 可以用一个if-test结构来测试.
6
7 echo
8
9 echo "Testing \"0\""
10 if [ 0 ] # 0
11 then
12 echo "0 is true."
13 else
14 echo "0 is false."
15 fi # 0为真.
16
17 echo
18
19 echo "Testing \"1\""
20 if [ 1 ] # 1
21 then
22 echo "1 is true."
23 else
24 echo "1 is false."
25 fi # 1为真.
26
27 echo
28
29 echo "Testing \"-1\""
30 if [ -1 ] # -1
31 then
32 echo "-1 is true."
33 else
34 echo "-1 is false."
35 fi # -1为真.
36
37 echo
38
39 echo "Testing \"NULL\""
40 if [ ] # NULL (空条件)
41 then
42 echo "NULL is true."
43 else
44 echo "NULL is false."
45 fi # NULL为假.
46
47 echo
48
49 echo "Testing \"xyz\""
50 if [ xyz ] # 字符串
51 then
52 echo "Random string is true."
53 else
54 echo "Random string is false."
55 fi # 任意字符串为true.
56
57 echo
58
59 echo "Testing \"\$xyz\""
60 if [ $xyz ] # 变量$xyz为null值, 但...
61 # 它只是一个未初始化的变量.
62 then
63 echo "Uninitialized variable is true."
64 else
65 echo "Uninitialized variable is false."
66 fi # 未初始化的变量为false.
67
68 echo
69
70 echo "Testing \"-n \$xyz\""
71 if [ -n "$xyz" ] # 进一步实验核实.
72 then
73 echo "Uninitialized variable is true."
74 else
75 echo "Uninitialized variable is false."
76 fi # 未始初化的变量为false.
77
78 echo
79
80
81 xyz= # 已初始化, 但设置成null值.
82
83 echo "Testing \"-n \$xyz\""
84 if [ -n "$xyz" ]
85 then
86 echo "Null variable is true."
87 else
88 echo "Null variable is false."
89 fi # Null值变量为假.
90
91
92 echo
93
94
95 # 什么时候"false"为真?
96
97 echo "Testing \"false\""
98 if [ "false" ] # "false"是一个字符串.
99 then
100 echo "\"false\" is true." #+ 它被测试为真.
101 else
102 echo "\"false\" is false."
103 fi # "false"为真.
104
105 echo
106
107 echo "Testing \"\$false\"" # 再来,未初始化的变量.
108 if [ "$false" ]
109 then
110 echo "\"\$false\" is true."
111 else
112 echo "\"\$false\" is false."
113 fi # "$false"变量为假.
114 # 现在, 我们取得了预期的效果.
115
116 # 如果我们测试未初始化的变量"$true"会发生什么?
117
118 echo
119
120 exit 0
练习. 上面例子 7-1的解释.
1 if [ condition-true ]
2 then
3 command 1
4 command 2
5 ...
6 else
7 # 或选的(如果不需要就可去掉).
8 # 如果条件测试失败,就在这里加入默认的执行命令.
9 command 3
10 command 4
11 ...
12 fi
当if和then在同一行的时候,一个分号(;)必须用在if语句的结尾。if和then都是关键字.关键字(或命令)开始一个语句,如果在同一行开始另一个新语句时,前面一个语句必须用分号(;)结束。
if [ -x "$filename" ]; then
Else if 和 elif
- elif
-
elif是else if的缩写。作用是在一个if/then里嵌入一个内部的if/then结构。
1 if [ condition1 ] 2 then 3 command1 4 command2 5 command3 6 elif [ condition2 ] 7 # 和else if相同 8 then 9 command4 10 command5 11 else 12 default-command 13 fi
if test condition-true结构是精确等同于if [ condition-true ].如果用[ condition-true ]结构,左方括[ , 是一个调用test命令的标识。右方括]在一个if/test中封闭左方括[,但它不是必须的,不过新一些的Bash版本会要求有。
- Bash内建的test命令测试文件类型和比较字符串. 因此,在一个Bash脚本中test语句不必调用外部的/usr/bin/test的二进制文件,这个test程序是sh-utils包的一部分。同样的,[也不调用/usr/bin/[,/usr/bin/[是链接到/usr/bin/test一个符号链接。
-
bash$ type test test is a shell builtin bash$ type '[' [ is a shell builtin bash$ type '[[' [[ is a shell keyword bash$ type ']]' ]] is a shell keyword bash$ type ']' bash: type: ]: not found
例子 7-2. 等价的测试命令:test,/usr/bin/test,[]和/usr/bin/[
1 #!/bin/bash 2 3 echo 4 5 if test -z "$1" 6 then 7 echo "No command-line arguments." 8 else 9 echo "First command-line argument is $1." 10 fi 11 12 echo 13 14 if /usr/bin/test -z "$1" # 和内建的"test"命令一样. 15 then 16 echo "No command-line arguments." 17 else 18 echo "First command-line argument is $1." 19 fi 20 21 echo 22 23 if [ -z "$1" ] # 和上面代码块的功能一样 24 # if [ -z "$1" 应该来说会运行, 但是... 25 #+ Bash给出错误说少了一个封闭的右方括. 26 then 27 echo "No command-line arguments." 28 else 29 echo "First command-line argument is $1." 30 fi 31 32 echo 33 34 35 if /usr/bin/[ -z "$1" ] # 同样和上面的代码块一样. 36 # if /usr/bin/[ -z "$1" # 工作, 但还是给出一个错误信息. 37 # # 注意: 38 # 这个已经在bash 3.x版本被修补好了。 39 then 40 echo "No command-line arguments." 41 else 42 echo "First command-line argument is $1." 43 fi 44 45 echo 46 47 exit 0
[[]]结构比Bash版本的[]更通用。它是从ksh88中引进的test命令的扩展。
在[[和]]之间的所有的字符都不会被文件扩展或是标记分割,但是会有参数引用和命令替换。
1 file=/etc/passwd 2 3 if [[ -e $file ]] 4 then 5 echo "Password file exists." 6 fi
用[[ ... ]]测试结构比用[ ... ]更能防止脚本里的许多逻辑错误。比如说,&&,||,<和>操作符能在一个[[]]测试里通过,但在[]结构会发生错误。
在一个if的后面,不必一定是test命令或是test结构([]或是[[]])。
1 dir=/home/bozo 2 3 if cd "$dir" 2>/dev/null; then # "2>/dev/null"会隐藏错误的信息. 4 echo "Now in $dir." 5 else 6 echo "Can't change to $dir." 7 fi
"if COMMAND"结构会返回COMMAND命令的退出状态码。
同样的,在一个测试方括号里面的条件测试也可以用列表结构(list construct)而不必用if。
1 var1=20 2 var2=22 3 [ "$var1" -ne "$var2" ] && echo "$var1 is not equal to $var2" 4 5 home=/home/bozo 6 [ -d "$home" ] || echo "$home directory does not exist."
(( ))结构扩展并计算一个算术表达式的值。如果表达式值为0,会返回1或假作为退出状态码。一个非零值的表达式返回一个0或真作为退出状态码。这个结构和先前test命令及[]结构的讨论刚好相反。
例子 7-3. 用(( ))进行算术测试
1 #!/bin/bash 2 # 算术测试. 3 4 # (( ... ))结构会求值并测试该值。 5 # 退出状态码与[ ... ]结构正好相反! 6 7 (( 0 )) 8 echo "Exit status of \"(( 0 ))\" is $?." # 1 9 10 (( 1 )) 11 echo "Exit status of \"(( 1 ))\" is $?." # 0 12 13 (( 5 > 4 )) # 真 14 echo "Exit status of \"(( 5 > 4 ))\" is $?." # 0 15 16 (( 5 > 9 )) # 假 17 echo "Exit status of \"(( 5 > 9 ))\" is $?." # 1 18 19 (( 5 - 5 )) # 0 20 echo "Exit status of \"(( 5 - 5 ))\" is $?." # 1 21 22 (( 5 / 4 )) # 除法有效. 23 echo "Exit status of \"(( 5 / 4 ))\" is $?." # 0 24 25 (( 1 / 2 )) # 除法计算结果< 1 26 echo "Exit status of \"(( 1 / 2 ))\" is $?." # 截取为0. 27 # 1 28 29 (( 1 / 0 )) 2>/dev/null # 除以0的非法计算. 30 # ^^^^^^^^^^^ 31 echo "Exit status of \"(( 1 / 0 ))\" is $?." # 1 32 33 # 起了什么作用? 34 # 如果不要"2>/dev/null"这句会怎么样? 35 # 试试去掉这句再运行这个脚本. 36 37 exit 0
7.2. 文件测试操作符
如果下面的条件成立返回真...
- -e
-
文件存在
- -a
-
文件存在
这个和-e的作用一样. 它是不赞成使用的,所以它的用处不大。
- -f
-
文件是一个普通文件(不是一个目录或是一个设备文件)
- -s
-
文件大小不为零
- -d
-
文件是一个目录
- -b
-
文件是一个块设备(软盘, 光驱, 等等.)
- -c
-
文件是一个字符设备(键盘, 调制解调器, 声卡, 等等.)
- -p
-
文件是一个管道
- -h
-
文件是一个符号链接
- -L
-
文件是一个符号链接
- -S
-
文件是一个socket
- -t
-
文件(描述符)与一个终端设备相关
这个测试选项可以用于检查脚本中是否标准输入 ([ -t 0 ])或标准输出([ -t 1 ])是一个终端.
- -r
-
文件是否可读 (指运行这个测试命令的用户的读权限)
- -w
-
文件是否可写 (指运行这个测试命令的用户的读权限)
- -x
-
文件是否可执行 (指运行这个测试命令的用户的读权限)
- -g
-
文件或目录的设置-组-ID(sgid)标记被设置
如果一个目录的sgid标志被设置,在这个目录下创建的文件都属于拥有此目录的用户组,而不必是创建文件的用户所属的组。这个特性对在一个工作组里的同享目录很有用处。
- -u
-
文件的设置-用户-ID(suid)标志被设置
一个root用户拥有的二进制执行文件如果设置了设置-用户-ID位(suid)标志普通用户可以以root权限运行。[1] 这对需要存取系统硬件的执行程序(比如说pppd和cdrecord)很有用。如果没有设置suid位,则这些二进制执行程序不能由非root的普通用户调用。
-
-rwsr-xr-t 1 root 178236 Oct 2 2000 /usr/sbin/pppd
被设置了suid标志的文件在权限列中以s标志表示.
- -k
-
粘住位设置
保存文本模式标志通常称为“粘滞位”,是一种特殊类型的文件权限。 如果文件设置了此标志,则该文件将保存在高速缓存中,以便更快地访问。 [2] 如果在目录上设置,它会限制写权限。 设置粘性位会将 t 添加到文件或目录列表的权限中。
drwxrwxrwt 7 root 1024 May 19 21:26 tmp/
如果用户不拥有设置了粘性位的目录,但在该目录中具有写权限,则他只能删除他拥有的文件。 这可以防止用户在可公开访问的目录(例如 /tmp)中无意覆盖或删除彼此的文件。
- -O
-
你是文件拥有者
- -G
-
你所在组和文件的group-id相同
- -N
-
文件最后一次读后被修改
- f1 -nt f2
-
文件f1比f2新
- f1 -ot f2
-
文件f1比f2旧
- f1 -ef f2
-
文件f1和f2 是相同文件的硬链接
- !
-
"非" -- 反转上面所有测试的结果(如果没有给出条件则返回真).
例子 7-4. 测试断掉的连接
1 #!/bin/bash 2 # broken-link.sh 3 # 由Lee bigelow所写<ligelowbee@yahoo.com> 4 # 已经征得作者的同意. 5 6 #一个用于发现死符号链接并且输出它们的链接文件的纯shell的脚本。 7 #所以它们能把输出提供给xargs并处理 :) 8 #例如: broken-link.sh /somedir /someotherdir|xargs rm 9 # 10 #下面是更好的发现死符号链接的办法: 11 # 12 #find "somedir" -type l -print0|\ 13 #xargs -r0 file|\ 14 #grep "broken symbolic"| 15 #sed -e 's/^\|: *broken symbolic.*$/"/g' 16 # 17 #但这不是纯bash脚本,下面的则是. 18 #注意: 谨防在/proc文件系统和死循环链接中使用! 19 ############################################################## 20 21 22 #如果没有参数被传递给脚本作为搜索目录,则使用当前目录 23 # 24 # 25 #################### 26 [ $# -eq 0 ] && directorys=`pwd` || directorys=$@ 27 28 #设置函数linkchk来检查它传递的目录 29 #对于链接且不存在的文件,然后打印它们的引号。 30 #如果目录中的元素之一是子目录,则 31 #将该子目录发送到链接检查功能。 32 ########## 33 linkchk () { 34 for element in $1/*; do 35 [ -h "$element" -a ! -e "$element" ] && echo \"$element\" 36 [ -d "$element" ] && linkchk $element 37 # Of course, '-h' tests for symbolic link, '-d' for directory. 38 done 39 } 40 41 #将传递给脚本的每个 arg 发送到 linkchk 函数 42 #如果它是一个有效的目录。 如果不是,则打印错误消息 43 #并使用信息。 44 ################ 45 for directory in $directorys; do 46 if [ -d $directory ] 47 then linkchk $directory 48 else 49 echo "$directory is not a directory" 50 echo "Usage: $0 dir1 dir2 ..." 51 fi 52 done 53 54 exit 0
例子 28-1, 例子 10-7, 例子 10-3, 例子 28-3, 和 例子 A-1 也说明了文件测试操作符的用法.
注
[1] 一定要意识到suid二进制文件可能引起潜在的安全漏洞,并且在shell脚本中suid标志已经不起作用了。
[2] 在现代UNIX操作系统,粘住位已经不在文件中再使用,只用在目录中。
7.3. 其它比较操作符
二元比较操作符比较两个变量或是数值。注意整数和字符串比较的分别。
整数比较
- -eq
-
等于
if [ "
b" ] - -ne
-
不等于
if [ "
b" ] - -gt
-
大于
if [ "
b" ] - -ge
-
大于等于
if [ "
b" ] - -lt
-
小于
if [ "
b" ] - -le
-
小于等于
if [ "
b" ] - <
-
小于(在双括号里使用)
(("
b")) - <=
-
小于等于 (在双括号里使用)
(("
b")) - >
-
大于 (在双括号里使用)
(("
b")) - >=
-
大于等于(在双括号里使用)
(("
b"))
字符串比较
- =
-
等于
if [ "
b" ] - ==
-
等于
if [ "
b" ]它和=是同义词。
==比较操作符在一个双方括号测试和一个单方括号号里意思不同。
1 [[ $a == z* ]] # 如果变量$a以字符"z"开始(模式匹配)则为真. 2 [[ $a == "z*" ]] # 如果变量$a与z*(字面上的匹配)相等则为真. 3 4 [ $a == z* ] # 文件扩展和单元分割有效. 5 [ "$a" == "z*" ] # 如果变量$a与z*(字面上的匹配)相等则为真.
!=
不相等
if [ "
b" ]操作符在[[ ... ]]结构里使用模式匹配.
<
小于,依照ASCII字符排列顺序
if [[ "
b" ]]if [ "
b" ]注意"<"字符在[ ] 结构里需要转义
>
大于,依照ASCII字符排列顺序
if [[ "
b" ]]if [ "
b" ]注意">"字符在[ ] 结构里需要转义.
参考例子 26-11 中这种比较的一个应用.
-z
字符串为"null",即是指字符串长度为零。
-n
字符串不为"null",即长度不为零.
在测试方括号里进行-n测试时一定要把字符串用引号起来。用没有引号引起的! -z或者在方括号里只有未引号引起的字符串 (参考例子 7-6)来进行测试一般都能工作,然而,这其实是不安全的测试。应该总是用引号把测试字符串引起来。[1]
例子 7-5. 算术和字符串比较
1 #!/bin/bash 2 3 a=4 4 b=5 5 6 # 这儿的"a"和"b"既能被看成是整数也能被看着是字符串。 7 # 在字符串和数字之间不是严格界定的, 8 #+ 因为Bash变量不是强类型的。 9 10 # Bash允许变量的整数操作和比较, 11 #+ 但这些变量必须只包括数字字符. 12 # 不管怎么样,你应该小心考虑. 13 14 echo 15 16 if [ "$a" -ne "$b" ] 17 then 18 echo "$a is not equal to $b" 19 echo "(arithmetic comparison)" 20 fi 21 22 echo 23 24 if [ "$a" != "$b" ] 25 then 26 echo "$a is not equal to $b." 27 echo "(string comparison)" 28 # "4" != "5" 29 # ASCII 52 != ASCII 53 30 fi 31 32 # 在这个实际的例子中,"-ne"和"!="都能工作. 33 34 echo 35 36 exit 0
例子 7-6. 测试一个字符串是否是null值
1 #!/bin/bash 2 # str-test.sh: 测试null字符串和没有用引号引起的字符串, 3 #+ but not strings and sealing wax, not to mention cabbages and kings . . . 4 5 # 用if [ ... ]结构 6 7 8 # 如果一个字符串变量没有被初始化,它里面的值未定义。 9 # 这种情况称为"null"值(不同于零). 10 11 if [ -n $string1 ] # $string1没有被声明或初始化. 12 then 13 echo "String \"string1\" is not null." 14 else 15 echo "String \"string1\" is null." 16 fi 17 # 错误结果. 18 # Shows $string1 as not null, although it was not initialized. 19 20 21 echo 22 23 24 # 让我们再试试. 25 26 if [ -n "$string1" ] # 这次, $string1变量被引号引起. 27 then 28 echo "String \"string1\" is not null." 29 else 30 echo "String \"string1\" is null." 31 fi # 在一个测试方括里用引号引起变量! 32 33 34 echo 35 36 37 if [ $string1 ] # 这次,变量$string1什么也不加. 38 then 39 echo "String \"string1\" is not null." 40 else 41 echo "String \"string1\" is null." 42 fi 43 # This works fine. 44 # The [ ] test operator alone detects whether the string is null. 45 # However it is good practice to quote it ("$string1"). 46 # 47 # As Stephane Chazelas points out, 48 # if [ $string1 ] has one argument, "]" 49 # if [ "$string1" ] has two arguments, the empty "$string1" and "]" 50 51 52 53 echo 54 55 56 57 string1=initialized 58 59 if [ $string1 ] # Again, $string1 stands naked. 60 then 61 echo "String \"string1\" is not null." 62 else 63 echo "String \"string1\" is null." 64 fi 65 # Again, gives correct result. 66 # Still, it is better to quote it ("$string1"), because . . . 67 68 69 string1="a = b" 70 71 if [ $string1 ] # Again, $string1 stands naked. 72 then 73 echo "String \"string1\" is not null." 74 else 75 echo "String \"string1\" is null." 76 fi 77 # Not quoting "$string1" now gives wrong result! 78 79 exit 0 80 # Thank you, also, Florian Wisser, for the "heads-up".
例子 7-7. zmore
1 #!/bin/bash 2 # zmore 3 4 #用'more'查看被压缩过的文件 5 6 NOARGS=65 7 NOTFOUND=66 8 NOTGZIP=67 9 10 if [ $# -eq 0 ] # 等同于: if [ -z "$1" ] 11 # $1可以存在,但为空: zmore "" arg2 arg3 12 then 13 echo "Usage: `basename $0` filename" >&2 14 # 错误信息打印到标准错误. 15 exit $NOARGS 16 # 返回65作为脚本的退出状态(错误码). 17 fi 18 19 filename=$1 20 21 if [ ! -f "$filename" ] # Quoting $filename allows for possible spaces. 22 then 23 echo "File $filename not found!" >&2 24 # 错误信息打印到标准错误. 25 exit $NOTFOUND 26 fi 27 28 if [ ${filename##*.} != "gz" ] 29 # 在方括号里使用变量替换. 30 then 31 echo "File $1 is not a gzipped file!" 32 exit $NOTGZIP 33 fi 34 35 zcat $1 | more 36 37 # 用过滤程序'more.' 38 # 如果你想,也可以用'less'代替。 39 40 41 exit $? # 脚本返回管道的退出状态 42 # 实际上"exit $?"是多余的,因为在任何情况下, 43 # 脚本会返回最后一个执行命令的退出码。
混合比较
- -a
-
逻辑与
如果exp1和exp2都为真,则exp1 -a exp2返回真.
- -o
-
逻辑或
只要exp1和exp2任何一个为真,则exp1 -o exp2 返回真.
它们和Bash中用于双方括号结构的比较操作符&&和||很相似。
[[ condition1 && condition2 ]]
可以参考Example 8-3, Example 26-16, 和Example A-28怎么实际地使用混合比较操作符。
注
[1] 就像S.C.指出的那样,在一个混合测试中,把一个字符串变量引号引起来可能还不够。如果$string变量是空的话,表达式[ -n "
a" = "$b" ]在一些Bash版本中可能会引起错误。安全的办法是附加一个外部的字符串给可能有空字符串变量比较的所有变量,[ "x a" = "x$b" ](x字符可以互相抵消)。7.4. 嵌套的if/then条件测试
使用if/then结构的测试可以嵌套。最终的结果和使用上一节的&&混合比较操作符一样。
1 if [ condition1 ] 2 then 3 if [ condition2 ] 4 then 5 do-something # But only if both "condition1" and "condition2" valid. 6 fi 7 fi
阅读例子 34-4可看到一个嵌套的if/thenif/then条件测试。
7.5. 检测你对测试命令掌握
一个xinitrc系统文件能用来启动一个X服务器。这个文件包含了相当多的if/then测试,就像下面这个文件的一个摘录展示的一样。
1 if [ -f $HOME/.Xclients ]; then 2 exec $HOME/.Xclients 3 elif [ -f /etc/X11/xinit/Xclients ]; then 4 exec /etc/X11/xinit/Xclients 5 else 6 # 失败后的安全设置。 虽然我们决不该执行到这儿 7 # (我们也在X客户提供可靠保证) 它不能被破坏. 8 xclock -geometry 100x100-5+5 & 9 xterm -geometry 80x50-50+150 & 10 if [ -f /usr/bin/netscape -a -f /usr/share/doc/HTML/index.html ]; then 11 netscape /usr/share/doc/HTML/index.html & 12 fi 13 fi
解释上面摘录的test结构,然后检查整个/etc/X11/xinit/xinitrc文件并分析那儿的所有if/then测试结构。你可能涉及到还没讨论过的grep,sed和正则表达式的主题。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律