Shell 是一个命令行解释器,它接收应用程序/用户的命令,然后调用操作系统内核。此外,Shell也是一个编程语言,也会有诸如流程控制、函数等概念。
脚本格式
#!/bin/bash ... ...脚本以
#!/bin/bash开头,指定解析器。编写
Hello World,vim helloworld.sh#!/bin/bashecho"helloworld"
执行脚本
- 1、采用
bash或sh后跟脚本的相对路径或绝对路径(不用赋予脚本+x 权限)
# 相对路径sh、bash [root@localhost shells]$ sh ./helloworld.sh helloworld [root@localhost shells]$ bash ./helloworld.sh helloworld # 绝对路径 sh、bash [root@localhost shells]$ sh /mydata/helloworld.sh helloworld [root@localhost shells]$ bash /mydata/helloworld.sh helloworld
这种执行方式,本质是由
bash解析器执行脚本,所以脚本本身不需要执行权限。- 2、采用脚本的绝对路径或相对路径执行脚本(必须具有可执行权限+x)
[root@localhost shells]$ chmod +x helloworld.sh # 相对路径 [root@localhost shells]$ ./helloworld.sh helloworld # 绝对路径 [root@localhost shells]$ /mydata/helloworld.sh helloworld
这种执行方式,本质是脚本需要自己执行(“亲历亲为”),因此需要执行器权限。
- 3、在脚本的路径前加上
.或者 source
[root@localhost shells]$ . helloworld.sh helloworld- 1、采用
前两种方式都是在当前 Shell(Shell 进程)中 fork 一个子 Shell 来执行脚本内容,当脚本执行结束,子 Shell 关闭,回到父 Shell 中。
而第三种方式,可以让脚本直接在当前 Shell 执行,无需 fork 子 Shell。典型例子是修改完
/etc/profile文件后,需要source刷新一下环境配置文件。它们的区别在于,环境变量的继承关系。如果在子 Shell 中设置的变量,父 Shell 是不可见的。
常见系统变量有$HOME、$PWD、$SHELL、$USER等。
查看系统变量的值
[root@localhost shells]$ echo$HOME列出当前 Shell 中所有变量
[root@localhost shells]$ set语法:变量名=变量值,如age=18,=前后不能有空格。
定义变量时,变量名不需要加
$,只有在使用时才会在变量名前加$。变量名和等号之间不能有空格。
变量命名规范
命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
中间不能有空格,但可以使用下划线
_连接。不能使用标点符号。
不能使用bash里的关键字(可用
help命令查看保留关键字)。
变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界(推荐给所有变量加上花括号)。
已定义的变量,可以被重新定义。
变量的值如果有空格,需要使用引号将其括起来。
在 bash 中,变量默认类型都是字符串类型,无法直接进行数值运算。
运行shell时,会同时存在三种变量:
局部变量: 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
环境变量: 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
shell变量: shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行。
全局变量和系统变量通常划等号。
[root@localhost shells]$ B=9
# 将变量提升为全局变量,可以被其它 shell 程序访问
[root@localhost shells]$ export B
# 只读变量(静态变量)
[root@localhost shells]$ readonly name='Tommy'
[root@localhost shells]$ name='Jack'
-bash: name: readonly variable
# 定义变量
[root@localhost shells]$ age=18
[root@localhost shells]$ echo$age
18
# 撤销变量
[root@localhost shells]$ unset age
[root@localhost shells]$ echo$age####特殊变量
trap
dirname
basename
envsubst
random/urandom
mknod/mkfifo
exec
pushd/popd
# 单行注释<<EOF多行注释...EOF# EOF 也可以使用其他符号<<!多行注释...!字符串可以用单引号,也可以用双引号,也可以不用引号。
单引号
单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的。
单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。
双引号
双引号里可以有变量。
双引号里可以出现转义字符。
your_name="runoob"# 使用双引号拼接,输出结果为:hello, runoob ! hello, runoob !
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"echo$greeting$greeting_1# 使用单引号拼接,输出结果为:hello, runoob ! hello, ${your_name} !
greeting_2='hello, '$your_name' !'
greeting_3='hello, ${your_name} !'echo$greeting_2$greeting_3string="abcd"echo${#string}# 输出 4# 变量为数组时,${#array} 等价于 ${#array[0]}:
array=(1 2 3 4)
echo${#array[0]}# 输出 1# 计算字符长度也可使用 length。
string="hello,everyone my name is mingming"# string字符串里边有空格,所以需要添加双引号
expr length "$string"# 输出 34# 使用 expr 命令时,表达式中的运算符左右必须包含空格,如果不包含空格,将会输出表达式本身
expr 5+6 # 直接输出 5+6
expr 5 + 6 # 输出 11# 对于某些运算符,还需要我们使用符号"\"进行转义,否则就会提示语法错误
expr 5 * 6 # 输出错误
expr 5 \* 6 # 输出30# 以下实例从字符串第 2 个字符开始截取 4 个字符
string="runoob is a great site"echo${string:1:4}# 输出 unoo# 查找字符 i 或 o 的位置(哪个字母先出现就计算哪个)
string="runoob is a great site"echo`expr index "$string" io`# 输出 4url=https://www.bilibili.com/123.html
# url 是变量名,# 是运算符,*// 表示从左边开始删除第一个 // 符号及左边的所有字符,即删除 https://echo${url#*//}# 结果是 :www.bilibili.com/123.html# ##*/ 表示从左边开始删除最后一个 / 符号及左边的所有字符,即删除 https://www.bilibili.com/echo${url##*/}# 结果是 123.htm# %/* 表示从右边开始,删除第一个 / 符号及右边的字符echo${url%/*}# 结果是:https://www.bilibili.com# %%/* 表示从右边开始,删除最后一个 / 符号及右边的字符echo${var%%/*}# 结果是:https:# 0 表示从左边第一个字符开始,5 表示字符的总个数echo${var:0:5}# 结果是:https:# 从左边第几个字符开始(从指定第几个字符 +1 开始),一直到结束echo${var:7}# 结果是 :www.bilibili.com/123.html# 0-7 表示从右边算起第七个字符结束,3 表示字符个数echo${var:0-7:3}# 结果是:123# 表示从右边第七个字符开始,一直到结束。(字符截取顺序从左到右)echo${var:0-7}# 结果是:123.htm#、##表示从左边开始删除,一个#表示从左边删除到第一个指定的字符;两个#表示从左边删除到最后一个指定的字符。%、%%表示从右边开始删除。一个%表示从右边删除到第一个指定的字符;两个%表示从右边删除到最后一个指定的字符。删除包括了指定的字符本身。
# 使用 string/pattern/string(source/pattern/target) 进行首个 pattern 的替换
string="text, dummy, text, dummy"echo${string/text/TEXT}# TEXT, dummy, text, dummy# 使用 string//pattern/string 进行全部 pattern 的替换
string="text, dummy, text, dummy"echo${string//text/TEXT}# TEXT, dummy, TEXT, dummy获取键盘输入信息
语法:read [-options] [variable...]
# -p 参数由于设置提示信息read -p "input a val:" a #获取键盘输入的 a 变量数字read -p "input b val:" b #获取键盘输入的 b 变量数字
r=$[a+b] #计算a+b的结果 赋值给r 不能有空格echo"result = ${r}"#输出显示结果 r向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,其中 $0 为执行的文件名(包含文件路径),1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推......
[root@localhost shells]$ vim test.sh
echo"Shell 传递参数实例!";echo"执行的文件名: $0";echo"第一个参数为: $1";echo"第二个参数为: $2";echo"第三个参数为: $3";
[root@localhost shells]$ chmod +x test.sh [root@localhost shells]$ ./test.sh 1 2 3
Shell 传递参数实例!
执行的文件名: ./test.sh
第一个参数为: 1
第二个参数为: 2
第三个参数为: 3| 参数表现形式 | 说明 |
|---|---|
$# | 传递到脚本的参数个数 |
$* | 以一个单字符串显示所有向脚本传递的参数,如果$*存在于双引号内部,则其表现形式为"$1 $2 $3 ... $n" |
$$ | 脚本运行的当前进程ID号 |
$! | 后台运行的最后一个进程的ID号 |
$@ | 与$*相同,但如果$@存在于双引号内部,则其表现形式为"$1" "$2" ... "$n",数组形式 |
$- | 显示Shell使用的当前选项,与set命令功能相同 |
$? | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误 |
演示
echo"Shell 传递参数实例!";echo"第一个参数为:$1";echo"参数个数为:$#";echo"传递的参数作为一个字符串显示:$*";
$ chmod +x test.sh $ ./test.sh 1 2 3
Shell 传递参数实例!
第一个参数为:1
参数个数为:3
传递的参数作为一个字符串显示:1 2 3bash 仅支持一维数组,且没有限定数组的容量。
数组元素的下标由 0 开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于 0。
在 Shell 中,用括号来表示数组,数组元素用"空格"符号分割开。定义数组的一般形式为: 数组名=(值1 值2 ... 值n)
array_name=(value0 value1 value2 value3)
# 或者
array_name=(
value0
value1
value2
value3
)
# 还可以单独定义数组的各个分量。可以不使用连续的下标,而且下标的范围没有限制。
array_name[0]=value0
array_name[1]=value1
array_name[n]=valuen# ${数组名[索引]}
value=${array_name[n]}# 使用 @ 符号可以获取数组中的所有元素echo${array_name[@]}# 获取数组长度的方法与获取字符串长度的方法相同# 取得数组元素的个数
length=${#array_name[@]}# 或者
length=${#array_name[*]}# 取得数组单个元素的长度
length=${#array_name[n]}其表现形式类似 map
语法:declare -A array_name。-A 选项就是用于声明一个关联数组。关联数组的键是唯一的。
# 创建一个关联数组 site,并创建不同的键值declare -A site=(["A"]="1" ["B"]="2" ["C"]="3")
# 也可以先声明一个关联数组,然后再设置键和值declare -A site
site["A"]="1"
site["B"]="2"
site["C"]="3"# 访问关联数组元素可以使用指定的键echo${site["B"]}# 执行脚本,输出结果:2# 在数组前加一个感叹号!可以获取数组的所有键echo"数组的键为: ${!site[*]}"# A B Cecho"数组的键为: ${!site[@]}"# A B Cwords="aaa bbb ccc"# 字符串转数组,空格是分隔符
array=(${words/// }) # 打印数组最后一个成员echo${array[${#array[*]}-1]} # ccc# 打印数组长度echo${#array[*]}# 3# 字符串不转换为数组,在循环实现以空格为分隔符打印每个成员forwordin${words};doecho${word}# aaa \n bbb \n cccdone原生bash不支持简单的数学运算,所以需要使用第三方的命令来实现,例如 awk 和 expr。
expr是一个表达式计算工具,awk是文本处理工具。
val=`expr 2 + 2`echo"两数之和为 : $val"# 两数之和为 : 4表达式和运算符之间要有空格,如2 + 2。
| 运算符 | 描述 | eg |
|---|---|---|
| 算数运算符 | 假定变量 a 为 10,变量 b 为 20 | |
| + | expr $a + $b 结果为 30 | |
| - | expr $a - $b 结果为 -10 | |
| * | expr $a * $b 结果为 200 | |
| / | expr $a / $b 结果为 0.5 | |
| % | expr $a % $b 结果为 10 | |
| = | a=$b 把变量 b 的值赋值给 a | |
| == | 用于比较两个数字,相同则返回 true | expr $a == $b 结果为 false |
| != | 用于比较两个数字,不相同则返回 true | expr $a != $b 结果为 true |
| 关系运算符 | ||
| -eq | == | [ $a -eq $b ] 返回 false |
| -ne | != | [ $a -ne $b ] 返回 true |
| -gt | > | [ $a -gr $b ] 返回 false |
| -lt | < | [ $a -lt $b ] 返回 true |
| -ge | >= | [ $a -ge $b ] 返回 false |
| -le | <= | [ $a -le $b ] 返回 true |
| 布尔运算符 | ||
| ! | 非运算 | [ !false ] 返回true |
| -o | 或运算 | [ $a -lt 20 -o $b -gt 100 ] 返回 true |
| -a | 与运算 | [ $a -lt 20 -a $b -gt 100 ] 返回 false |
| 逻辑运算符 | ||
| && | 逻辑 AND | [[ $a -lt 100 && $b -gt 100 ]] 返回 false |
| 逻辑 OR | ||
| 字符串运算符 | 假定变量 a 为 "abc",变量 b 为 "efg" | |
| = | 比较两个字符串是否相等,相等返回 true | [ $a = $b ] 返回 false |
| != | 比较两个字符串是否不相等,不相等返回 true | [ $a != $b ] 返回 true |
| -z | 字符串长度是否为0,为0返回 true | [ -z $a ] 返回 false |
| -n | 字符串长度是否不为 0,不为 0 返回 true | [ -n "$a" ] 返回 true |
| $ | 字符串是否不为空,不为空返回 true | [ $a ] 返回 true |
| 文件运算符 | ||
-b file | 检测文件是否是块设备文件,如果是,则返回 true | |
-c file | 检测文件是否是字符设备文件,如果是,则返回 true | |
-d file | 检测文件是否是目录,如果是,则返回 true | |
-f file | 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true | |
-g file | 检测文件是否设置了 SGID 位,如果是,则返回 true | |
-k file | 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true | |
-p file | 检测文件是否是有名管道,如果是,则返回 true | |
-u file | 检测文件是否设置了 SUID 位,如果是,则返回 true | |
-r file | 检测文件是否可读,如果是,则返回 true | |
-w file | 检测文件是否可写,如果是,则返回 true | |
-x file | 检测文件是否可执行,如果是,则返回 true | |
-s file | 检测文件是否为空(文件大小是否大于0),不为空返回 true | |
-e file | 检测文件(包括目录)是否存在,如果是,则返回 true |
条件表达式要放在方括号之间,并且要有空格。如[ $a == $b ]。
逻辑运算符:||(逻辑OR),
[[ $a -lt 100 || $b -gt 100 ]]返回 true
# ifif condition
then
command1 command2
...
commandN fi# if elseif condition
then
command1 command2
...
commandN
elsecommandfi# if else-if elseif condition1
then
command1
elif condition2 then command2
else
commandN
fiif else 的 [...] 判断语句中大于使用 -gt,小于使用 -lt。如果使用((...))将其括起来,大于和小于可以直接使用 > 和 <。
a=10
b=20
if [ $a==$b ]
thenecho"a 等于 b"elif(($a>$b))thenecho"a 大于 b"elif [ $a-lt$b ]
thenecho"a 小于 b"elseecho"没有符合的条件"fifor变量名in item1 item2 ... itemN
do
command1
command2
...
commandN
done# 或者for变量名in 数组名
do
command1
command2
...
commandN
done示例
forloopin 1 2 3 4 5
doecho"The value is: $loop"done# 输出结果:
The value is: 1
The value is: 2
The value is: 3
The value is: 4
The value is: 5
forstrin This is a string
doecho$strdone# 输出结果:
This
is
a
stringwhile condition
docommanddone示例
a=1
while(($a<=5))doecho$alet"a++"done# 输出结果
1
2
3
4
5until循环的循环条件与while循环恰好相反,until循环的循环条件为 true 时停止循环,而while循环则是为 false 时停止循环。
一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用。
# condition 一般为条件表达式,如果返回值为 false,则继续执行循环体内的语句,否则跳出循环。until condition docommanddone示例
a=0
# a < 10until [ !$a-lt 10 ]
doecho$a
a=`expr $a + 1`done# 输出结果为:
0
1
2
3
4
5
6
7
8
9类似switch case分支选择结构。
case 变量 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac示例
echo'输入 1 到 4 之间的数字:'echo'你输入的数字为:'read aNum
case$aNumin
1) echo'你选择了 1'
;;
2) echo'你选择了 2'
;;
3) echo'你选择了 3'
;;
4) echo'你选择了 4'
;;
*) echo'你没有输入 1 到 4 之间的数字'
;;
esac如果没有匹配到分支,可以用*来执行默认分支逻辑,类似default。
break 命令允许跳出所有循环(终止执行后面的所有循环)。
continue 命令与 break 命令类似,它不会跳出所有循环,仅仅跳出当前循环。进入下一个循环。
#!/bin/bashwhile:doecho -n "输入 1 到 5 之间的数字:"read aNum
case$aNumin
1|2|3) echo"你输入的数字为 $aNum!"continue
;;
4|5) echo"你输入的数字为 $aNum!"
;;
*) echo"你输入的数字不是 4 到 5 之间的!"break
;;
esacdone定义格式
[ function] funname [()]
{
action;
[return int;]
}
可以带function fun() 定义,也可以直接fun() 定义。默认返回值将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255)
funWithParam(){
echo"第一个参数为 $1 !"echo"第二个参数为 $2 !"echo"第十个参数为 $10 !"echo"第十个参数为 ${10} !"echo"第十一个参数为 ${11} !"echo"参数总数有 $# 个!"echo"作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
# 输出结果:
第一个参数为 1 !
第二个参数为 2 !
第十个参数为 10 !
第十个参数为 34 !
第十一个参数为 73 !
参数总数有 11 个!
作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 !$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数。
[root@localhost shells]$ cat /etc/passwd | grep ^a # 会匹配出所有以 a 开头的行[root@localhost shells]$ cat /etc/passwd | grep t$
# 会匹配出所有以 t 结尾的行[root@localhost shells]$ cat /etc/passwd | grep r..t
# 会匹配包含 rabt,rbbt,rxdt,root 等的所有行配合左边相邻的字符连用,表示匹配左边相邻的字符0次或多次。
[root@localhost shells]$ cat /etc/passwd | grep ro*t
# 会匹配 rt, rot, root, rooot, roooot 等所有行[ ]表示匹配某个范围内的一个字符[6,8]------ 匹配 6 或者 8[0-9]------ 匹配一个 0-9 的数字[0-9]*------ 匹配任意长度的数字字符串[a-z]------匹配一个 a-z 之间的字符[a-z]*------ 匹配任意长度的字母字符串[a-c, e-f]----- 匹配 a-c 或者 e-f 之间的任意字符
cut 命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段写至标准输出。
语法:cut [参数] [file]。
如果不指定 File 参数,cut 命令将读取标准输入。必须指定 -b、-c 或 -f。
| 参数 | 说明 |
|---|---|
| -b | 以字节为单位进行分割。这些字节位置将忽略多字节字符边界,除非也指定了 -n 标志。 |
| -c | 以字符为单位进行分割。 |
| -d | 自定义分隔符,默认为制表符。 |
| -f | -d一起使用,指定显示哪个区域。 |
| -n | 取消分割多字节字符。仅和 -b 标志一起使用。如果字符的最后一个字节落在由 -b 标志的 List 参数指示的范围之内,该字符将被写出;否则,该字符将被排除 |
示例
# 当执行who命令时,会输出类似如下的内容:
$ who
rocrocket :0 2009-01-08 11:07
rocrocket pts/0 2009-01-08 11:23 (:0.0)
rocrocket pts/1 2009-01-08 14:15 (:0.0)
# 如果我们想提取每一行的第3个字节,如下:
$ who|cut -b 3
c
csed 主要用来批量新增、替换、删除多个文件中指定的字符。
awk 是一种处理文本文件的语言,是一个强大的文本分析工具,把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理。