Linux学习笔记(七):以shell脚本的编写结束
本篇shell脚本以CentOS 7 和 Kali 2021为例
一、一个shell脚本
shell 脚本就是包含一系列命令的文件。读取这个文件,然后执行其中的所有命令
注意点
设置脚本权限:$ chomd 755 shell.sh //可执行可读,755或者700
设置环境,放入家目录下的bin文件中:$ mv shell.sh ~/bin //这样就不用指定脚本所在的路径
$ ls -ad
$ ls --all --directory //在脚本中尽量使用命令选项的长形式,易读
$ find playground \( -type f -not -perm 0600 -exec
chmod 0600 ‘{}’ ‘;’ \) -or \( -type d -not -perm 0711 -exec chmod
0711 ‘{}’ ‘;’ \)
//使用缩进和行继续符来使脚本易读
find playground \
\( \
-type f \
-not -perm 0600 \
-exec chmod 0600 ‘{}’ ‘;’ \
\) \
-or \
\( \
-type d \
-not -perm 0711 \
-exec chmod 0711 ‘{}’ ‘;’ \
\)
#!/bin/bash //指定shell
#test shell
echo "Hello,Linux!"
调试脚本
语法错误(拼写错误) 大多数情况下会导致 shell 拒绝执行此脚本
丢失引号, 如 echo 命令的参数中缺少双引号,可以在完整的vim编辑器中输入:syntax on来使语法高亮生效
忘记复合命令,比如 if 或 while(if 命令中的分号)
逻辑错误,虽然脚本会正常运行,但不会产生期望的结果
-
不正确的条件表达式,一个错误的 if/then/else 语句(逻辑颠倒,或逻辑结构不完整)
-
计数错误,有些循环语句要求从 0 开始计数,而不是从 1 开始,这有可能会被忽视。这些类型的错误会导致循环计数结果错误
-
意外情况,如一个包含嵌入式空格的文件名展开成多个命令参数而不是单个的文件名。
通过在语句前添加注释来排查调试
set 命令加上 -x 选项来启动追踪,+x 选项关闭追踪(检查错误的脚本的多个部分)
#!/bin/bash -x //也可以在脚本的开头的shell加-x进行追踪
测试案例,通过谨慎地选择输入数据或者运行边缘案例和极端案例来完成
dir_name 包含一个已经存在的目录的名字
dir_name 包含一个不存在的目录的名字
dir_name 为空
二、变量和常量
宽松的语法
当 shell 碰到一个变量的时候,会自动地创建它。 这不同于许多编程语言的变量在使用之前必须声明或是定义(shell 要求非常宽松)
variable=value
//variable是变量的名字,value是一个字符串。shell不会在乎变量值的类型(字符串)
可以在同一行中对多个变量赋值
在参数展开过程中,变量名可能被花括号 “{}” 包围着,由于变量名周围的上下文,其变得不明确的情况下,通过添加花括号会很有帮助
变量名的规则:
- 变量名可由字母数字字符(字母和数字)和下划线字符组成
- 变量名的第一个字符必须是一个字母或一个下划线
- 变量名中不允许出现空格和标点符号
在赋值过程中,变量名,等号和变量值之间必须没有空格
shell 不能辨别变量和常量;指定大写字母来表示常量,小写字母表示变量
局部变量
在 shell 函数中,局部变量只能在定义它们的 shell 函数中使用,一旦 shell 函数执行完毕,就不存在 (局部变量可以与已存在的变量名相同)
#!/bin/bash
foo=0 # global variable foo
funct_1 () {
local foo
foo=1
echo "funct_1: foo = $foo"
}
funct_2 () {
local foo
foo=2
echo "funct_2: foo = $foo"
}
echo "global: foo = $foo"
funct_1
echo "global: foo = $foo"
funct_2
echo "global: foo = $foo"
//对两个shell函数中的局部变量foo赋值,不会影响到在函数之外定义的变量foo 的值
在变量名之前加上 local,来定义局部变量
局部变量允许 shell 函数能保持各自以及与它们所在脚本之间的独立性
三、程序设计
自顶向下设计,逐渐细分
确定上层步骤,再逐步细化步骤的过程被称为自顶向下设计。可以把庞大而复杂的任务分割为许多小而简单的任务。自顶向下设计是一种常见的程序设计方法,尤其适合 shell 编程。
- 可以编写脚本,把它们放置到 环境变量 PATH 所列出的目录下
- 把脚本作为 shell 函数嵌入到程序中
function name {
commands
return
}
//Shell 函数有两种语法形式
name () {
commands
return
}
//这里的 name 是函数名,commands 是一系列包含在函数中的命令。
name () {
return
}
//shell函数的命名规则和变量一样。一个函数必须至少包含一条命令,return命令(可选)满足要求
四、流程控制
编程语言学习中的必需,都一样有点小不同
(1)if分支结构
判断语句
if commands; then
commands
[elif commands; then
commands...]
[else
commands]
fi
//if语句语法
当命令执行完毕后,命令(脚本,shell 函数)会给系统发送一个值,叫做退出状态。 这个值是一个 0 到 255 之间的整数,一个零值说明成功,其它所有值说明失败。 Shell 提供了一个参数 echo $? 检查退出状态
shell 提供了两个内部命令,以一个0或1退出状态来终止执行。 True 命令总是执行成功,而 false 命令总是执行失败
如果 if 之后跟随一系列命令,则将计算列表中的最后一个命令
文件表达式
文件表达式被用来计算文件状态
表达式 | 如果为真 |
---|---|
file1 -ef file2 | file1 和 file2 拥有相同的索引号(硬链接两个文件名指向相同的文件) |
file1 -nt file2 | file1新于 file2 |
file1 -ot file2 | file1早于 file2 |
-b file | file 存在并且是一个块(设备)文件 |
-c file | file 存在并且是一个字符(设备)文件 |
-d file | file 存在并且是一个目录 |
-e file | file 存在 |
-f file | file 存在并且是一个普通文件 |
-g file | file 存在并且设置了组 ID |
-G file | file 存在并且由有效组 ID 拥有。 |
-k file | file 存在并且设置了它的“sticky bit”。 |
-L file | file 存在并且是一个符号链接。 |
-O file | file 存在并且由有效用户 ID 拥有。 |
-p file | file 存在并且是一个命名管道。 |
-r file | file 存在并且可读(有效用户有可读权限)。 |
-s file | file 存在且其长度大于零。 |
-S file | file 存在且是一个网络 socket。 |
-t fd | fd 是一个定向到终端/从终端定向的文件描述符 。 这可以被用来决定是否重定向了标准输入/输出错误。 |
-u file | file 存在并且设置了 setuid 位。 |
-w file | file 存在并且可写(有效用户拥有可写权限)。 |
-x file | file 存在并且可执行(有效用户有执行/搜索权限)。 |
字符串表达式
字符表达式用来计算字符串
表达式 | 如果为真 |
---|---|
string | string 不为 null。 |
-n string | 字符串 string 的长度大于零。 |
-z string | 字符串 string 的长度为零。 |
string1 = string2 string1 == string2 | string1 和 string2 相同. 单或双等号都可以,不过双等号更受欢迎。 |
string1 != string2 | string1 和 string2 不相同。 |
string1 > string2 | sting1 排列在 string2 之后。 |
string1 < string2 | string1 排列在 string2 之前。 |
这个 > 和 <表达式操作符必须用引号引起来(或用反斜杠), 当与 test 一块使用的时候
整型表达式
整型表达式用于整数判断
表达式 | 如果为真… |
---|---|
integer1 -eq integer2 | integer1 等于 integer2. |
integer1 -ne integer2 | integer1 不等于 integer2. |
integer1 -le integer2 | integer1 小于或等于 integer2. |
integer1 -lt integer2 | integer1 小于 integer2. |
integer1 -ge integer2 | integer1 大于或等于 integer2. |
integer1 -gt integer2 | integer1 大于 integer2. |
逻辑操作符
操作符 | 测试 | [[ ]] and (( )) |
---|---|---|
AND | -a | && |
OR | -o | || |
NOT | ! | ! |
比较(综合表达式)
test expression
[ expression ]
//这里的 expression 是一个表达式,其执行结果是 true 或是 false
//当表达式为真时,退出状态为0 ,当表达式为假时,test 命令退出状态为1
[[ expression ]]
//expression 是一个表达式,其计算结果为真或假。
//这个[[ ]]命令非常相似于 test 命令(它支持所有的表达式),增加了一个重要的新的字符串表达式:string1 =~ regex
//其返回值为真,如果 string1匹配扩展的正则表达式 regex。为执行一些任务提供了许多可能性。
[[ ]]添加的另一个功能是==操作符支持类型匹配,使[[ ]]有助于计算文件和路径名
(( )) 复合命名,其有利于操作整数。支持完整的算术计算。
(( ))被用来执行算术真测试。如果算术计算的结果是非零值,则一个算术真测试值为真。
控制操作符
bash 支持两种可以执行分支任务的控制操作符。 &&(AND)和||(OR)操作符作用如同复合命令 [[ ]] 中的逻辑操作符
command1 && command2
//对于 && 操作符,先执行 command1,如果并且只有如果 command1 执行成功后,才会执行command2。
command1 || command2
//对于 || 操作符,先执行 command1,如果并且只有如果 command1 执行失败后,才会执行command2。
(2)while/until循环
循环结构
while commands; do commands; done
//循环语句
和 if 一样, while 计算一系列命令的退出状态。只要退出状态为零,它就执行循环内的命令
每次循环结束之后,会重复执行 test 命令。 test 命令不再返回退出状态零,且循环终止。 程序继续执行循环之后的语句
跳出循环
bash 提供了两个内部命令,它们可以用来在循环内部控制程序流程。
break 命令立即终止一个循环, 且程序继续执行循环之后的语句
continue 命令导致程序跳过循环中剩余的语句,且程序继续执行 下一次循环
while ture; do
//通过使用 true 命令为 while 提供一个退出状态。因为 true 的退出状态总是为零,所以循环永远不会终止
until
until 命令与 while 非常相似,当遇到一个非零退出状态的时候, while 退出循环, 而 until 不退出。
一个 until 循环会继续执行直到它接受了一个退出状态零
(3)case分支
Bash 的多选复合命令称为case
case word in
[pattern [| pattern]...) commands ;;]...
esac
case 命令检查一个变量值,然后试图去匹配其中一个具体的模式。当与之相匹配的模式找到之后,就会执行与该模式相关联的命令。若找到一个模式之后,就不会再继续寻找。
模式 | 描述 |
---|---|
a) | 若单词为 “a”,则匹配 |
[[:alpha:]]) | 若单词是一个字母字符,则匹配 |
???) | 若单词只有3个字符,则匹配 |
*.txt) | 若单词以 “.txt” 字符结尾,则匹配 |
*) | 匹配任意单词。可把这个模式做为 case 命令的最后一个模式,可以捕捉到任意一个与先前模式不匹配的数值(捕捉到任何可能的无效值) |
添加 ;;& 表达式来终止每个行动,添加的 “;;&” 的语法允许 case 语句继续执行下一条测试,而不是简单地终止运行。
case word in
[pattern [| pattern]...) commands ;;]...;;&
esac
(4)for循环
处理序列的方式
for variable [in words]; do
commands
done
//variable是一个变量的名字,这个变量在循环执行期间会增加,
//words是一个可选的条目列表,其值会按顺序赋值给variable,commands是在每次循环迭代中要执行的命令
for i in A B C D; do
echo $i;
done
A
B
C
D
//可以通过通过花括号展开{A..D},或者路径展开
如果省略掉 for 命令的可选项 words 部分,for 命令会默认处理位置参数
for 循环的实例都选择 i 作为变量。这一传统的基础源于 Fortran 编程语言。 在 Fortran 语言中,以字母 I,J,K,L 和 M 开头的未声明变量的类型 自动设为整形,而以其它字母开头的变量则为实数类型(带有小数的数字)。这种行为导致程序员使用变量 I,J,和 K 作为循环变量, 因为当需要一个临时变量(正如循环变量)的时候,使用它们工作量比较少。
C 语言格式
for (( expression1; expression2; expression3 )); do
commands
done
//这里的 expression1,expression2,和 expression3 都是算术表达式,commands 是每次循环迭代时要执行的命令
//相当于以下构造
(( expression1 ))
while (( expression2 )); do
commands
(( expression3 ))
done
//expression1 用来初始化循环条件,expression2 用来决定循环结束的时间,还有在每次循环迭代的末尾会执行 expression3。
五、输入和输出
交互性
从标准输入读取数值(read)
不能管道 read,子 shell 执行的时候,会为进程创建父环境的副本。当进程结束 之后,环境副本就会被破坏掉。这意味着一个子 shell 永远不能改变父进程的环境。read 赋值变量, 然后会变为环境的一部分。
通过shell 函数,[[ ]],(( )),控制操作符 &&,if 和 一些正则表达式来校正read的输入
read [-options] [variable…]
echo 'input:'
read
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 中的输入,而不是标准输入。 |
IFS
shell 对提供给 read 的输入按照单词进行分离,IFS 的默认值包含一个空格,一个 tab,和一个换行符,每一个都会把字段分割开。 可以调整 IFS 的值来控制输入字段的分离
OLD_IFS="$IFS"
IFS=":"
read user pw uid gid name home shell <<< "$file_info"
IFS="$OLD_IFS"
//先存储 IFS 的值,然后赋给一个新值,再执行 read 命令,最后把 IFS 恢复原值。
Here Documents(here script)
command << token
text
token
//command 是一个可以接受标准输入的命令名,token 是一个用来指示嵌入文本结束的字符串
here documents 中的单引号和双引号会失去它们在 shell 中的特殊含义,可以随意的嵌入引号
EOF用法
EOF配合cat命令能够多行文本输出.
cat <<-eof
...
...
...
eof
这里的eof可以替换为其他
<<-EOF 和 <<EOF 的区别:
用cat <<EOF时,需要在一个新的一行输入EOF结束stdin的输入。EOF必须顶行写,前面不能用制表符或者空格。
如果是<<-EOF,那么EOF所在行的开头部分的制表符和空格都将被去除。EOF仍然会被当做结束分界符,表示stdin的结束,提高脚本的可读性
六、位置参数
让程序访问命令行内容
#!/bin/bash
# shell1.sh
echo "
\$0 = $0
\$1 = $1
\$2 = $2
\$3 = $3
\$4 = $4
\$5 = $5
\$6 = $6
\$7 = $7
\$8 = $8
\$9 = $9
"
//位置参数的变量集合,这个集合包含了命令行中所有独立的单词。这些变量按照从0到9给予命名
//可以访问的参数个数多于9个。只要指定一个大于9的数字,用花括号把该数字括起来:${10}, ${55}, ${211},
不带命令行参数,位置参数 $0 总会包含已执行程序的路径名
通过参数展开方式可以访问的参数个数多于9个。只要指定一个大于9的数字,用花括号把该数字括起来
$#,可以得到命令行参数个数的变量
#!/bin/bash
# shell1.sh
echo "
Number of arguments: $#
\$0 = $0
\$1 = $1
\$2 = $2
\$3 = $3
\$4 = $4
\$5 = $5
\$6 = $6
\$7 = $7
\$8 = $8
\$9 = $9
"
集体位置参数
把所有的位置参数作为一个集体来管理
创建一个脚本或 shell 函数,来简化另一个程序的执行。 包裹程序提供了一个命令行选项列表,然后把这个参数列表传递给下一级的程序。为此 shell 提供了两种特殊的参数能扩展成完整的位置参数列表,
参数 | 描述 |
---|---|
$* | 展开成一个从1开始的位置参数列表。当它被用双引号引起来的时候,展开成一个由双引号引起来 的字符串,包含了所有的位置参数,每个位置参数由 shell 变量 IFS 的第一个字符(默认为一个空格)分隔开。 |
$@ | 展开成一个从1开始的位置参数列表。当它被用双引号引起来的时候, 它把每一个位置参数展开成一个由双引号引起来的分开的字符串。 |
#!/bin/bash
# shell1.sh
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 "you" "YOU CHAN WILL"
//创建了两个参数: “you” 和 “YOU CHAN WILL”,然后把它们传递pass_params 函数
//再把两个参数传递给 print_params 函数, 使用了特殊参数 $* 和 $@ 提供的四种可用方法
七、字符串和数字
操作字符串和数字的 shell 功能
基本参数展开
$a:当 $a 展开后,会变成变量 a 所包含的值。也可用花括号引起来:${a}
若变量 a 与其它的文本相邻,shell可能会出错,通过添加花括号可以解决这个问题
前面说过 通过把数字包裹在花括号中,可以访问大于9的位置参数。 例如,访问第十一个位置参数:${11}
空变量展开
用来处理不存在和空变量的参数展开形式
${parameter:-word}
//若 parameter没有设置(例如,不存在)或者为空,展开结果是 word 的值
//若 parameter不为空,则展开结果是parameter的值
${parameter:=word}
//若 parameter 没有设置或为空,展开结果是 word 的值,word 的值会赋值给 parameter
//若 parameter 不为空,展开结果是 parameter 的值
位置参数或其它的特殊参数不能以这种方式赋值
${parameter:?word}
//若 parameter 没有设置或为空,这种展开导致脚本带有错误退出,并且 word 的内容会发送到标准错误
//若parameter不为空,展开结果是 parameter的值
${parameter:+word}
//若 parameter 没有设置或为空,展开结果为空
//若 parameter 不为空, 展开结果是 word 的值会替换掉 parameter 的值,但是parameter 的值不会改变
返回变量名的参数展开
shell 具有返回变量名的能力。
${!prefix*}
${!prefix@}
//会返回以 prefix 开头的已有变量名,这两种展开形式的执行结果相同
字符串展开
有大量的展开形式可用于操作字符串。其中许多展开形式尤其适用于路径名的展开。
${#parameter}
//展开成由 parameter 所包含的字符串的长度。通常parameter 是一个字符串
//如果 parameter 是 @ 或者是 * 的话, 则展开结果是位置参数的个数
${parameter:offset}
${parameter:offset:length}
//从 parameter 所包含的字符串中提取一部分字符。提取的字符始于第 offset 个字符(从字符串开头算起)直到字符串的末尾,除非指定提取的长度
//若 offset 的值为负数,则认为 offset 值是从字符串的末尾开始算起,而不是从开头。注意负数前面必须有一个空格, 为防止与 ${parameter:-word} 展开形式混淆。length,若出现,则必须不能小于零
//如果 parameter 是 @,展开结果是 length 个位置参数,从第 offset 个位置参数开始
${parameter#pattern}
${parameter##pattern}
//会从 paramter 所包含的字符串中清除开头一部分文本,这些字符要匹配定义的 patten。pattern 是 通配符模式(在路径名展开中的模式)
//两种形式的差异之处是该 '#' 形式清除最短的匹配结果,'##' 模式清除最长的匹配结果
${parameter%pattern}
${parameter%%pattern}
//和上面的 '#' 和 '##' 展开一样,除了它们清除的文本从 parameter 所包含字符串的末尾开始,而不是开头
${parameter/pattern/string}
${parameter//pattern/string}
${parameter/#pattern/string}
${parameter/%pattern/string}
//对 parameter 的内容执行查找和替换操作。如果找到了匹配通配符 pattern 的文本, 则用 string 的内容替换它
//在正常形式下,只有第一个匹配项会被替换掉。在该 // 形式下,所有的匹配项都会被替换掉
//该 '/#' 要求匹配项出现在字符串的开头,而 '/%' 要求匹配项出现在字符串的末尾。/string 可能会省略掉,会导致删除匹配的文本
字符串操作展开可以用来替换其它常见命令比方说 sed 和 cut。 通过减少使用外部程序,展开提高了脚本的效率
大小写转换
bash 的四个参数展开和 declare 命令的两个选项来进行大小写转换
declare -u upper
declare -l lower
//使用 declare 命令来创建两个变量,upper 和 lower
格式 | 结果 |
---|---|
${parameter,} | 把 parameter 的值全部展开成小写字母。 |
${parameter,} | 仅仅把 parameter 的第一个字符展开成小写字母。 |
${parameter^^} | 把 parameter 的值全部转换成大写字母。 |
${parameter^} | 仅仅把 parameter 的第一个字符转换成大写字母(首字母大写)。 |
算术求值和展开
算术展开被用来对整数执行各种算术运算
$((expression))
// expression 是一个有效的算术表达式。这个与复合命令 (( )) 有关,此命令用做算术求值(true测试)
表示法 | 描述 |
---|---|
number | 默认情况下,没有任何表示法的数字被看做是十进制数(以10为底)。 |
0number | 在算术表达式中,以零开头的数字被认为是八进制数。 |
0xnumber | 十六进制表示法 |
base#number | number 以 base 为底 |
算数运算符 | 描述 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 整除 |
** | 乘方 |
% | 取模(余数) |
shell 算术只操作整形,所以除法运算的结果总是整数,余数显得更为重要
赋值运算
单个 = 运算符执行赋值运算, == 运算符计算等价性
赋值运算表示法 | 描述 |
---|---|
parameter = value | 简单赋值。给 parameter 赋值。 |
parameter += value | 加。等价于 parameter = parameter + value。 |
parameter -= value | 减。等价于 parameter = parameter – value。 |
parameter *= value | 乘。等价于 parameter = parameter * value。 |
parameter /= value | 整除。等价于 parameter = parameter / value。 |
parameter %= value | 取模。等价于 parameter = parameter % value。 |
parameter++ | 后缀自增变量。等价于 parameter = parameter + 1 (但,要看下面的讨论)。 |
parameter– | 后缀自减变量。等价于 parameter = parameter - 1。 |
++parameter | 前缀自增变量。等价于 parameter = parameter + 1。 |
–parameter | 前缀自减变量。等价于 parameter = parameter - 1。 |
自增和自减运算符可能会出现在参数的前面或者后面。都是把参数值加1或减1,这两个位置有个微小的差异。
若运算符放置在参数的前面,参数值会在参数返回之前增加(或减少)。若放置在后面,则运算会在参数返回之后执行。
位运算符
运算符 | 描述 |
---|---|
~ | 按位取反。对一个数字所有位取反 |
<< | 位左移. 把一个数字的所有位向左移动 |
>> | 位右移. 把一个数字的所有位向右移动 |
& | 位与。对两个数字的所有位执行一个 AND 操作 |
^ | 位异或。对两个数字的所有位执行一个异或操作 |
除了按位取反运算符之外,其它所有位运算符都有相对应的赋值运算符(如,<<=)
逻辑运算符
比较运算符 | 描述 |
---|---|
<= | 小于或相等 |
>= | 大于或相等 |
< | 小于 |
> | 大于 |
== | 相等 |
!= | 不相等 |
&& | 逻辑与 |
expr1?expr2:expr3 | 条件(三元)运算符。若表达式 expr1 的计算结果为非零值(算术真),则 执行表达式 expr2,否则执行表达式 expr3。 |
当表达式用于逻辑运算时,表达式遵循算术逻辑规则;表达式的计算结果是零,则认为假,而非零表达式认为真
if ((1)); then echo "true"; else echo "false"; fi
if ((0)); then echo "true"; else echo "false"; fi
//操作三个算术表达式(字符串不会起作用),并且若第一个表达式为真(或非零), 则执行第二个表达式。否则执行第三个表达式
高精度计算器语言(bc)
执行更高级的数学运算或使用浮点数
算术结果在最底部,版权信息之后。可以通过 -q(quiet)选项禁止这些版权信息
可以通过标准输入把一个脚本传递给 bc 程序,使用here 文档,here字符串和管道等来传递脚本
八、数组
数组是一次能存放多个数据的变量
declare -a a
//用 declare 命令的-a参数创建一个数组a
单个值赋值
name[subscript]=value
//name 是数组的名字,subscript 是一个大于或等于零的整数(或算术表达式)
//数组第一个元素的下标是0, 而不是1。数组元素的值可以是一个字符串或整数
多个值赋值
name=(value1 value2 ...)
//name 是数组的名字,value 是要按照顺序赋给数组的值,从元素0开始
数组操作
常见的数组操作(删除数组,确定数组大小,排序……)
下标 * 和 @ 可以被用来访问数组中的每一个元素
bash 允许赋值的数组下标包含 “间隔”,用于确定哪个元素真正存在
${!array[*]}
${!array[@]}
//array 是一个数组变量的名字。和其它使用符号 * 和 @ 的展开一样,用引号引起来的 @ 格式能展开成分离的词
使用 += 赋值运算符,在数组末尾添加元素
a=(a b c)
a+=(d e f)
删除一个数组,使用 unset 命令,数组下标开始于0!
关联数组
关联数组使用字符串而不是整数作为数组索引
必须用带有 -A 选项的 declare 命令创建关联数组
数组和循环经常被一起使用。该形式的循环尤其适合计算数组下标。
for ((expr; expr; expr))
九、shell脚本的特性
一些特性,虽然不常用但是很有效
组命令和子shell
使用group命令或者子shell来将多个命令组合
组命令:{ command1; command2; [command3; ...] } //命令使用花括号包裹,且花括号和命令间加空格,最后一个命令必须用一个分号或者一个换行符终止
子shell:(command1; command2; [command3;...])
一个组命令在当前 shell 中执行它的所有命令,而一个子 shell在当前 shell 的一个子副本中执行它的命令。
子shell中运行环境被复制给了一个新的 shell 实例。当这个子 shell 退出时,环境副本会消失, 在子 shell环境(包括变量赋值)中的任何更改也会消失。
{ command1; command2; command3; } > output.txt
( command1; command2; command3; ) > output.txt
进程替换
在子shell中给变量赋值
<(list) #适用于产生标准输出的进程
>(list) #适用于接受标准输入的进程
# list 是命令列表
进程替换允许把一个子 shell 的输出结果当作一个用于重定向的普通文件
[root@localhost ~]# echo <(echo "foo")
/dev/fd/63
#子shell的输出结果,由一个名为 /dev/fd/63 的文件提供
trap(陷阱)
让脚本响应信号
trap argument signal [signal...]
//argument 是一个字符串(读取并被当作一个命令)
//signal 是一个信号的说明,会触发执行所要解释的命令
trap "echo 'erro'" SIGINT SIGTERM
//当脚本运行的时候,这个陷阱每当接受到一个 SIGINT 或 SIGTERM 信号时,就会执行一个 echo 命令
临时文件的安全性
在脚本执行期间,可能会在 /tmp 目录下创建它们的临时文件,/tmp是 一个服务于临时文件的共享目录。
/tmp目录是共享的,这会引起一定的安全顾虑,除了给这些文件设置合适的权限外,还可以给临时文件一个不可预测的文件名
tempfile=/tmp/$(basename $0).$.$RANDOM
//$RANDOM 变量只能返回一个范围在1-32767内的整数值
tempfile=$(mktemp /tmp/foobar.$.XXXXXXXXX)
//使用 mktemp 程序来命名和创建临时文件。包含一系列的 “X” 字符, 随后这些字符会被相应数量的随机字母和数字替换掉。
[[ -d $HOME/tmp ]] || mkdir $HOME/tmp
//对于那些由普通用户操作执行的脚本,避免使用 /tmp 目录,在家目录下为临时文件创建一个目录
异步执行
多任务处理
wait 命令导致一个父脚本暂停运行,直到一个特定的进程(子脚本)运行结束
child & //子脚本后台运行
pid=$! //记录子脚本的进程ID(pid)
echo "Parent: child (PID= $pid) launched."
wait $pid //wait命令使用子脚本的pid,父脚本暂停运行,直到子脚本退出
echo "Parent: child is finished. Continuing..."
echo "Child is running"
sleep 5
echo "child is done"
命名管道
命名管道用来在两个进程之间建立连接
命令管道的行为类似于文件,形成了先入先出(FIFO)的缓冲。和管道符一样, 数据从一端进入,然后从另一端出现。
rocess1 > named_pipe
process2 < named_pipe
这个过程就像: process1 | process2
mkfifo pipe1 //使用 mkfifo 命令创建命令管道
//属性字段的第一个字母是“p”,表明是一个命名管道
输入一个命令,并把命令的输出重定向到命名管道,命令将会挂起。这是因为在管道的另一端没有任何接受数据(管道阻塞) 当绑定一个进程到管道的另一端,该进程开始从管道中读取输入的时候,这种情况会消失。
第一个终端窗口的目录列表出现在第二个终端中,并作为来自 cat 命令的输出。
十、关于Linux
现在相比于七个月前,经历了很多,也看到了那么多闪闪发光的人。列了很多的学习计划,Linux学习是迈出的第一步,中间确实也有的是想要快点完成文章导致其中的内容没有被消化而囫囵吞枣,但是大体上也还是不错的。中间主要参考了那本《The Linux Command Line》和一些网上的文章资料。
这九天的Linux系统的学习就告一段落。在今年年初的时候就靠连续水了十几天的关于Linux命令的帖子开启了发表博客的道路,现在再去看那些文章空洞且水,技术的沉淀与积累是一个漫长且无法快速的过程,当时有点急功近利,现在也仍无法避免……
Linux的学习没有捷径,唯有不断地练习,在Linux中的命令行现在仅仅接触到了表面,/usr/bin目录下还有很多值得去探索的东西,还有很多特性需要去发掘
学习的过程是枯燥的,之后是备考信息安全工程师,短短的两个半月的时间,明确目标,沉下心来……
CHANWILL YOU
安恒大厦 2021/8/19
路漫漫其修远兮,吾将上下而求索。