Shell脚本快速入门学习

常用的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时,会同时存在三种变量:

  1. 全局变量 指变量在当前的整个 Shell 会话中都有效。每个 Shell 会话都有自己的作用域,彼此之间互不影响。在 Shell 中定义的变量,默认就是全局变量。
  2. 局部变量 在 Shell 函数中定义的变量默认也是全局变量,它和在函数外部定义变量拥有一样的效果;要想变量的作用域仅限于函数内部,那么可以在定义时加上local命令,此时该变量就成了局部变量。
  3. 环境变量 只在当前 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连起来使用的意思是:忽略左边的所有字符,直到遇见 charschars 不会被截取)。

 

#!/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脚本传递参数

想脚本传递参数,脚本内获取参数的格式为:$nn代表一个数字,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 内置命令(例如 testecho),还是外部的 Linux 命令(例如 cdls),还是自定义的 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不支持简单的数学运算,但可以使用其他命令实现,例如awkexpr(最常用)

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

除了将 mn 定义为整数,还必须将 ret 定义为整数,如果不这样做,在执行ret=$m+$n时,Shell 依然会将 mn 视为字符串。

此外,也不能写类似echo $m+$n这样的语句,这种情况下 mn 也会被视为字符串。

总之,除了将参与运算的变量定义为整数,还得将承载结果的变量定义为整数,而且只能用整数类型的变量来承载运算结果,不能直接使用 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


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章