常用的Shell
Shell 既是一种脚本编程语言,也是一个连接内核和用户的软件。
常见的 Shell 有 sh、bash、csh、tcsh、ash 等。
bash shell 是 Linux 的默认 shell,bash 由 GNU 组织开发,保持了对 sh shell 的兼容性,是各种 Linux 发行版默认配置的 shell。
bash 兼容 sh 意味着,针对 sh 编写的 Shell 代码可以不加修改地在 bash 中运行。
尽管如此,bash 和 sh 还是有一些不同之处:
- 一方面,bash 扩展了一些命令和参数;
- 另一方面,bash 并不完全和 sh 兼容,它们有些行为并不一致,但在大多数企业运维的情况下区别不大,特殊场景可以使用 bash 代替 sh。
查看Shell
Shell是一个程序,一般放在/bin
或/user/bin
目录下,Linux系统可用的Shell都可以使用下面命令查看
user@PxeCtrlSys:~$ cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/dash
/bin/bash
/bin/rbash
现在Linux基本都是用的bash Shell,查看Linux的默认shell
user@PxeCtrlSys:~$ echo $SHELL
/bin/bash
运行方法
- 作为可执行程序
代码保存test.sh
文本中
# 进入脚本所在目录,让脚本拥有可执行权限
chmod +x ./test.sh
# 执行方式
./test.sh
一定要写成./test.sh
,而不是test.sh
,运行其它二进制的程序也一样,直接写 test.sh
,linux 系统会去 PATH 里寻找有没有叫 test.sh
的,而只有 /bin
, /sbin
, /usr/bin
,/usr/sbin
等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 test.sh
是会找不到命令的,要用 ./test.sh
告诉系统说,就在当前目录找。
- 作为解释器参数
直接运行解释器,其参数就是 shell 脚本的文件名
/bin/sh test.sh
# 或者
/bin/bash test.sh
Shell变量
在Bash Shell中,每一个变量的值都是字符串,无论给变量赋值时有没使用引号,值都会以字符串的形式存储,即使将整数或小数赋值给变量,他们也会被视为字符串。
变量赋值
定义变量,三种定义方式,显式赋值
name="test"
name='test'
name=test
注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样。同时,变量名的命名须遵循如下规则:
- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
- 中间不能有空格,可以使用下划线(_)。
- 不能使用标点符号。
- 不能使用bash里的关键字(可用help命令查看保留关键字)。
如果变量值包含空白字符,就必须使用引号包围起来,例如name="bom smith"
用语句给变量赋值
for file in `ls /etc`
或
for file in $(ls /etc)
将/etc
目录下的文件名循环出来。
使用变量
name="test"
echo $name
# 或者加花括号,含义一样,加花括号是为了帮助解释器识别变量的边界
echo ${name}
例如和变量连接的字符
#!/bin/bash
for item in Python Java Shell Bat; do
echo "This is ${item}Script"
done
运行结果如下
This is PythonScript
This is JavaScript
This is ShellScript
This is BatScript
如果没有花括号,那么变量就是$itemScript
,其值为空,结果就会错误。推荐加花括号{}
。
修改变量的值
已定义过的变量可以重新赋值
#!/bin/bash
name='test'
echo ${name} # test
name="new"
echo $name # new
变量赋值不能加$
符号,只有在使用键盘时才能加$
。
命令执行结果赋值给变量
支持将命令的执行结果赋值给变量使用,常见的两种方式:
value=`command`
# 或
value=$(command)
# 默认输出以空白符填充
echo $value
# 按照原格式输出,例如有换行符的
echo "$value"
第一种是使用反引号包围起来,第二种是使用$()
包围起来(推荐使用第二种方式)。command
是执行命令。
user@PxeCtrlSys:~$ res=`who`
user@PxeCtrlSys:~$ echo $res
user pts/0 2019-03-11 09:04 (192.168.96.16) user pts/1 2019-03-11 09:04 (192.168.96.16)
user@PxeCtrlSys:~$ res=$(ls /)
user@PxeCtrlSys:~$ echo $res
bin boot dev etc home initrd.img lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var vmlinuz
执行结果赋值给变量,输出不会自动添加换行符。
如果被替换的命令的输出内容包括多行(也即有换行符),或者含有多个连续的空白符,那么在输出变量时应该将变量用双引号包围,否则系统会使用默认的空白符来填充,这会导致换行无效,以及连续的空白符被压缩成一个。请看下面的代码:
user@PxeCtrlSys:~$ res=`who`
# 不用引号包围变量,输出不会自动换行
user@PxeCtrlSys:~$ echo $res
user pts/0 2019-03-11 09:04 (192.168.96.16) user pts/1 2019-03-11 09:04 (192.168.96.16)
# 使用引号包围变量,输出按照原来的格式
user@PxeCtrlSys:~$ echo "$res"
user pts/0 2019-03-11 09:04 (192.168.96.16)
user pts/1 2019-03-11 09:04 (192.168.96.16)
为了防止出现格式混乱的情况,建议在输出变量时加上双引号。
只读变量
使用 readonly
命令可以将变量定义为只读变量,只读变量的值不能被改变。
下面的例子尝试更改只读变量,结果报错:
#!/bin/bash
name='test'
readonly name
name='new'
echo $name
运行结果
test
/tmp/280278210/main.sh: line 4: name: readonly variable
# 当改为只读后,再次赋值就会报错
删除变量
使用 unset
命令可以删除变量
#!/bin/bash
name='test'
unset name
echo $name # 执行没有输出
变量删除后不能再次使用。unset
命令不能删除只读变量。
变量类型,作用域
运行shell时,会同时存在三种变量:
- 全局变量 指变量在当前的整个 Shell 会话中都有效。每个 Shell 会话都有自己的作用域,彼此之间互不影响。在 Shell 中定义的变量,默认就是全局变量。
- 局部变量 在 Shell 函数中定义的变量默认也是全局变量,它和在函数外部定义变量拥有一样的效果;要想变量的作用域仅限于函数内部,那么可以在定义时加上
local
命令,此时该变量就成了局部变量。 - 环境变量 只在当前 Shell 会话中有效,如果使用export命令将它导出,那么它就在所有的子 Shell 中也有效了。
Shell字符串
字符串最常用的数据类型,可以用单/双引号,也可以不用引号。
单引号
str='this is a string'
单引号字符串的限制:
- 单引号里面的任何字符都会原样输出,单引号字符串里面的变量是无效的,也就是变量那一串就只是字符串;
- 单引号字符中不能出现单独一个的单引号(对单引号使用转义符后也不行),但能成对出现,相当于字符串拼接
s='abc\'0e'
,这个就会报错,而s='abc\'0'e'
会输出abc\0e
双引号
- 双引号中可以有变量,输出时会先解析里面的变量和命令
- 双引号也可以出现转义字符
#!/bin/bash
name='lr'
echo $name
s1="my name is $name"
echo $s1
s2="my name is "$name""
echo $s2
s3="my name is \"$name\""
echo $s3
# 运行结果
lr
my name is lr
my name is lr
my name is "lr"
单/双引号使用场景
建议:
- 如果变量的值是数字,那么不加引号
a=1
; - 如果要将字符串原样输出,就用反引号
str='单引号中原样输出${test}'
; - 没有特别要求最好都是用双引号(最常见)。
拼接字符串
字符串的拼接(也称字符串连接或者字符串合并),在 Shell 中你不需要使用任何运算符,将两个字符串并排放在一起就能实现拼接。
#!/bin/bash
name='lr'
s1="hello, $name" # 双引号中可以直接使用变量
echo $s1
s2="hello, "$name"" # 相当于两对双引号拼接
echo $s2
s3='hello, $name' # 单引号中变量都当作普通字符串
echo $s3
s4='hello, "$name"'
echo $s4
s5='hello, '$name'' # 相当于两对单引号和变量进行拼接
echo $s5
nick='xy'
s6=$nick$name # 中间不能有空格
s7="$nick $name" # 如果是双引号包围,就允许中间有空格
s8=$nick"..."$name # 中间可以出现其他字符串
s9="$nick...$name" # 中间也可以这样写
s10="昵称${nick}...${name}名字"
echo $s6
echo $s7
echo $s8
echo $s9
echo $s10
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
hello, lr
hello, lr
hello, $name
hello, "$name"
hello, lr
xylr
xy lr
xy...lr
xy...lr
昵称xy...lr名字
获取字符串长度
使用${#变量名}
获取长度
#!/bin/bash
name='lr'
echo ${#name}
# 运行结果
2
截取字符串
Shell 截取字符串通常有两种方式:从指定位置开始截取和从指定字符(子字符串)开始截取。
从指定位置开始截取
种方式需要两个参数:除了指定起始位置,还需要截取长度,才能最终确定要截取的字符串。
从字符串左边开始计数,格式为${string: start :length}
#!/bin/bash
str="I love Python, but I need study Shell"
echo ${str:2:7} # 下标以0开始,从第2个开始截取,共截取7个字符
echo ${str:19} # 省略长度,直接从指定位置截取到最后
# 运行结果
love Py
I need study Shell
从右边开始计数,格式为${string: 0-start :length}
多了0-
,这是固定的写法,专门用来表示从字符串右边开始计数。
- 从左边开始计数时,起始数字是 0(这符合程序员思维);从右边开始计数时,起始数字是 1(这符合常人思维)。计数方向不同,起始数字也不同。
- 不管从哪边开始计数,截取方向都是从左到右。
echo ${str:0-5:2} # 从右向左数第5个,长度为2的字符串
echo ${str:0-5} # 省略长度,直接从指定位置截取到最后
# 运行结果
Sh
Shell
从指定字符(子字符串)开始截取
这种截取方式无法指定字符串长度,只能从指定字符(子字符串)截取到字符串末尾。Shell 可以截取指定字符(子字符串)右边的所有字符,也可以截取左边的所有字符。
使用 # 号截取右边字符,格式为${string#*chars}
其中,string
表示要截取的字符,chars
是指定的字符(或者子字符串),*
是通配符的一种,表示任意长度的字符串。*chars
连起来使用的意思是:忽略左边的所有字符,直到遇见 chars
(chars
不会被截取)。
#!/bin/bash
str="I love Python, but I need study Shell"
echo ${str#*,} # 以 , 分割,取右边
echo ${str#*Python,} # 以Python , 分割取右边
echo ${str#I love} # 如果不需要忽略左边的字符,那么也可以不写*
echo ${str#*o} # 以o分割,因为字符串有好两个o,那么遇到第一个就结束了
echo ${str##*o} # 使用两个#,就可以匹配到最后一个指定字符(子字符串)右方内容
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
but I need study Shell
but I need study Shell
Python, but I need study Shell
ve Python, but I need study Shell
n, but I need study Shell
如果希望直到最后一个指定字符(子字符串)再匹配结束,那么可以使用##,具体格式为:${string##*chars}
使用 % 截取左边字符,格式为${string%chars*}
#!/bin/bash
str="I love Python, but I need study Shell"
echo ${str%,*} # 以,分割,取左边
echo ${str%Python,*} # 以Python,分割取左边
echo ${str%Shell} # 如果不需要忽略右边的字符,那么也可以不写*
echo ${str%o*} # 以o分割,因为字符串有好两个o,从右往左,那么遇到第一个就结束了
echo ${str%%o*} # 使用两个%%,就可以匹配到从右往左的最后一个指定字符(子字符串)左方内容
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
I love Python
I love
I love Python, but I need study
I love Pyth
I l
截取字符串汇总
格式 | 说明 |
---|---|
${string: start :length} | 从 string 字符串的左边索引为 start 的字符开始,向右截取 length 个字符。 |
${string: start} | 从 string 字符串的左边索引为 start 的字符开始截取,直到最后。 |
${string: 0-start :length} | 从 string 字符串的右边第 start 个字符开始,向右截取 length 个字符。 |
${string: 0-start} | 从 string 字符串的右边第 start 个字符开始截取,直到最后。 |
${string#*chars} | 从 string 字符串第一次出现 *chars 的位置开始,截取 *chars 右边的所有字符。 |
${string##*chars} | 从 string 字符串最后一次出现 *chars 的位置开始,截取 *chars 右边的所有字符。 |
${string%*chars} | 从 string 字符串第一次出现 *chars 的位置开始,截取 *chars 左边的所有字符。 |
${string%%*chars} | 从 string 字符串最后一次出现 *chars 的位置开始,截取 *chars 左边的所有字符。 |
查找字符串
#!/bin/bash
str="I love Python, but I need study Shell"
echo `expr index "$str" SP` # 查找字符S或者P最先出现的位置,下标以1开始,第8个字符为P,就输入了8
# 运行结果
8
以上脚本中 ` 是反引号,而不是单引号 '
Shell数组
bash支持一维数组(但不支持多维数组),并且没有限制数组大小。数组元素的下标从0开始,获取数组元素使用小标,下标可以试整数或算术表达式,其值应大于等于0。
定义数组
用括号表示数组,数组元素用空格
符号分隔开。
数组名=(值1 值2 值3 ... 值n)
或者
array_name=(
value0
value1
...
valuen
)
还可以单独定义数组的各个分量
array_name[0]=value0
array_name[1]=value1
array_name[n]=valuen
可以不使用连续的下标,而且下标的范围没有限制
array=([2]=12 [5]=99)
echo ${array[0]} # 为空值
echo ${array[2]} # 输出值为12
array[0]=5 # 赋值一个
echo ${array[0]} # 输出值为5
# 获取数组的所有元素
echo ${array[*]}
5 12 99
# 获取数组长度
echo ${#array[*]}
3
读取数组
一般格式
${数组名[下标]}
例如valuen=${array_name[n]}
,输出数组所有元素用echo ${array_name[@]}
#!/bin/bash
arr[0]=1
arr[2]=2
echo ${arr[@]} # 使用 @ 符号可以获取数组中的所有元素: 1 2
echo ${arr[2]} # 2
echo ${arr[1]} # 没有的值获取为空
使用@
或*
可以获取数组中的所有元素。
获取数组的长度
所谓数组长度,就是数组元素的个数。
利用@
或*
,可以将数组扩展成列表,然后使用#
来获取数组元素的个数,格式为 ${#array_name[@]}
或 ${#array_name[*]}
,其中array_name
表示数组名。两种形式是等价的。
#!/bin/bash
arr=(1 2 3 "abc")
echo ${#arr[@]} # 获取数组的长度:4
echo ${#arr[*]} # *也可以:4
echo ${#arr[3]} # 获取数组中单独元素的长度:3
获取数组中元素为字符串的长度${#arr[2]}
(假设下标为2的元素为字符串),因为获取字符串长度的方法是 ${#string_name}
删除数组的元素或数组
使用 unset arr[index]
删除数组元素,使用 unset arr
删除整个数组,所有元素都会消失。
#!/bin/bash
arr=(1 2 3 "abc")
echo ${#arr[@]} # 获取数组的长度:4
echo 原来数组的长度为:${#arr[*]} # *也可以:4
echo ${#arr[3]} # 获取数组中单独元素的长度:3
unset arr[1]
echo "删除第二个元素后长度为:${#arr[*]}"
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
4
原来数组的长度为:4
3
删除第二个元素后长度为:3
数组拼接、合并
将两个数组连接成一个数组。
拼接数组的思路是:先利用@或*,将数组扩展成列表,然后再合并到一起。格式为 array_new=(${array1[@]} ${array2[@]})
或 array_new=(${array1[*]} ${array2[*]})
#!/bin/bash
arr1=(1 2 3 "abc")
arr2=(66)
echo "数组1:${arr1[*]}"
echo "数组2:${arr2[@]}"
new_arr=(${arr1[*]} ${arr2[@]})
echo "合并后的数组为:${new_arr[*]}"
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
数组1:1 2 3 abc
数组2:66
合并后的数组为:1 2 3 abc 66
Shell注释
以#
开始的行就是注释,会被解释器忽略
通过每一行添加一个#
来设置多行注释
如果在开发过程中,遇到大段的代码需要临时注释起来,过一会儿又取消注释,怎么办呢?
每一行加个#
符号太费力了,可以把这一段要注释的代码用一对花括号括起来,定义成一个函数,没有地方调用这个函数,这块代码就不会执行,达到了和注释一样的效果。
多行注释
:<<EOF
注释内容...
注释内容...
注释内容...
EOF
EOF
也可以使用其他符号
:<<'
注释内容...
注释内容...
注释内容...
'
:<<!
注释内容...
注释内容...
注释内容...
!
Shell脚本传递参数
想脚本传递参数,脚本内获取参数的格式为:$n
。n
代表一个数字,1为执行脚本的第一个参数,2为执行脚本的第二个参数,以此类推...
给脚本文件传递参数
向脚本传递3个参数,其中$0
表示执行的文件名
user@PxeCtrlSys:~$ vim test.sh
user@PxeCtrlSys:~$ chmod +x test.sh # 添加执行权限
user@PxeCtrlSys:~$ ./test.sh
执行的文件名:./test.sh
传递的第一个参数:
传递的第二个参数:
传递的第三个参数:
所有参数:
user@PxeCtrlSys:~$ ./test.sh 1 2
执行的文件名:./test.sh
传递的第一个参数:1
传递的第二个参数:2
传递的第三个参数:
所有参数:1 2
user@PxeCtrlSys:~$ ./test.sh 1 2 3
执行的文件名:./test.sh
传递的第一个参数:1
传递的第二个参数:2
传递的第三个参数:3
所有参数:1 2 3
# test.sh文件内容
#!/bin/bash
echo "执行的文件名:$0"
echo "传递的第一个参数:$1"
echo "传递的第二个参数:$2"
echo "传递的第三个参数:$3"
echo "所有参数:$*"
如果参数个数太多,达到或者超过了 10 个,那么就得用${n}
的形式来接收了,例如 ${10}
、${23}
。{ }
的作用是为了帮助解释器识别参数的边界,这跟使用变量时加{ }
是一样的效果。
特殊字符处理参数(特殊变量)
参数处理 | 说明 |
---|---|
$# | 获取传递到脚本的参数个数 |
$* | 以一个单字符串显示所有向脚本传递的参数,如$* 就和"$1 $2 $3 ... $n" 输出形式一样 |
$$ | 脚本运行的当前ID号 |
$! | 后台运行的最后一个进程的ID号 |
$@ | 与$* 相同,但是使用时加括号,斌仔引号中返回每个参数,"$1" "$2" … "$n" 形式 |
$- | 显示Shell使用的当前选项,与set命令功能相同 |
$? | 显示最后命令的退出状态。0表示没有错误,其他任何值表示有错误 |
@ 的区别
- 相同点:都是引用所有参数
- 不同点:只有在双引号中提现出来。假设在脚本运行时写了三个参数1、2、3,则
*
等价于"1 2 3"
(传递了一个参数:会将所有的参数从整体上看做一份数据,而不是把每个参数都看做一份数据。),而@
等价于"1" "2" "3"
(仍然将每个参数都看作一份数据,彼此之间是独立的)
#!/bin/bash
echo "执行的文件名:$0"
echo "传递的第一个参数:$1"
echo "传递的第二个参数:$2"
echo "传递的第三个参数:$3"
echo "\$*演示"
for i in "$*";do
echo $i
done
echo "\$@演示"
for j in $@;do
echo $j
done
# 运行结果
user@PxeCtrlSys:~$ ./test.sh 1 2 3
执行的文件名:./test.sh
传递的第一个参数:1
传递的第二个参数:2
传递的第三个参数:3
$*演示 # 只循环了一次,因为$*当作一个参数
1 2 3
$@演示 # 循环多次,$@当作多个参数
1
2
3
Shell函数
shell函数格式
[ function ] funname [()]
{
action;
[return int;]
}
- 可以带
function func()
定义,也可以直接func()
定义,不带任何参数。 - 参数返回,可以显示加:
return
返回,如果不加,将以最后一条命令运行结果,作为返回值。return
后跟数值n(0-255)
无返回值函数
#!/bin/bash
func(){
echo "这是一个函数中的内容"
}
echo "开始调用函数"
func
echo "调用函数完成"
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
开始调用函数
这是一个函数中的内容
调用函数完成
有返回值函数
定义一个带return
的函数
#!/bin/bash
func(){
echo "请输入两个数,执行加法"
echo -n "请输入第一个数:"
read num1
echo -n "请输入第二个数:"
read num2
# return $[ $num1 + $num2]
# 下方表达式也正确
return $(( $num1 + $num2 ))
}
echo "开始调用函数"
func
echo "函数返回值为 $?"
echo "调用函数完成"
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
开始调用函数
请输入两个数,执行加法
请输入第一个数:2
请输入第二个数:3
函数返回值为 5
调用函数完成
函数返回值在调用该函数后通过 $?
来获得。
所有函数在使用前必须定义。这就是说必须将函数放在脚本开始部分,直到shell解释器首次发现它时才可以使用。调用函数仅使用其函数名即可。
$? 使用方法:返回值或退出状态
$?
用来获取函数返回值或者上一个命令的退出状态
每一条 Shell 命令,不管是 Bash 内置命令(例如 test
、echo
),还是外部的 Linux 命令(例如 cd
、ls
),还是自定义的 Shell 函数,当它退出(运行结束)时,都会返回一个比较小的整数值给调用(使用)它的程序,这就是命令的退出状态(exit status)。
if
语句的判断条件,从本质上讲,判断的就是命令的退出状态。
$? 获取上一次命令退出状态
退出状态,就是上一个命令执行后的返回结果。退出状态是一个数字,一般情况下,大部分命令执行成功会返回 0,失败返回 1,这和C语言的 main() 函数是类似的。
不过,也有一些命令返回其他值,表示不同类型的错误。
#!/bin/bash
echo -n 请输入a:
read a
echo -n 请输入b:
read b
(( $a == $b ));
echo "退出状态:$?"
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
请输入a:1
请输入b:2
退出状态:1
user@PxeCtrlSys:~$ echo $?
0
user@PxeCtrlSys:~$ ./test.sh
请输入a:6
请输入b:6
退出状态:0
user@PxeCtrlSys:~$ echo $?
0
$? 获取函数的返回值
给函数传递参数
Shell调用函数也可以向其传递参数。在函数体内部,通过 $n
的形式来传递参数。例如$1
表示第一个参数。$2
表示第二次参数
#!/bin/bash
func(){
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "第二个参数:${2}"
echo "第五个参数:$5"
echo "第10个参数:$10" # 这个相当于第一个参数$1连接一个0
echo "第10个参数:${10}"
echo "第11个参数:$11" # 相当于第一个参数$1连接一个1
echo "第11个参数:${11}"
echo "参数总数有 $# 个"
echo "作为一个地府传输出所有参数:$*"
}
echo "开始调用函数"
func 0 2 3 4 5 23 36 65 99 123
echo "函数返回值为 $?"
echo "调用函数完成"
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
开始调用函数
第一个参数:0
第二个参数:2
第二个参数:2
第五个参数:5
第10个参数:00
第10个参数:123
第11个参数:01
第11个参数:
参数总数有 10 个
作为一个地府传输出所有参数:0 2 3 4 5 23 36 65 99 123
函数返回值为 0
调用函数完成
$10
不能获取到第10个参数,正确用法是${10}
。当n>=10
时,需要使用${n}
来获取参数。
处理参数的特殊字符
参数处理 | 说明 |
---|---|
$# | 传递到脚本的参数个数 |
$* | 以一个单字符串显示所有向脚本传递的参数 |
$$ | 脚本运行的当前进程ID号 |
$! | 后台运行的最后一个进程的ID号 |
$@ | 与$*相同,但是使用时加引号,并在引号中返回每个参数。 |
$- | 显示Shell使用的当前选项,与set命令功能相同。 |
$? | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 |
Shell基本运算符
支持的运算符
- 算术运算符
- 关系运算符
- 布尔运算符
- 字符串运算符
- 文件测试运算符
原生bash不支持简单的数学运算,但可以使用其他命令实现,例如awk
、expr
(最常用)
expr
是一款表达式计算工具,使用它完成表达式的求值操作
例如,两个数量价,注意是反引号,而不是单引号
#!/bin/bash
val=`expr 2+2`
echo "求值结果:$val"
val=`expr 2 + 2`
echo "求值结果:$val"
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
求值结果:2+2
求值结果:4
- 表达式和运算符之间要用空格,例如
2+2
是不正确的,需要写成2 + 2
,这与大多数编程语言不同 - 完整的表达式要被两个反引号` `包含.
算术运算符
两个数直接加不会进行算术运算
Shell 和其它编程语言不同,Shell 不能直接进行算数运算,必须使用数学计算命令。
默认情况下,Shell 不会直接进行算术运算,而是把+
两边的数据(数值或者变量)当做字符串,把+
当做字符串连接符,最终的结果是把两个字符串拼接在一起形成一个新的字符串。这是因为,在Shell如果不特别知名,每一个变量都是字符串,无论赋值的时候有没使用引号,值都会以字符串形式存储,默认情况下不区分变量类型。
Shell expr:进行整数计算
a 不等于 b
#!/bin/bash
a=2
b=3
echo `expr $a + $b`
echo `expr $a - $b`
echo `expr $a \* $b` # 做乘法需要添加斜杠转义
echo `expr $b / $a`
echo `expr $b % $a`
c=$b
echo "赋值后c的值:$c"
if [ $a == $b ]
then
echo "a 等于 b"
else
echo "a 不等于 b"
fi
if [ $b == $c ]
then
echo "b 等于 c"
fi
if [ $a != $b ]
then
echo "a 不等于 b"
fi
if [ $c != $b ]
then
echo "c 不等于 b"
fi
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
5
-1
6
1
1
赋值后c的值:3
a 不等于 b
b 等于 c
a 不等于 b
运算符 | 说明 (例如a=2、b=3) | 举例 |
---|---|---|
+ | 加法 | `expr b` 结果为 30。 |
- | 减法 | `expr b` 结果为 -10。 |
* | 乘法 | `expr b` 结果为 200。 |
/ | 除法 | `expr a` 结果为 2。 |
% | 取余 | `expr a` 结果为 0。 |
= | 赋值 | a=$b 将把变量 b 的值赋给 a。 |
== | 相等。用于比较两个数字,相同则返回 true。 | [ b ] 返回 false。 |
!= | 不相等。用于比较两个数字,不相同则返回 true。 | [ b ] 返回 true。 |
- 条件表达式要放在方括号之间,并且要有空格,例如:
[$a==$b]
是错误的,必须写成[ $a == $b ]
。 - 乘号(
*
)前边必须加反斜杠(\*
)才能实现乘法运算; if...then...fi
是条件语句- 在 MAC 中 shell 的
expr
语法是:$((表达式))
,此处表达式中的 "*" 不需要转义符号 ""
数学计算命令
Shell 中常用的六种数学计算方式
运算操作符/运算命令 | 说明 |
---|---|
(()) | 用于整数运算,效率很高,推荐使用。 |
let | 用于整数运算,和 (()) 类似。 |
[$[] | 用于整数运算,不如 (()) 灵活。 |
expr | 可用于整数运算,也可以处理字符串。比较麻烦,需要注意各种细节,不推荐使用。 |
bc | Linux下的一个计算器程序,可以处理整数和小数。Shell 本身只支持整数运算,想计算小数就得使用 bc 这个外部的计算器。 |
declare -i | 将变量定义为整数,然后再进行数学运算时就不会被当做字符串了。功能有限,仅支持最基本的数学运算(加减乘除和取余),不支持逻辑运算、自增自减等,所以在实际开发中很少使用。 |
Shell (()):对整数进行数学运算
(( ))
只能进行整数运算,不能对小数(浮点数)或者字符串进行运算。语法格式 ((表达式))
,就是将数学运算表达式放在((
和))
之间。
表达式可以只有一个,也可以有多个,多个表达式之间以逗号,
分隔。对于多个表达式的情况,以最后一个表达式的值作为整个 (( ))
命令的执行结果。
可以使用$
获取 (( ))
命令的结果,这和使用$
获得变量值是类似的。
运算操作符/运算命令 | 说明 |
---|---|
((a=10+66) ((b=a-15)) ((c=a+b)) | 这种写法可以在计算完成后给变量赋值。以 ((b=a-15)) 为例,即将 a-15 的运算结果赋值给变量 c 。 注意,使用变量时不用加$ 前缀,(( )) 会自动解析变量名。 |
a=$((10+66) b=$((a-15)) c=$((a+b)) | 可以在 (( )) 前面加上$ 符号获取 (( )) 命令的执行结果,也即获取整个表达式的值。以 c=$((a+b)) 为例,即将 a+b 这个表达式的运算结果赋值给变量 c 。 注意,类似 c=((a+b)) 这样的写法是错误的,不加$ 就不能取得表达式的结果。 |
((a>7 && b==c)) | (( )) 也可以进行逻辑运算,在 if 语句中常会使用逻辑运算。 |
echo $((a+10)) | 需要立即输出表达式的运算结果时,可以在 (( )) 前面加$ 符号。 |
((a=3+5, b=a+10)) | 对多个表达式同时进行计算。 |
在 (( ))
中使用变量无需加上$
前缀,(( ))
会自动解析变量名,这使得代码更加简洁,也符合程序员的书写习惯。
算术运算
# 直接输出运算结果
user@PxeCtrlSys:~$ echo $((1+1))
2
user@PxeCtrlSys:~$ echo $((2*3))
6
# 计算完成后,给变量赋值
user@PxeCtrlSys:~$ i=5
user@PxeCtrlSys:~$ ((i=i*2))
user@PxeCtrlSys:~$ echo $i
10
user@PxeCtrlSys:~$ ((i*=2)) # 简写,等效于 ((i=i*2))
user@PxeCtrlSys:~$ echo $i
20
# 复杂运算,结果赋值给变量a,变量在括号内
user@PxeCtrlSys:~$ ((a=2-5*2/4+2**2))
user@PxeCtrlSys:~$ echo $a
4
# 运算结果赋值给变量b,变量b在括号外,需要使用$
user@PxeCtrlSys:~$ b=$((2-5*2/4+2**2))
user@PxeCtrlSys:~$ echo $b
4
# 直接输出表达式的值,$符号不能去掉
user@PxeCtrlSys:~$ echo $((2-5*2/4+2**2))
4
# 利用公式计算1-100的和
user@PxeCtrlSys:~$ echo $((100*(100+1)/2))
5050
逻辑运算
# 结果为真,输出1,1表示真
user@PxeCtrlSys:~$ echo $((3<5))
1
user@PxeCtrlSys:~$ echo $((3>5))
0
user@PxeCtrlSys:~$ echo $((3==2+1))
1
# 多条件成立
user@PxeCtrlSys:~$ if ((8>6&&5==2+3))
> then
> echo yes
> fi
yes
(())进行自增++和自减--运算
user@PxeCtrlSys:~$ a=10
# ++在后面,先输出a的值,在自增
user@PxeCtrlSys:~$ echo $((a++))
10
user@PxeCtrlSys:~$ echo $a
11
# --在后面,先输出a的值,再自减
user@PxeCtrlSys:~$ echo $((a--))
11
user@PxeCtrlSys:~$ echo $a
10
# --在前面,先自减,再输出a的值
user@PxeCtrlSys:~$ echo $((--a))
9
user@PxeCtrlSys:~$ echo $a
9
# ++在前面,先自增,再输出a的值
user@PxeCtrlSys:~$ echo $((++a))
10
user@PxeCtrlSys:~$ echo $a
10
多个表达式计算
# 先计算第一个表达式,再计算第二个表达式
user@PxeCtrlSys:~$ ((a=3*2, b=a+6))
user@PxeCtrlSys:~$ echo $a $b
6 12
# 以最后一个表达式的结果作为整个(())命令的执行结果
user@PxeCtrlSys:~$ c=$((1+2, a+b))
user@PxeCtrlSys:~$ echo $c
18
Shell let:对整数进行数学运算
和双小括号 (( ))
一样,let
命令也只能进行整数运算,不能对小数(浮点数)或者字符串进行运算。
语法格式 let 表达式
、let "表达式"
、let '表达式'
,都等价于 ((表达式))
。
当表达式中含有 Shell 特殊字符(例如 |
)时,需要用双引号" "
或者单引号' '
将表达式包围起来。
和 (( ))
类似,let
命令也支持一次性计算多个表达式,并且以最后一个表达式的值作为整个 let
命令的执行结果。但是,对于多个表达式之间的分隔符,let
和 (( ))
是有区别的:
let
命令以空格来分隔多个表达式;(( ))
以逗号,
来分隔多个表达式。
user@PxeCtrlSys:~$ i=2
user@PxeCtrlSys:~$ let i+=3
user@PxeCtrlSys:~$ echo $i
5
let i+=3
等价于((i+=3))
,但后者效率更高。
let
后面可以跟多个表达式,用空格分隔
user@PxeCtrlSys:~$ a=3
user@PxeCtrlSys:~$ let b=3**2 c=a+b
user@PxeCtrlSys:~$ echo $a $b
3 9
user@PxeCtrlSys:~$ echo $c
12
Shell $[]:对整数进行数学运算
和 (())
、let
命令类似,$[]
也只能进行整数运算。语法为 $[表达式]
$[]
会对表达式
进行计算,并取得计算结果。如果表达式
中包含了变量,那么你可以加$
,也可以不加。
# 直接输出结果
user@PxeCtrlSys:~$ echo $[2*3]
6
user@PxeCtrlSys:~$ echo $[(2+3)/3]
1
user@PxeCtrlSys:~$ echo $[(2+3)%3]
2
user@PxeCtrlSys:~$ a=6
# 将结果赋值给变量
user@PxeCtrlSys:~$ b=$[a*2]
user@PxeCtrlSys:~$ echo $b
12
user@PxeCtrlSys:~$ echo $[a+b]
18
# 变量前加$对结果没有影响
user@PxeCtrlSys:~$ echo $[$a+$b]
18
Shell declare -i:将变量声明为整数
默认情况下,Shell每一个变量的值都是一个字符串,即使给变量赋值一个数字,它也是字符串。
使用 declare
命令的-i
选项可以将一个变量声明为整数类型,这样在进行数学计算时就不会作为字符串处理了。
#!/bin/bash
echo 定义之前,直接求两个数的和
m=2
n=3
echo $m+$n
echo 求和后赋值给一个变量
ret=$m+$n
echo $ret
echo -e "\n声明变量为整数"
declare -i m n ret
m=2
n=3
echo 直接输出声明后的求和
echo $m+$n
ret=$m+$n
echo 求和后赋值变量
echo $ret
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
定义之前,直接求两个数的和
2+3
求和后赋值给一个变量
2+3
声明变量为整数
直接输出声明后的求和
2+3
求和后赋值变量
5
除了将 m
、n
定义为整数,还必须将 ret
定义为整数,如果不这样做,在执行ret=$m+$n
时,Shell 依然会将 m
、n
视为字符串。
此外,也不能写类似echo $m+$n
这样的语句,这种情况下 m
、n
也会被视为字符串。
总之,除了将参与运算的变量定义为整数,还得将承载结果的变量定义为整数,而且只能用整数类型的变量来承载运算结果,不能直接使用 echo 输出。
和 (())
、let
、$[]
不同,declare -i
的功能非常有限,仅支持最基本的数学运算(加减乘除和取余),不支持逻辑运算(比较运算、与运算、或运算、非运算),所以在实际开发中很少使用。
关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字
运算符 | 说明(例如a=2、b=3) | 举例 |
---|---|---|
-eq | 等于检测两个数是否相等,相等返回 true。 | [ b ] 返回 false。 |
-ne | 不等于检测两个数是否不相等,不相等返回 true。 | [ b ] 返回 true。 |
-gt | 大于检测左边的数是否大于右边的,如果是,则返回 true。 | [ b ] 返回 false。 |
-lt | 小于检测左边的数是否小于右边的,如果是,则返回 true。 | [ b ] 返回 true。 |
-ge | 大等于检测左边的数是否大于等于右边的,如果是,则返回 true。 | [ b ] 返回 false。 |
-le | 小等于检测左边的数是否小于等于右边的,如果是,则返回 true。 | [ b ] 返回 true。 |
#!/bin/bash
a=2
b=3
if [ $a -eq $b ]
then
echo "a 大于 b"
else
echo "a 小于 b"
fi
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
a 小于 b
布尔运算符
运算符 | 说明(例如a=2、b=3) | 举例 |
---|---|---|
! | 非运算,表达式为 true 则返回 false,否则返回 true。 | [ ! false ] 返回 true。 |
-o | 或运算,有一个表达式为 true 则返回 true。 | [ b -gt 100 ] 返回 true。 |
-a | 与运算,两个表达式都为 true 才返回 true。 | [ b -gt 100 ] 返回 false。 |
2 小于 5 或 3 大于 100 : 返回 true
#!/bin/bash
a=2
b=3
if [ $a != $b ]
then
echo "$a != $b : a 不等于 b"
else
echo "$a != $b: a 等于 b"
fi
if [ $a -lt 100 -a $b -gt 15 ]
then
echo "$a 小于 100 且 $b 大于 15 : 返回 true"
else
echo "$a 小于 100 且 $b 大于 15 : 返回 false"
fi
if [ $a -lt 100 -o $b -gt 100 ]
then
echo "$a 小于 100 或 $b 大于 100 : 返回 true"
else
echo "$a 小于 100 或 $b 大于 100 : 返回 false"
fi
if [ $a -lt 5 -o $b -gt 100 ]
then
echo "$a 小于 5 或 $b 大于 100 : 返回 true"
else
echo "$a 小于 5 或 $b 大于 100 : 返回 false"
fi
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
2 != 3 : a 不等于 b
2 小于 100 且 3 大于 15 : 返回 false
2 小于 100 或 3 大于 100 : 返回 true
2 小于 5 或 3 大于 100 : 返回 true
逻辑运算符
运算符 | 说明 | 举例 | ||||
---|---|---|---|---|---|---|
&& | 逻辑的 AND | [[ $a -lt 100 && $b -gt 100 ]] 返回 false | ||||
` | ` | 逻辑的OR | `[[ $a -lt 100 | $b -gt 100 ]]` 返回 true |
#!/bin/bash
a=2
b=3
if [[ $a -lt 5 && $b -gt 2 ]]
then
echo "返回true"
else
echo "返回false"
fi
if [[ $a -ge 2 || $b -le 3 ]]
then
echo "返回true"
else
echo "返回false"
fi
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
返回true
返回true
字符串运算符
运算符 | 说明 | 举例 |
---|---|---|
= | 检测两个字符串是否相等,相等返回 true。 | [ b ] 返回 false。 |
!= | 检测两个字符串是否相等,不相等返回 true。 | [ b ] 返回 true。 |
-z | 检测字符串长度是否为0,为0返回 true。 | [ -z $a ] 返回 false。 |
-n | 检测字符串长度是否为0,不为0返回 true。 | [ -n "$a" ] 返回 true。 |
$ | 检测字符串是否为空,不为空返回 true。 | [ $a ] 返回 true。 |
#!/bin/bash
a="abc"
b="efg"
if [ $a = $b ]
then
echo "$a = $b : a 等于 b"
else
echo "$a = $b: a 不等于 b"
fi
if [ $a != $b ]
then
echo "$a != $b : a 不等于 b"
else
echo "$a != $b: a 等于 b"
fi
if [ -z $a ]
then
echo "-z $a : 字符串长度为 0"
else
echo "-z $a : 字符串长度不为 0"
fi
if [ -n "$a" ]
then
echo "-n $a : 字符串长度不为 0"
else
echo "-n $a : 字符串长度为 0"
fi
if [ $a ]
then
echo "$a : 字符串不为空"
else
echo "$a : 字符串为空"
fi
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
abc = efg: a 不等于 b
abc != efg : a 不等于 b
-z abc : 字符串长度不为 0
-n abc : 字符串长度不为 0
abc : 字符串不为空
文件测试运算符
文件测试运算符用于检测Unix文件的各种属性
操作符 | 说明 | 举例 |
---|---|---|
-b file | 检测文件是否是块设备文件,如果是,则返回 true。 | [ -b $file ] 返回 false。 |
-c file | 检测文件是否是字符设备文件,如果是,则返回 true。 | [ -c $file ] 返回 false。 |
-d file | 检测文件是否是目录,如果是,则返回 true。 | [ -d $file ] 返回 false。 |
-f file | 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 | [ -f $file ] 返回 true。 |
-g file | 检测文件是否设置了 SGID 位,如果是,则返回 true。 | [ -g $file ] 返回 false。 |
-k file | 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 | [ -k $file ] 返回 false。 |
-p file | 检测文件是否是有名管道,如果是,则返回 true。 | [ -p $file ] 返回 false。 |
-u file | 检测文件是否设置了 SUID 位,如果是,则返回 true。 | [ -u $file ] 返回 false。 |
-r file | 检测文件是否可读,如果是,则返回 true。 | [ -r $file ] 返回 true。 |
-w file | 检测文件是否可写,如果是,则返回 true。 | [ -w $file ] 返回 true。 |
-x file | 检测文件是否可执行,如果是,则返回 true。 | [ -x $file ] 返回 true。 |
-s file | 检测文件是否为空(文件大小是否大于0),不为空返回 true。 | [ -s $file ] 返回 true。 |
-e file | 检测文件(包括目录)是否存在,如果是,则返回 true。 | [ -e $file ] 返回 true。 |
变量 file 表示文件"/home/user/test.sh",它的大小为100字节,具有 rwx 权限。下面的代码,将检测该文件的各种属性:
#!/bin/bash
file="/home/user/test.sh"
if [ -r $file ]
then
echo "文件可读"
else
echo "文件不可读"
fi
if [ -w $file ]
then
echo "文件可写"
else
echo "文件不可写"
fi
if [ -x $file ]
then
echo "文件可执行"
else
echo "文件不可执行"
fi
if [ -f $file ]
then
echo "文件为普通文件"
else
echo "文件为特殊文件"
fi
if [ -d $file ]
then
echo "文件是个目录"
else
echo "文件不是个目录"
fi
if [ -s $file ]
then
echo "文件不为空"
else
echo "文件为空"
fi
if [ -e $file ]
then
echo "文件存在"
else
echo "文件不存在"
fi
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
文件可读
文件可写
文件可执行
文件为普通文件
文件不是个目录
文件不为空
文件存在
Shell内建(内置)命令
由 Bash 自身提供的命令,而不是文件系统中的某个可执行文件。
用于进入或者切换目录的 cd 命令,虽然我们一直在使用它,但如果不加以注意很难意识到它与普通命令的性质是不一样的:该命令并不是某个外部文件,只要在 Shell 中你就一定可以运行这个命令。
可以使用 type
来确定一个命令是否是内建命令:
user@PxeCtrlSys:~$ type cd
cd is a shell builtin # 内建命令
user@PxeCtrlSys:~$ type ls
ls is aliased to 'ls --color=auto'
user@PxeCtrlSys:~$ type ssh
ssh is /usr/bin/ssh # 外部命令
通常来说,内建命令会比外部命令执行得更快,执行外部命令时不但会触发磁盘 I/O,还需要 fork 出一个单独的进程来执行,执行完成后再退出。而执行内建命令相当于调用当前 Shell 进程的一个函数。
bash | : | . | [ | alias | bg | bind |
break | builtin | cd | command | compgen | complete | continue |
declare | dirs | disown | echo | enable | eval | exec |
exit | export | fc | fg | getopts | hash | help |
history | jobs | kill | let | local | logout | popd |
printf | pushd | pwd | read | readonly | return | set |
shift | shopt | source | suspend | test | times | trap |
type | typeset | ulimit | umask | unalias | unset | wait |
Shell alias:给命令创建别名
alias
用来给命令创建一个别名。
查看alias所有别名
若直接输入该命令且不带任何参数,则列出当前 Shell 环境中使用了哪些别名。
user@PxeCtrlSys:~$ alias
alias ls='ls --color=auto'
终于知道我的腾讯云debian上ls
命令没有颜色区分了
# 没有为ls创建别名
root@StarMeow-Svr:~# alias
root@StarMeow-Svr:~# alias ls='ls --color=auto'
root@StarMeow-Svr:~# ls
# 这儿的文件和文件夹就有颜色区分了
使用 alias
当然也可以自定义别名,比如说一般的关机命令是shutdown-h now
,写起来比较长,这时可以重新定义一个关机命令,以后就方便多了。使用 alias 定义的别名命令也是支持 Tab 键补全的,如下所示:
alias myShutdown='shutdown -h now'
注意,这样定义别名只能在当前 Shell 环境中有效,换句话说,重新登录后这个别名就消失了。
永久生效alias别名
为了确保永远生效,可以将该别名手动写入到用户主目录中的.bashrc
文件。
root@StarMeow-Svr:~# vim /root/.bashrc
# 将下方代码取消被注释
export LS_OPTIONS='--color=auto'
eval "`dircolors`"
alias ls='ls $LS_OPTIONS'
alias ll='ls $LS_OPTIONS -l'
alias l='ls $LS_OPTIONS -lA'
# 修改完后使其生效
root@StarMeow-Svr:~# source ~/.bashrc
root@StarMeow-Svr:~# ls
# 这儿的文件和文件夹就有颜色区分了
root@StarMeow-Svr:~# alias
alias l='ls $LS_OPTIONS -lA'
alias ll='ls $LS_OPTIONS -l'
alias ls='ls $LS_OPTIONS'
删除alias别名
使用 unalias
内建命令可以删除当前 Shell 环境中的别名。unalias
有两种使用方法:
- 第一种用法是在命令后跟上某个命令的别名,用于删除指定的别名。
- 第二种用法是在命令后接
-a
参数,删除当前 Shell 环境中所有的别名。
同样,这两种方法都是在当前 Shell 环境中生效的。要想永久删除在.bashrc
文件中定义的别名,只能进入该文件手动删除。
# 例如已经通过alias查到如下别名ls
user@PxeCtrlSys:~$ alias
alias ls='ls --color=auto'
# 使用unalias ls就可以删除当前环境的别名
user@PxeCtrlSys:~$ unalias ls
Shell echo:输出字符串
用于字符串输出echo string
显示普通字符串
echo "It is a string1"
这里的双引号可以完全省略
echo It is a string2
显示转义字符
#!/bin/bash
echo "\"It is a string1\""
echo \"It is a string2\"
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
"It is a string1"
"It is a string2"
默认情况下,echo
不会解析以反斜杠\
开头的转义字符。比如,\n
表示换行,echo
默认会将它作为普通字符对待。
echo "hello \nworld"
# 运行结果
hello \nworld
echo -e "hello \nworld"
# 运行结果
hello
world
同样双引号都是可以省略的
显示变量
read
命令从标准输入中读取一行,并把输入行的每个字段的值指定给shell变量
#!/bin/bash
read name
echo "You entered $name"
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
66
You entered 66
显示换行:-e参数和\n
#!/bin/bash
echo -e "this is first line \n"
echo "next"
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
this is first line
next
输出中-e
表示开启转义,\n
表示换行
显示不换行:-e参数和\c或-n参数
#!/bin/bash
echo -e "this is first line \c"
echo "next"
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
this is first line next
-e
开启转义,\c
表示不换行
echo -n "this is first line"
echo -n "next"
# 运行结果
this is first linenext
输出结果重定向到文件
#!/bin/bash
echo -e "this is first line" > file.ts
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
user@PxeCtrlSys:~$ ls
file.ts software test.sh
user@PxeCtrlSys:~$ cat file.ts
this is first line
原样输出,不转义,不取变量
直接使用单引号即可
#!/bin/bash
name='lr'
echo '$name"\'
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
$name"\
显示命令执行结果
使用反引号,而不是单引号,可以执行Linux的命令
#!/bin/bash
echo `date`
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
Tue Mar 5 10:41:55 CST 2019
Shell exit:退出Shell命令
exit 是一个 Shell 内置命令,用来退出当前Shell:
- 如果在终端中直接运行
exit
命令,会退出当前登录的 Shell,并关闭终端; - 如果在Shell中出现
exit
命令,会停止执行后边的所有代码,立即退出 Shell 脚本。
exit
命令可以接受的参数是一个状态值 n
,代表退出时的状态。如果不指定,默认状态值是 0。
#!/bin/bash
echo "exit命令前输出"
exit 9
echo "exit命令后输出"
# 运行结果
user@PxeCtrlSys:~$ ./test.sh
exit命令前输出 # 也就是说exit后面的语句已经不会执行了
# 紧接着用 $? 来获取test.sh的退出状态
user@PxeCtrlSys:~$ echo $?
9
Shell ulimit:显示并设置进程资源速度
系统的可用资源是有限的,如果不限制用户和进程对系统资源的使用,则很容易陷入资源耗尽的地步,而使用 ulimit 命令可以控制进程对可用资源的访问(ulimit 是一个 Shell 内置命令)。
默认情况下 Linux 系统的各个资源都做了软硬限制,其中硬限制的作用是控制软限制(换言之,软限制不能高于硬限制)。使用ulimit -a
可以查看当前系统的软限制,使用命令ulimit -a –H
可查看系统的硬限制。
ulimit -a查看软限制
user@PxeCtrlSys:~$ ulimit -a
core file size (blocks, -c) 0
# core文件大小,单位是block,默认为0
data seg size (kbytes, -d) unlimited
# 数据段大小,单位是kbyte,默认不做限制
scheduling priority (-e) 0
# 调度优先级,默认为0
file size (blocks, -f) unlimited
# 创建文件的大小,单位是block,默认不做限制
pending signals (-i) 15596
# 挂起的信号数量,默认是8192
max locked memory (kbytes, -l) 64
# 最大锁定内存的值,单位是kbyte,默认是32
max memory size (kbytes, -m) unlimited
# 最大可用的常驻内存值,单位是kbyte,默认不做限制
open files (-n) 65536
# 最大打开的文件数,默认是1024
pipe size (512 bytes, -p) 8
# 管道最大缓冲区的值
POSIX message queues (bytes, -q) 819200
# 消息队列的最大值,单位是byte
real-time priority (-r) 0
# 程序的实时性优先级,默认为0
stack size (kbytes, -s) 8192
# 栈大小,单位是kbyte
cpu time (seconds, -t) unlimited
# 最大cpu占用时间,默认不做限制
max user processes (-u) 15596
# 用户最大进程数,默认是8192
virtual memory (kbytes, -v) unlimited
# 最大虚拟内存,单位是kbyte,默认不做限制
file locks (-x) unlimited
# 文件锁,默认不做限制
每一行中都包含了相应的改变该项设置的参数,以最大可以打开的文件数为例(open files
默认是 1024),想要增大至 4096 则按照如下命令设置(可参照此方法调整其他参数)。
# -n参数是设置最大文件打开数
# 下面命令会同时设置硬限制和软限制
user@PxeCtrlSys:~$ ulimit -n 65536
# 使用-S参数单独设置软限制
user@PxeCtrlSys:~$ ulimit -S -n 65536
# 使用-H参数单独设置硬限制
user@PxeCtrlSys:~$ ulimit -H -n 65536
limits.conf 配置文件
使用 ulimit
直接调整参数,只会在当前运行时生效,一旦系统重启,所有调整过的参数就会变回系统默认值。所以建议将所有的改动放在 ulimit
的系统配置文件中。
user@PxeCtrlSys:~$ cat /etc/security/limits.conf
# /etc/security/limits.conf
#该文件是ulimit的配置文件,任何对系统的ulimit的修改都应写入该文件
#Each line describes a limit for a user in the form:
#配置应该写成西面格式,即每个配置占用1行,每行4列
#<domain> <type> <item> <value>
#
#Where:
#<domain>取值如下:一个用户名、一个组名,组名前面用@符号、通配符*、通配符%
#<domain> can be:
# - a user name
# - a group name, with @group syntax
# - the wildcard *, for default entry
# - the wildcard %, can be also used with %group syntax,
# for maxlogin limit
# - NOTE: group and wildcard limits are not applied to root.
# To apply a limit to the root user, <domain> must be
# the literal username root.
#
#<type>有两个可用值:soft用于设置软限制、hard用于设置硬限制
#<type> can have the two values:
# - "soft" for enforcing the soft limits
# - "hard" for enforcing hard limits
#
#<item> can be one of the following:
# - core - limits the core file size (KB)
# - data - max data size (KB)
# - fsize - maximum filesize (KB)
# - memlock - max locked-in-memory address space (KB)
# - nofile - max number of open files
# - rss - max resident set size (KB)
# - stack - max stack size (KB)
# - cpu - max CPU time (MIN)
# - nproc - max number of processes
# - as - address space limit (KB)
# - maxlogins - max number of logins for this user
# - maxsyslogins - max number of logins on the system
# - priority - the priority to run user process with
# - locks - max number of file locks the user can hold
# - sigpending - max number of pending signals
# - msgqueue - max memory used by POSIX message queues (bytes)
# - nice - max nice priority allowed to raise to values: [-20, 19]
# - rtprio - max realtime priority
# - chroot - change root to directory (Debian-specific)
#
#<domain> <type> <item> <value>
#
#以下是使用样例
#* soft core 0
#root hard core 100000
#* hard rss 10000
#@student hard nproc 20
#@faculty soft nproc 20
#@faculty hard nproc 50
#ftp hard nproc 0
#ftp - chroot /ftp
#@student - maxlogins 4
# End of file