Shell脚本快速入门学习

简介

Shell是脚本语言,解释型,无需提前编译。

Shel同时也是一个程序,它的一端连接着Linux内核,另一端连接着用户和其他应用程序,也就是说Shell是用户或应用程序与内核沟通的桥梁,

mark

扩展名为.sh

#!/bin/bash
echo "Hello World !"

#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。

/bin/bash指明了解释器的具体路径。

echo 命令用于向窗口输出文本。

常用的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 $a + $b\` 结果为 30。
-减法\`expr $a - $b\` 结果为 -10。
*乘法\`expr $a \\* $b\` 结果为 200。
/除法\`expr $b / $a\` 结果为 2。
%取余\`expr $b % $a\` 结果为 0。
=赋值a=$b 将把变量 b 的值赋给 a。
==相等。用于比较两个数字,相同则返回 true。[ $a == $b ] 返回 false。
!=不相等。用于比较两个数字,不相同则返回 true。[ $a != $b ] 返回 true。
  • 条件表达式要放在方括号之间,并且要有空格,例如: [$a==$b] 是错误的,必须写成 [ $a == $b ]
  • 乘号(*)前边必须加反斜杠(\*)才能实现乘法运算;
  • if...then...fi 是条件语句
  • 在 MAC 中 shell 的 expr 语法是:$((表达式)),此处表达式中的 "*" 不需要转义符号 "\"

数学计算命令

Shell 中常用的六种数学计算方式

运算操作符/运算命令说明
(())用于整数运算,效率很高,推荐使用
let用于整数运算,和 (()) 类似。
[$[]用于整数运算,不如 (()) 灵活。
expr可用于整数运算,也可以处理字符串。比较麻烦,需要注意各种细节,不推荐使用。
bcLinux下的一个计算器程序,可以处理整数和小数。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。[ $a -eq $b ] 返回 false。
-ne不等于检测两个数是否不相等,不相等返回 true。[ $a -ne $b ] 返回 true。
-gt大于检测左边的数是否大于右边的,如果是,则返回 true。[ $a -gt $b ] 返回 false。
-lt小于检测左边的数是否小于右边的,如果是,则返回 true。[ $a -lt $b ] 返回 true。
-ge大等于检测左边的数是否大于等于右边的,如果是,则返回 true。[ $a -ge $b ] 返回 false。
-le小等于检测左边的数是否小于等于右边的,如果是,则返回 true。[ $a -le $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。[ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a与运算,两个表达式都为 true 才返回 true。[ $a -lt 20 -a $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。[ $a = $b ] 返回 false。
!=检测两个字符串是否相等,不相等返回 true。[ $a != $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:.[aliasbgbind
breakbuiltincdcommandcompgencompletecontinue
declaredirsdisownechoenableevalexec
exitexportfcfggetoptshashhelp
historyjobskillletlocallogoutpopd
printfpushdpwdreadreadonlyreturnset
shiftshoptsourcesuspendtesttimestrap
typetypesetulimitumaskunaliasunsetwait

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

Shell printf命令

模仿C语言的printf()程序,printf的脚本比echo移植性好。

printf使用引用文本或空格分隔的参数,外面可以在printf中使用格式化字符串,还可以制定字符串的宽度,左右对齐方式等。默认printf不会像echo自动添加换行符,可以手动添加\n

命令语法:printf format-string [arguments...]

  • format-string为格式控制字符串
  • arguments参数列表
user@PxeCtrlSys:~$ echo "123"
123
user@PxeCtrlSys:~$ printf "123"
123user@PxeCtrlSys:~$ printf "123\n"
123

格式化输出

#!/bin/bash

printf "%-10s %-8s %-4s\n" 姓名 性别 体重
printf "%-10s %-8s %-4.2f\n" 郭静 男 40

# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
姓名     性别   体重
郭静     男      40.00

%s%c%d%f都是格式化替代符

%-10s指一个宽度为10个字符(-表示左对齐,没有该符号就是右对齐),任何字符都会被显示在10个字符宽的字符内。如果不足则以空格填充,超出也会将内容全部显示出来。

%-4.2f指格式化为小数,.2指保留两位小数。

%d %s %c %f 格式替代符详解:

  • d: Decimal 十进制整数 :对应位置参数必须是十进制整数,否则报错!
  • s: String 字符串 :对应位置参数必须是字符串或者字符型,否则报错!
  • c: Char 字符 :对应位置参数必须是字符串或者字符型,否则报错!
  • f: Float 浮点 : 对应位置参数必须是数字型,否则报错!
#!/bin/bash
#!/bin/bash

# 格式化字符串为双引号
printf "%d %s\n" 1 "abc"

# 格式化字符串为单引号,效果和双引号一致
printf '%d %s\n' 1 'abc'

# 没有引号也可以输出,但要求参数只能是一个的情况
printf %s abc

echo # 使用这个是为了输出换行

# 格式中只指定了一个参数,但多出的参数仍然会被按照该格式输出,格式化字符串被重用
printf %s abc "def"

echo

printf "\s\n" qwe "zxc"

# 参数按照格式化指定个数进行分组输出,而不够的将输出空值
printf "%s %s %s\n" a b c d e f g

# 如果没有参数,那么 %s 用空值代替,%d用0代替
printf "%s 和 %d\n"

# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
1 abc
1 abc
abc
abcdef
\s
a b c
d e f
g  
 和 0

printf转义序列

序列说明
\a警告字符,通常为ASCII的BEL字符
\b后退
\c抑制(不显示)输出结果中任何结尾的换行字符(只在%b格式指示符控制下的参数字符串中有效),而且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略
\f换页(formfeed)
\n换行
\r回车(Carriage return)
\t水平制表符
\v垂直制表符
\\一个字面上的反斜杠字符
\ddd表示1到3位数八进制值的字符。仅在格式字符串中有效
\0ddd表示1到3位的八进制值字符
#!/bin/bash

printf "<%s>" "a\nb"

echo

printf "<%b>" "a\nb"

echo

printf "abc\adef"

echo 这儿要换行的


# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
<a\nb>
<a
b>
abcdef这儿要换行的

Shell test 命令

Shell中的test用于检查某个条件是否成立,可以进行数值、字符和文件三个方面的测试。

数值测试

参数说明
-eq等于则为真
-ne不等于则为真
-gt大于则为真
-ge大于等于则为真
-lt小于则为真
-le小于等于则为真
#!/bin/bash

num1=100
num2=200

if test $[num1] -eq $[num2]
then
    echo '两个数相等'
else
    echo '两个数不相等'
fi

# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
两个数不相等


#!/bin/bash

num1=100
num2=200

# 也可以直接比较两个数的结果
if test $num1 -eq $num2
then
    echo "相等"
else
    echo "不相等"
fi

# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
不相等

代码中的[]执行基本的算术运算,如

#!/bin/bash

num1=100
num2=200

# 注意等号两边不能有空格,$[num1+num2]等同于`expr $num1 + $num2`
result=$[num1+num2]
echo "两个数相加的结果为:$result"

# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
两个数相加的结果为:300

字符串测试

参数说明
=等于则为真
!=不相等则为真
-z 字符串字符串的长度为零则为真
-n 字符串字符串的长度不为零则为真
#!/bin/bash

str1='shell'
str2='she11'

if test $str1 = $str2
then
    echo "两个字符串相等"
else
    echo "两个字符串不相等"
fi

if test -n $str1
then
    echo "字符串的长度不为0"
else
    echo "字符串的长度为0"
fi


# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
两个字符串不相等
字符串的长度不为0

文件测试

参数说明
-e 文件名如果文件存在则为真
-r 文件名如果文件存在且可读则为真
-w 文件名如果文件存在且可写则为真
-x 文件名如果文件存在且可执行则为真
-s 文件名如果文件存在且至少有一个字符则为真
-d 文件名如果文件存在且为目录则为真
-f 文件名如果文件存在且为普通文件则为真
-c 文件名如果文件存在且为字符型特殊文件则为真
-b 文件名如果文件存在且为块特殊文件则为真
#!/bin/bash

if test -w /home/user/file.ts
then
   echo "文件存在且可写"
else
    echo "文件不存在"
fi


# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
文件存在且可写

与或非逻辑运算test

Linux还提供了与-a、或-o、非!三个逻辑操作符用于将测试条件连接起来,优先级为:! > -a > -o

#!/bin/bash

# file.ts文件存在且可写,以及test.sh存在且可执行时为真
if test -w /home/user/file.ts -a -x /home/user/test.sh
then
   echo "文件都存在,且一个可写,一个可执行"
else
    echo "文件不存在"
fi

# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
文件都存在,且一个可写,一个可执行

Shell流程控制

在sh/bash里如果else分支没有语句执行,就不要写这个else

if else

if语句语法格式

if condition
then
    command1
    command2
    ...
    commandN
fi

或者

if condition; then
    command1
    command2
    ...
    commandN
fi

请注意 condition 后边的分号;,当 ifthen 位于同一行的时候,这个分号是必须的,否则会有语法错误。

写成一行(适用于终端命令提示符):

if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi

# 在这个系统上运行
user@PxeCtrlSys:~$ ps -ef | grep -c "ssh"
15

if else 语法格式

if condition
then
    command1
    command2
    ...
    commandN
else
    command
fi

Shell 支持任意数目的分支,当分支比较多时,if else-if else 语法格式

if condition1
then
    command1
elif condition2
then
    command2
else
    commandN
fi

判断两个变量是否相等

#!/bin/bash

a=10
b=10

if [ $a -eq $b ]
then
    echo "a和b相等"
elif [ $a -gt $b ]
then
    echo "a大于b"
else
    echo "a小于b"
    # -lt
fi

# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
a和b相等

多条件判断

#!/bin/bash

read age

if (( $age <= 2 )); then
    echo "婴儿"
elif (( $age >= 3 && $age <= 8 )); then
    echo "幼儿"
elif (( $age >= 9 && $age <= 17 )); then
    echo "少年"
elif (( $age >= 18 && $age <=25 )); then
    echo "成年"
elif (( $age >= 26 && $age <= 40 )); then
    echo "青年"
elif (( $age >= 41 && $age <= 60 )); then
    echo "中年"
else
    echo "老年"
fi

与test结合使用

#!/bin/bash

a=$[3*6]
b=$[12+3]

if test $[a] -eq $[b]
then
    echo "a和b相等"
else
    echo "a和b不相等"
fi

# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
a和b不相等

for循环

一般格式为

for var in item1 item2 ... itemN
do
    command1
    command2
    ...
    commandN
done

写成一行:

for var in item1 item2 ... itemN; do command1; command2; … commandN; done;

例如:

user@PxeCtrlSys:~$ for i in 1 2 3 4; do echo $i; echo $[i*i]; done;
1
1
2
4
3
9
4
16

当变量在列表里,for循环即执行一次所有命令,使用变量名获取列表中的当前取值。命令可为任何有效的shell命令和语句。in列表可以包含替换、字符串和文件名。

in列表是可选的,如果不使用它,for循环使用命令行的位置参数

#!/bin/bash

# 顺序输出当前列表中的数字
for i in 1 2 3 4 5
do
    echo "循环中输出的值为:$i"
done

# 顺序输出字符串中的字符
times=0
for str in "顺序输出字符串中的数字"
do
    echo 输出 $[time+1] 次
    echo $str
done

# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
循环中输出的值为:1
循环中输出的值为:2
循环中输出的值为:3
循环中输出的值为:4
循环中输出的值为:5
输出 1 次
顺序输出字符串中的数字

如果是循环字符串,那么它并不是按照字符串中每一个字符输出,而是当作一个整体,循环整个串一次

while语句---条件满足循环

while循环用于不断执行一系列命令,也用于从输入文件中读取数据;命令通常为测试条件,格式为:

while condition
do
   command
done

使用while循环,当不满足条件时,就跳出循环

#!/bin/bash

num=1

while(( $num<=5 ))
do
    echo $num
    let "num++"
    # 直接使用:let num++ 也是可以的
done

# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
1
2
3
4
5

上方用了Bash的let命令,它用于执行一个或多个表达式,变量计算中不需要加上$来表示变量。

while循环可用于读取键盘输入

#!/bin/bash

echo "按下<Ctrl-D>退出"
echo -n "输入你所想的编程语言:"

while read Code
do
   echo "$Code 是一门流行语言"
done



# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
按下<Ctrl-D>退出
输入你所想的编程语言:python
python 是一门流行语言
shell
shell 是一门流行语言

无限循环

语法格式

while :
do
    command
done

或者

while true
do
    command
done

或者

for (( ; ; ))
do
    command
done

until循环---条件不满足循环

until循环执行一系列命令,知道条件为true时停止。

until循环与while循环再处理方式上刚好相反。

一般while循环优于until循环,但在某些极少情况until更有用。

语法格式

until condition
do
    command
done

condition一般为条件表达式,如果返回值为false,则继续执行循环体内的语句,否则跳出循环。表达式为true跳出循环

#!/bin/bash

a=0

# a小等于10取反,就表示a大等于10,比如3<10为false继续循环
# 由于是先+1再判断,当输出了a=9,+1后,就变成10,满足了10=10为true,就退出循环
until [ ! $a -lt 10 ]
do
    echo $a
    a=`expr $a + 1`
done

# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
0
1
2
3
4
5
6
7
8
9

case多选择语句

可以用case语句匹配一个值与一个模式,如果匹配成功,则执行相匹配的命令。语法格式

case 值 in
模式1)
    command1
    command2
    ...
    commandN
    ;;
模式2)
    command1
    command2
    ...
    commandN
    ;;
esac

case工作方式如上。取值后面必须为单词in,每一模式必须以右括号)结束。取值可以为变量或常熟。匹配发现取值复核某一模式后,其间所有命令开始执行直到;;

取值将检测匹配每一个模式。一旦模式匹配,则执行完该匹配模式相应命令后不再继续其他模式。如果无一匹配模式,使用星号*捕获该值,再执行后面的命令。

#!/bin/bash

echo "输入1到5之间的数字:"
echo 你输入的数字为:

read num

case $num in
1) echo '你输入了1'
    ;;
2)
    echo 你输入了2
    ;;
3)
    echo "你输入了3";;
4)
    echo "你输入了4"
    ;;
5)
    echo 你输入了5 ;;
*) echo "你输入的数字不在该区间"
    ;;
esac

# 运行结果
user@PxeCtrlSys:~$ ./test.sh
输入1到5之间的数字:
你输入的数字为:
1
你输入了1
user@PxeCtrlSys:~$ ./test.sh
输入1到5之间的数字:
你输入的数字为:
2
你输入了2
user@PxeCtrlSys:~$ ./test.sh
输入1到5之间的数字:
你输入的数字为:
3
你输入了3
user@PxeCtrlSys:~$ ./test.sh
输入1到5之间的数字:
你输入的数字为:
4
你输入了4
user@PxeCtrlSys:~$ ./test.sh
输入1到5之间的数字:
你输入的数字为:
5
你输入了5
user@PxeCtrlSys:~$ ./test.sh
输入1到5之间的数字:
你输入的数字为:
6
你输入的数字不在该区间

跳出循环

未达到循环结束条件时强制跳出循环

break

break命令允许跳出所有循环(终止执行后面的所有循环)。

#!/bin/bash

while :  # 无限循环   
do
    echo -n "输入1到5之间的数字:"
    read num
    case $num in
    1|2|3|4|5)
        echo "你输入的数字为:$num"
        ;;
    *) echo '你数的数字不在该区间,跳出循环'
        break;;
    esac
done

# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
输入1到5之间的数字:3
你输入的数字为:3
输入1到5之间的数字:6
你数的数字不在该区间,跳出循环

continue

当执行到continue语句是,不会跳出循环,而是之后的语句不会继续执行

#!/bin/bash

while :  # 无限循环
do
    echo -n "输入1到5之间的数字:"
    read num
    case $num in
    1|2|3|4|5)
        echo "你输入的数字为:$num"
        ;;
    *) echo '你数的数字不在该区间'
        continue
        echo "continue之后的不会再执行"
        ;;
    esac
done

# 运行结果
user@PxeCtrlSys:~$ ./test.sh 
输入1到5之间的数字:3
你输入的数字为:3
输入1到5之间的数字:6
你数的数字不在该区间
输入1到5之间的数字:2
你输入的数字为:2
输入1到5之间的数字:^C

esac

case的语法需要一个esac(就是case反过来)作为结束标记,每个case分支用右圆括号,用两个分号表示break

Shell输入/输出重定向

大多数Unix系统命令是从终端接受输入并将产生的输出发送回终端。一个命令通常从一个叫标准输入的地方读取,默认情况下,这恰好是终端。同样,一个命令通常将其输出写入到标准输出,默认情况下,也恰好时终端。

命令说明
command > file将输出重定向到 file。
command < file将输入重定向到 file。
command >> file将输出以追加的方式重定向到 file。
n > file将文件描述符为 n 的文件重定向到 file。
n >> file将文件描述符为 n 的文件以追加的方式重定向到 file。
n >& m将输出文件 m 和 n 合并。
n <& m将输入文件 m 和 n 合并。
<< tag将开始标记 tag 和结束标记 tag 之间的内容作为输入。
需要注意的是文件描述符 0 通常是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。

输出重定向

重定向一般通过在命令间插入特定的符号来实现,类似于command > file,这个命令的意思就是将命令command的输出内容存入file,且file中已经存在的内容将被新内容替代。如果要将新内容添加到文件末尾,需要使用>>操作符。

user@PxeCtrlSys:~$ who > users
user@PxeCtrlSys:~$ cat users 
user     pts/0        2019-03-11 09:04 (192.168.96.16)
user     pts/1        2019-03-11 09:04 (192.168.96.16)

> 重定向覆盖

使用>并不会在终端显示内容,而是将who的输出保存到users这个文件中了。

user@PxeCtrlSys:~$ ls / > users 
user@PxeCtrlSys:~$ cat users 
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:~$ who >> users 
user@PxeCtrlSys:~$ cat users 
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     pts/0        2019-03-11 09:04 (192.168.96.16)
user     pts/1        2019-03-11 09:04 (192.168.96.16)

输入重定向

和输出重定向类似,也可以从文件获取输入,语法为command < file,这样,本来需要从键盘获取输入的命令就会转移到从文件中读取内容。

user@PxeCtrlSys:~$ cat users 
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:~$ wc -l users 
2 users

# 输入重定向到文件,获取行数
user@PxeCtrlSys:~$ wc -l < users 
2

结果不同:第一个例子,会输出文件名;第二个不会,因为它仅仅知道从标准输入读取内容。

command < infile > outfile

同时替换输入和输出,执行command,从文件infile读取内容,然后将输出写入到outfile中。

重定向深入讲解

一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:

  • 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
  • 标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
  • 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。

默认情况下,command > filestdout 重定向到 filecommand < filestdin 重定向到 file

stderr重定向到file

如果希望 stderr 重定向到 file,可以这样写:

command 2 > file

stderr追加到file

如果希望 stderr 追加到 file 文件末尾,可以这样写:

command 2 >> file

2 表示标准错误文件(stderr)。

stdout和stderr合并重定向到file

如果希望将 stdoutstderr 合并后重定向到 file,可以这样写:

command > file 2>&1

或者

command >> file 2>&1

stdin和stdout都重定向

如果希望对 stdinstdout 都重定向,可以这样写(command 命令将 stdin 重定向到 file1,将 stdout 重定向到 file2。):

command < file1 > file2

Here Document

Here Document 是 Shell 中的一种特殊的重定向方式,用来将输入重定向到一个交互式 Shell 脚本或程序。

command << delimiter
    document
delimiter

它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command

delimiter:定界符、分隔符、分界符

  • 结尾的delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。
  • 开始的delimiter前后的空格会被忽略掉。
user@PxeCtrlSys:~$ wc -l << EOF
shell
python
EOF  # 输入完回车就在下方显示结果
2

将分隔符之间的文本显示出来

user@PxeCtrlSys:~$ cat << EOF
> shell
> python
> EOF  # 回车后就会输入下方结果
shell
python

/dev/null 文件

如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null

command > /dev/null

/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。
如果希望屏蔽 stdoutstderr,可以这样写:

command > /dev/null 2>&1
注意:0 是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。

Shell文件包含

Shell 也可以包含外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。

Shell 文件包含的语法格式如下:

. filename   # 注意点号(.)和文件名中间有一空格

或

source filename

创建两个 shell 脚本文件。

test1.sh 代码如下

user@PxeCtrlSys:~$ vim test1.sh

user@PxeCtrlSys:~$ cat test1.sh 
a=2
b=3

test2.sh 代码如下

user@PxeCtrlSys:~$ vim test2.sh

user@PxeCtrlSys:~$ cat test2.sh 
#!/bin/bash

# 使用 . 号来引用test1.sh文件
. ./test1.sh

# 或者使用下方的代码
# source ./test1.sh

echo "从test1.sh引入的a的值为:$a,b的值为:$b"

为 test2.sh 添加执行权限,运行结果

user@PxeCtrlSys:~$ chmod +x test2.sh

user@PxeCtrlSys:~$ ./test2.sh 
从test1.sh引入的a的值为:2,b的值为:3
被包含的文件 test1.sh 不需要可执行权限。

参考学习两篇文章
http://www.runoob.com/linux/linux-shell.html
http://c.biancheng.net/shell/

转载自

吾星喵:https://blog.starmeow.cn/detail/7ef605fc8dba5425d6965fbd4c8fbe1f/