简介
Shell是脚本语言,解释型,无需提前编译。
Shel同时也是一个程序,它的一端连接着Linux内核,另一端连接着用户和其他应用程序,也就是说Shell是用户或应用程序与内核沟通的桥梁,
扩展名为.sh
1 | #!/bin/bash |
#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 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都可以使用下面命令查看
1 | user@PxeCtrlSys:~$ cat /etc/shells |
现在Linux基本都是用的bash Shell,查看Linux的默认shell
1 | user@PxeCtrlSys:~$ echo $SHELL |
运行方法
- 作为可执行程序
代码保存test.sh文本中
1 | # 进入脚本所在目录,让脚本拥有可执行权限 |
一定要写成./test.sh,而不是test.sh,运行其它二进制的程序也一样,直接写 test.sh,linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 test.sh 是会找不到命令的,要用 ./test.sh 告诉系统说,就在当前目录找。
- 作为解释器参数
直接运行解释器,其参数就是 shell 脚本的文件名
1 | /bin/sh test.sh |
Shell变量
在Bash Shell中,每一个变量的值都是字符串,无论给变量赋值时有没使用引号,值都会以字符串的形式存储,即使将整数或小数赋值给变量,他们也会被视为字符串。
变量赋值
定义变量,三种定义方式,显式赋值
1 | name="test" |
注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样。同时,变量名的命名须遵循如下规则:
- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
- 中间不能有空格,可以使用下划线(_)。
- 不能使用标点符号。
- 不能使用bash里的关键字(可用help命令查看保留关键字)。
如果变量值包含空白字符,就必须使用引号包围起来,例如name="bom smith"
用语句给变量赋值
1 | for file in `ls /etc` |
将/etc目录下的文件名循环出来。
使用变量
1 | name="test" |
例如和变量连接的字符
1 | #!/bin/bash |
运行结果如下
1 | This is PythonScript |
如果没有花括号,那么变量就是$itemScript,其值为空,结果就会错误。推荐加花括号{}。
修改变量的值
已定义过的变量可以重新赋值
1 | #!/bin/bash |
变量赋值不能加$符号,只有在使用键盘时才能加$。
命令执行结果赋值给变量
支持将命令的执行结果赋值给变量使用,常见的两种方式:
1 | value=`command` |
第一种是使用反引号包围起来,第二种是使用$()包围起来(推荐使用第二种方式)。command是执行命令。
1 | user@PxeCtrlSys:~$ res=`who` |
执行结果赋值给变量,输出不会自动添加换行符。
如果被替换的命令的输出内容包括多行(也即有换行符),或者含有多个连续的空白符,那么在输出变量时应该将变量用双引号包围,否则系统会使用默认的空白符来填充,这会导致换行无效,以及连续的空白符被压缩成一个。请看下面的代码:
1 | user@PxeCtrlSys:~$ res=`who` |
为了防止出现格式混乱的情况,建议在输出变量时加上双引号。
只读变量
使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
下面的例子尝试更改只读变量,结果报错:
1 | #!/bin/bash |
运行结果
1 | test |
删除变量
使用 unset 命令可以删除变量
1 | #!/bin/bash |
变量删除后不能再次使用。unset命令不能删除只读变量。
变量类型,作用域
运行shell时,会同时存在三种变量:
- 全局变量 指变量在当前的整个 Shell 会话中都有效。每个 Shell 会话都有自己的作用域,彼此之间互不影响。在 Shell 中定义的变量,默认就是全局变量。
- 局部变量 在 Shell 函数中定义的变量默认也是全局变量,它和在函数外部定义变量拥有一样的效果;要想变量的作用域仅限于函数内部,那么可以在定义时加上
local命令,此时该变量就成了局部变量。 - 环境变量 只在当前 Shell 会话中有效,如果使用export命令将它导出,那么它就在所有的子 Shell 中也有效了。
Shell字符串
字符串最常用的数据类型,可以用单/双引号,也可以不用引号。
单引号
1 | str='this is a string' |
单引号字符串的限制:
- 单引号里面的任何字符都会原样输出,单引号字符串里面的变量是无效的,也就是变量那一串就只是字符串;
- 单引号字符中不能出现单独一个的单引号(对单引号使用转义符后也不行),但能成对出现,相当于字符串拼接
s='abc\'0e',这个就会报错,而s='abc\'0'e'会输出abc\0e
双引号
- 双引号中可以有变量,输出时会先解析里面的变量和命令
- 双引号也可以出现转义字符
1 | #!/bin/bash |
单/双引号使用场景
建议:
- 如果变量的值是数字,那么不加引号
a=1; - 如果要将字符串原样输出,就用反引号
str='单引号中原样输出${test}'; - 没有特别要求最好都是用双引号(最常见)。
拼接字符串
字符串的拼接(也称字符串连接或者字符串合并),在 Shell 中你不需要使用任何运算符,将两个字符串并排放在一起就能实现拼接。
1 | #!/bin/bash |
获取字符串长度
使用${#变量名}获取长度
1 | #!/bin/bash |
截取字符串
Shell 截取字符串通常有两种方式:从指定位置开始截取和从指定字符(子字符串)开始截取。
从指定位置开始截取
种方式需要两个参数:除了指定起始位置,还需要截取长度,才能最终确定要截取的字符串。
从字符串左边开始计数,格式为${string: start :length}
1 | #!/bin/bash |
从右边开始计数,格式为${string: 0-start :length}
多了0-,这是固定的写法,专门用来表示从字符串右边开始计数。
- 从左边开始计数时,起始数字是 0(这符合程序员思维);从右边开始计数时,起始数字是 1(这符合常人思维)。计数方向不同,起始数字也不同。
- 不管从哪边开始计数,截取方向都是从左到右。
1 | echo ${str:0-5:2} # 从右向左数第5个,长度为2的字符串 |
从指定字符(子字符串)开始截取
这种截取方式无法指定字符串长度,只能从指定字符(子字符串)截取到字符串末尾。Shell 可以截取指定字符(子字符串)右边的所有字符,也可以截取左边的所有字符。
使用 # 号截取右边字符,格式为${string#*chars}
其中,string 表示要截取的字符,chars 是指定的字符(或者子字符串),*是通配符的一种,表示任意长度的字符串。*chars连起来使用的意思是:忽略左边的所有字符,直到遇见 chars(chars 不会被截取)。
1 | #!/bin/bash |
如果希望直到最后一个指定字符(子字符串)再匹配结束,那么可以使用##,具体格式为:${string##*chars}
使用 % 截取左边字符,格式为${string%chars*}
1 | #!/bin/bash |
截取字符串汇总
| 格式 | 说明 |
|---|---|
${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 左边的所有字符。 |
查找字符串
1 | #!/bin/bash |
以上脚本中 ` 是反引号,而不是单引号 '
Shell数组
bash支持一维数组(但不支持多维数组),并且没有限制数组大小。数组元素的下标从0开始,获取数组元素使用小标,下标可以试整数或算术表达式,其值应大于等于0。
定义数组
用括号表示数组,数组元素用空格符号分隔开。
1 | 数组名=(值1 值2 值3 ... 值n) |
或者
1 | array_name=( |
还可以单独定义数组的各个分量
1 | array_name[0]=value0 |
可以不使用连续的下标,而且下标的范围没有限制
1 | array=([2]=12 [5]=99) |
读取数组
一般格式
1 | ${数组名[下标]} |
例如valuen=${array_name[n]},输出数组所有元素用echo ${array_name[@]}
1 | #!/bin/bash |
使用@或*可以获取数组中的所有元素。
获取数组的长度
所谓数组长度,就是数组元素的个数。
利用@或*,可以将数组扩展成列表,然后使用#来获取数组元素的个数,格式为 ${#array_name[@]} 或 ${#array_name[*]} ,其中array_name 表示数组名。两种形式是等价的。
1 | #!/bin/bash |
获取数组中元素为字符串的长度${#arr[2]}(假设下标为2的元素为字符串),因为获取字符串长度的方法是 ${#string_name}
删除数组的元素或数组
使用 unset arr[index] 删除数组元素,使用 unset arr 删除整个数组,所有元素都会消失。
1 | #!/bin/bash |
数组拼接、合并
将两个数组连接成一个数组。
拼接数组的思路是:先利用@或*,将数组扩展成列表,然后再合并到一起。格式为 array_new=(${array1[@]} ${array2[@]}) 或 array_new=(${array1[*]} ${array2[*]})
1 | #!/bin/bash |
Shell注释
以#开始的行就是注释,会被解释器忽略
通过每一行添加一个#来设置多行注释
如果在开发过程中,遇到大段的代码需要临时注释起来,过一会儿又取消注释,怎么办呢?
每一行加个#符号太费力了,可以把这一段要注释的代码用一对花括号括起来,定义成一个函数,没有地方调用这个函数,这块代码就不会执行,达到了和注释一样的效果。
多行注释
1 | :<<EOF |
EOF也可以使用其他符号
1 | :<<' |
Shell脚本传递参数
想脚本传递参数,脚本内获取参数的格式为:$n。n代表一个数字,1为执行脚本的第一个参数,2为执行脚本的第二个参数,以此类推...
给脚本文件传递参数
向脚本传递3个参数,其中$0表示执行的文件名
1 | user@PxeCtrlSys:~$ vim test.sh |
1 | # test.sh文件内容 |
如果参数个数太多,达到或者超过了 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"(仍然将每个参数都看作一份数据,彼此之间是独立的)
1 | #!/bin/bash |
Shell函数
shell函数格式
1 | [ function ] funname [()] |
- 可以带
function func()定义,也可以直接func()定义,不带任何参数。 - 参数返回,可以显示加:
return返回,如果不加,将以最后一条命令运行结果,作为返回值。return后跟数值n(0-255)
无返回值函数
1 | #!/bin/bash |
有返回值函数
定义一个带return的函数
1 | #!/bin/bash |
函数返回值在调用该函数后通过 $? 来获得。
所有函数在使用前必须定义。这就是说必须将函数放在脚本开始部分,直到shell解释器首次发现它时才可以使用。调用函数仅使用其函数名即可。
$? 使用方法:返回值或退出状态
$?用来获取函数返回值或者上一个命令的退出状态
每一条 Shell 命令,不管是 Bash 内置命令(例如 test、echo),还是外部的 Linux 命令(例如 cd、ls),还是自定义的 Shell 函数,当它退出(运行结束)时,都会返回一个比较小的整数值给调用(使用)它的程序,这就是命令的退出状态(exit status)。
if 语句的判断条件,从本质上讲,判断的就是命令的退出状态。
$? 获取上一次命令退出状态
退出状态,就是上一个命令执行后的返回结果。退出状态是一个数字,一般情况下,大部分命令执行成功会返回 0,失败返回 1,这和C语言的 main() 函数是类似的。
不过,也有一些命令返回其他值,表示不同类型的错误。
1 | #!/bin/bash |
$? 获取函数的返回值
给函数传递参数
Shell调用函数也可以向其传递参数。在函数体内部,通过 $n 的形式来传递参数。例如$1表示第一个参数。$2表示第二次参数
1 | #!/bin/bash |
$10不能获取到第10个参数,正确用法是${10}。当n>=10时,需要使用${n}来获取参数。
处理参数的特殊字符
| 参数处理 | 说明 |
|---|---|
| $# | 传递到脚本的参数个数 |
| $* | 以一个单字符串显示所有向脚本传递的参数 |
| $$ | 脚本运行的当前进程ID号 |
| $! | 后台运行的最后一个进程的ID号 |
| $@ | 与$*相同,但是使用时加引号,并在引号中返回每个参数。 |
| $- | 显示Shell使用的当前选项,与set命令功能相同。 |
| $? | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 |
Shell基本运算符
支持的运算符
- 算术运算符
- 关系运算符
- 布尔运算符
- 字符串运算符
- 文件测试运算符
原生bash不支持简单的数学运算,但可以使用其他命令实现,例如awk、expr(最常用)
expr是一款表达式计算工具,使用它完成表达式的求值操作
例如,两个数量价,注意是反引号,而不是单引号
1 | #!/bin/bash |
- 表达式和运算符之间要用空格,例如
2+2是不正确的,需要写成2 + 2,这与大多数编程语言不同 - 完整的表达式要被两个反引号\` \`包含.
算术运算符
两个数直接加不会进行算术运算
Shell 和其它编程语言不同,Shell 不能直接进行算数运算,必须使用数学计算命令。
默认情况下,Shell 不会直接进行算术运算,而是把+两边的数据(数值或者变量)当做字符串,把+当做字符串连接符,最终的结果是把两个字符串拼接在一起形成一个新的字符串。这是因为,在Shell如果不特别知名,每一个变量都是字符串,无论赋值的时候有没使用引号,值都会以字符串形式存储,默认情况下不区分变量类型。
Shell expr:进行整数计算
1 | 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 | 可用于整数运算,也可以处理字符串。比较麻烦,需要注意各种细节,不推荐使用。 |
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)) | 对多个表达式同时进行计算。 |
在 (( )) 中使用变量无需加上$前缀,(( )) 会自动解析变量名,这使得代码更加简洁,也符合程序员的书写习惯。
算术运算
1 | # 直接输出运算结果 |
逻辑运算
1 | # 结果为真,输出1,1表示真 |
(())进行自增++和自减--运算
1 | user@PxeCtrlSys:~$ a=10 |
多个表达式计算
1 | # 先计算第一个表达式,再计算第二个表达式 |
Shell let:对整数进行数学运算
和双小括号 (( )) 一样,let 命令也只能进行整数运算,不能对小数(浮点数)或者字符串进行运算。
语法格式 let 表达式、let "表达式"、let '表达式',都等价于 ((表达式))。
当表达式中含有 Shell 特殊字符(例如 |)时,需要用双引号" "或者单引号' '将表达式包围起来。
和 (( )) 类似,let 命令也支持一次性计算多个表达式,并且以最后一个表达式的值作为整个 let 命令的执行结果。但是,对于多个表达式之间的分隔符,let 和 (( )) 是有区别的:
let命令以空格来分隔多个表达式;(( ))以逗号,来分隔多个表达式。
1 | user@PxeCtrlSys:~$ i=2 |
let i+=3等价于((i+=3)),但后者效率更高。
let后面可以跟多个表达式,用空格分隔
1 | user@PxeCtrlSys:~$ a=3 |
Shell $[]:对整数进行数学运算
和 (())、let 命令类似,$[] 也只能进行整数运算。语法为 $[表达式]
$[] 会对表达式进行计算,并取得计算结果。如果表达式中包含了变量,那么你可以加$,也可以不加。
1 | # 直接输出结果 |
Shell declare -i:将变量声明为整数
默认情况下,Shell每一个变量的值都是一个字符串,即使给变量赋值一个数字,它也是字符串。
使用 declare 命令的-i选项可以将一个变量声明为整数类型,这样在进行数学计算时就不会作为字符串处理了。
1 | #!/bin/bash |
除了将 m、n 定义为整数,还必须将 ret 定义为整数,如果不这样做,在执行ret=$m+$n时,Shell 依然会将 m、n 视为字符串。
此外,也不能写类似echo $m+$n这样的语句,这种情况下 m、n 也会被视为字符串。
总之,除了将参与运算的变量定义为整数,还得将承载结果的变量定义为整数,而且只能用整数类型的变量来承载运算结果,不能直接使用 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。 |
1 | #!/bin/bash |
布尔运算符
| 运算符 | 说明(例如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。 |
1 | 2 小于 5 或 3 大于 100 : 返回 true |
逻辑运算符
| 运算符 | 说明 | 举例 | ||||
|---|---|---|---|---|---|---|
&& | 逻辑的 AND | [[ $a -lt 100 && $b -gt 100 ]] 返回 false | ||||
| ` | ` | 逻辑的OR | `[[ $a -lt 100 | $b -gt 100 ]]` 返回 true |
1 | #!/bin/bash |
字符串运算符
| 运算符 | 说明 | 举例 |
|---|---|---|
| = | 检测两个字符串是否相等,相等返回 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。 |
1 | #!/bin/bash |
文件测试运算符
文件测试运算符用于检测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 权限。下面的代码,将检测该文件的各种属性:
1 | #!/bin/bash |
Shell内建(内置)命令
由 Bash 自身提供的命令,而不是文件系统中的某个可执行文件。
用于进入或者切换目录的 cd 命令,虽然我们一直在使用它,但如果不加以注意很难意识到它与普通命令的性质是不一样的:该命令并不是某个外部文件,只要在 Shell 中你就一定可以运行这个命令。
可以使用 type 来确定一个命令是否是内建命令:
1 | user@PxeCtrlSys:~$ type cd |
通常来说,内建命令会比外部命令执行得更快,执行外部命令时不但会触发磁盘 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 环境中使用了哪些别名。
1 | user@PxeCtrlSys:~$ alias |
终于知道我的腾讯云debian上ls命令没有颜色区分了
1 | # 没有为ls创建别名 |
使用 alias 当然也可以自定义别名,比如说一般的关机命令是shutdown-h now,写起来比较长,这时可以重新定义一个关机命令,以后就方便多了。使用 alias 定义的别名命令也是支持 Tab 键补全的,如下所示:
1 | alias myShutdown='shutdown -h now' |
注意,这样定义别名只能在当前 Shell 环境中有效,换句话说,重新登录后这个别名就消失了。
永久生效alias别名
为了确保永远生效,可以将该别名手动写入到用户主目录中的.bashrc文件。
1 | root@StarMeow-Svr:~# vim /root/.bashrc |
删除alias别名
使用 unalias 内建命令可以删除当前 Shell 环境中的别名。unalias 有两种使用方法:
- 第一种用法是在命令后跟上某个命令的别名,用于删除指定的别名。
- 第二种用法是在命令后接
-a参数,删除当前 Shell 环境中所有的别名。
同样,这两种方法都是在当前 Shell 环境中生效的。要想永久删除在.bashrc文件中定义的别名,只能进入该文件手动删除。
1 | # 例如已经通过alias查到如下别名ls |
Shell echo:输出字符串
用于字符串输出echo string
显示普通字符串
1 | echo "It is a string1" |
这里的双引号可以完全省略
1 | echo It is a string2 |
显示转义字符
1 | #!/bin/bash |
默认情况下,echo 不会解析以反斜杠\开头的转义字符。比如,\n表示换行,echo 默认会将它作为普通字符对待。
1 | echo "hello \nworld" |
同样双引号都是可以省略的
显示变量
read命令从标准输入中读取一行,并把输入行的每个字段的值指定给shell变量
1 | #!/bin/bash |
显示换行:-e参数和\n
1 | #!/bin/bash |
输出中-e表示开启转义,\n表示换行
显示不换行:-e参数和\c或-n参数
1 | #!/bin/bash |
-e开启转义,\c表示不换行
1 | echo -n "this is first line" |
输出结果重定向到文件
1 | #!/bin/bash |
原样输出,不转义,不取变量
直接使用单引号即可
1 | #!/bin/bash |
显示命令执行结果
使用反引号,而不是单引号,可以执行Linux的命令
1 | #!/bin/bash |
Shell exit:退出Shell命令
exit 是一个 Shell 内置命令,用来退出当前Shell:
- 如果在终端中直接运行
exit命令,会退出当前登录的 Shell,并关闭终端; - 如果在Shell中出现
exit命令,会停止执行后边的所有代码,立即退出 Shell 脚本。
exit 命令可以接受的参数是一个状态值 n,代表退出时的状态。如果不指定,默认状态值是 0。
1 | #!/bin/bash |
Shell ulimit:显示并设置进程资源速度
系统的可用资源是有限的,如果不限制用户和进程对系统资源的使用,则很容易陷入资源耗尽的地步,而使用 ulimit 命令可以控制进程对可用资源的访问(ulimit 是一个 Shell 内置命令)。
默认情况下 Linux 系统的各个资源都做了软硬限制,其中硬限制的作用是控制软限制(换言之,软限制不能高于硬限制)。使用ulimit -a可以查看当前系统的软限制,使用命令ulimit -a –H可查看系统的硬限制。
ulimit -a查看软限制
1 | user@PxeCtrlSys:~$ ulimit -a |
每一行中都包含了相应的改变该项设置的参数,以最大可以打开的文件数为例(open files 默认是 1024),想要增大至 4096 则按照如下命令设置(可参照此方法调整其他参数)。
1 | # -n参数是设置最大文件打开数 |
limits.conf 配置文件
使用 ulimit 直接调整参数,只会在当前运行时生效,一旦系统重启,所有调整过的参数就会变回系统默认值。所以建议将所有的改动放在 ulimit 的系统配置文件中。
1 | user@PxeCtrlSys:~$ cat /etc/security/limits.conf |
Shell printf命令
模仿C语言的printf()程序,printf的脚本比echo移植性好。
printf使用引用文本或空格分隔的参数,外面可以在printf中使用格式化字符串,还可以制定字符串的宽度,左右对齐方式等。默认printf不会像echo自动添加换行符,可以手动添加\n
命令语法:printf format-string [arguments...]
format-string为格式控制字符串arguments参数列表
1 | user@PxeCtrlSys:~$ echo "123" |
格式化输出
1 | #!/bin/bash |
%s、%c、%d、%f都是格式化替代符
%-10s指一个宽度为10个字符(-表示左对齐,没有该符号就是右对齐),任何字符都会被显示在10个字符宽的字符内。如果不足则以空格填充,超出也会将内容全部显示出来。
%-4.2f指格式化为小数,.2指保留两位小数。
%d %s %c %f 格式替代符详解:
- d: Decimal 十进制整数 :对应位置参数必须是十进制整数,否则报错!
- s: String 字符串 :对应位置参数必须是字符串或者字符型,否则报错!
- c: Char 字符 :对应位置参数必须是字符串或者字符型,否则报错!
- f: Float 浮点 : 对应位置参数必须是数字型,否则报错!
1 | #!/bin/bash |
printf转义序列
| 序列 | 说明 |
|---|---|
| \a | 警告字符,通常为ASCII的BEL字符 |
| \b | 后退 |
| \c | 抑制(不显示)输出结果中任何结尾的换行字符(只在%b格式指示符控制下的参数字符串中有效),而且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略 |
| \f | 换页(formfeed) |
| \n | 换行 |
| \r | 回车(Carriage return) |
| \t | 水平制表符 |
| \v | 垂直制表符 |
| \\ | 一个字面上的反斜杠字符 |
| \ddd | 表示1到3位数八进制值的字符。仅在格式字符串中有效 |
| \0ddd | 表示1到3位的八进制值字符 |
1 | #!/bin/bash |
Shell test 命令
Shell中的test用于检查某个条件是否成立,可以进行数值、字符和文件三个方面的测试。
数值测试
| 参数 | 说明 |
|---|---|
| -eq | 等于则为真 |
| -ne | 不等于则为真 |
| -gt | 大于则为真 |
| -ge | 大于等于则为真 |
| -lt | 小于则为真 |
| -le | 小于等于则为真 |
1 | #!/bin/bash |
代码中的[]执行基本的算术运算,如
1 | #!/bin/bash |
字符串测试
| 参数 | 说明 |
|---|---|
| = | 等于则为真 |
| != | 不相等则为真 |
| -z 字符串 | 字符串的长度为零则为真 |
| -n 字符串 | 字符串的长度不为零则为真 |
1 | #!/bin/bash |
文件测试
| 参数 | 说明 |
|---|---|
| -e 文件名 | 如果文件存在则为真 |
| -r 文件名 | 如果文件存在且可读则为真 |
| -w 文件名 | 如果文件存在且可写则为真 |
| -x 文件名 | 如果文件存在且可执行则为真 |
| -s 文件名 | 如果文件存在且至少有一个字符则为真 |
| -d 文件名 | 如果文件存在且为目录则为真 |
| -f 文件名 | 如果文件存在且为普通文件则为真 |
| -c 文件名 | 如果文件存在且为字符型特殊文件则为真 |
| -b 文件名 | 如果文件存在且为块特殊文件则为真 |
1 | #!/bin/bash |
与或非逻辑运算test
Linux还提供了与-a、或-o、非!三个逻辑操作符用于将测试条件连接起来,优先级为:! > -a > -o。
1 | #!/bin/bash |
Shell流程控制
在sh/bash里如果else分支没有语句执行,就不要写这个else
if else
if语句语法格式
1 | if condition |
或者
1 | if condition; then |
请注意 condition 后边的分号;,当 if 和 then 位于同一行的时候,这个分号是必须的,否则会有语法错误。
写成一行(适用于终端命令提示符):
1 | if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi |
if else 语法格式
1 | if condition |
Shell 支持任意数目的分支,当分支比较多时,if else-if else 语法格式
1 | if condition1 |
判断两个变量是否相等
1 | #!/bin/bash |
多条件判断
1 | #!/bin/bash |
与test结合使用
1 | #!/bin/bash |
for循环
一般格式为
1 | for var in item1 item2 ... itemN |
写成一行:
1 | for var in item1 item2 ... itemN; do command1; command2; … commandN; done; |
例如:
1 | user@PxeCtrlSys:~$ for i in 1 2 3 4; do echo $i; echo $[i*i]; done; |
当变量在列表里,for循环即执行一次所有命令,使用变量名获取列表中的当前取值。命令可为任何有效的shell命令和语句。in列表可以包含替换、字符串和文件名。
in列表是可选的,如果不使用它,for循环使用命令行的位置参数
1 | #!/bin/bash |
如果是循环字符串,那么它并不是按照字符串中每一个字符输出,而是当作一个整体,循环整个串一次
while语句---条件满足循环
while循环用于不断执行一系列命令,也用于从输入文件中读取数据;命令通常为测试条件,格式为:
1 | while condition |
使用while循环,当不满足条件时,就跳出循环
1 | #!/bin/bash |
上方用了Bash的let命令,它用于执行一个或多个表达式,变量计算中不需要加上$来表示变量。
while循环可用于读取键盘输入
1 | #!/bin/bash |
无限循环
语法格式
1 | while : |
或者
1 | while true |
或者
1 | for (( ; ; )) |
until循环---条件不满足循环
until循环执行一系列命令,知道条件为true时停止。
until循环与while循环再处理方式上刚好相反。
一般while循环优于until循环,但在某些极少情况until更有用。
语法格式
1 | until condition |
condition一般为条件表达式,如果返回值为false,则继续执行循环体内的语句,否则跳出循环。表达式为true跳出循环
1 | #!/bin/bash |
case多选择语句
可以用case语句匹配一个值与一个模式,如果匹配成功,则执行相匹配的命令。语法格式
1 | case 值 in |
case工作方式如上。取值后面必须为单词in,每一模式必须以右括号)结束。取值可以为变量或常熟。匹配发现取值复核某一模式后,其间所有命令开始执行直到;;。
取值将检测匹配每一个模式。一旦模式匹配,则执行完该匹配模式相应命令后不再继续其他模式。如果无一匹配模式,使用星号*捕获该值,再执行后面的命令。
1 | #!/bin/bash |
跳出循环
未达到循环结束条件时强制跳出循环
break
break命令允许跳出所有循环(终止执行后面的所有循环)。
1 | #!/bin/bash |
continue
当执行到continue语句是,不会跳出循环,而是之后的语句不会继续执行
1 | #!/bin/bash |
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中已经存在的内容将被新内容替代。如果要将新内容添加到文件末尾,需要使用>>操作符。
1 | user@PxeCtrlSys:~$ who > users |
> 重定向覆盖
使用>并不会在终端显示内容,而是将who的输出保存到users这个文件中了。
1 | user@PxeCtrlSys:~$ ls / > users |
也就是说使用>如果再次重定向到同样的文件,该文件内容则会被覆盖。
>> 重定向追加
如果不希望覆盖,则使用>>追加方式。
1 | user@PxeCtrlSys:~$ who >> users |
输入重定向
和输出重定向类似,也可以从文件获取输入,语法为command < file,这样,本来需要从键盘获取输入的命令就会转移到从文件中读取内容。
1 | user@PxeCtrlSys:~$ cat users |
结果不同:第一个例子,会输出文件名;第二个不会,因为它仅仅知道从标准输入读取内容。
1 | command < infile > outfile |
同时替换输入和输出,执行command,从文件infile读取内容,然后将输出写入到outfile中。
重定向深入讲解
一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:
- 标准输入文件(stdin):
stdin的文件描述符为0,Unix程序默认从stdin读取数据。 - 标准输出文件(stdout):
stdout的文件描述符为1,Unix程序默认向stdout输出数据。 - 标准错误文件(stderr):
stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。
默认情况下,command > file 将 stdout 重定向到 file,command < file 将stdin 重定向到 file。
stderr重定向到file
如果希望 stderr 重定向到 file,可以这样写:
1 | command 2 > file |
stderr追加到file
如果希望 stderr 追加到 file 文件末尾,可以这样写:
1 | command 2 >> file |
2 表示标准错误文件(stderr)。
stdout和stderr合并重定向到file
如果希望将 stdout 和 stderr 合并后重定向到 file,可以这样写:
1 | command > file 2>&1 |
stdin和stdout都重定向
如果希望对 stdin 和 stdout 都重定向,可以这样写(command 命令将 stdin 重定向到 file1,将 stdout 重定向到 file2。):
1 | command < file1 > file2 |
Here Document
Here Document 是 Shell 中的一种特殊的重定向方式,用来将输入重定向到一个交互式 Shell 脚本或程序。
1 | command << delimiter |
它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command。
delimiter:定界符、分隔符、分界符
- 结尾的
delimiter一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。 - 开始的
delimiter前后的空格会被忽略掉。
1 | user@PxeCtrlSys:~$ wc -l << EOF |
将分隔符之间的文本显示出来
1 | user@PxeCtrlSys:~$ cat << EOF |
/dev/null 文件
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:
1 | command > /dev/null |
/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。
如果希望屏蔽 stdout 和 stderr,可以这样写:
1 | command > /dev/null 2>&1 |
注意:0 是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。
Shell文件包含
Shell 也可以包含外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。
Shell 文件包含的语法格式如下:
1 | . filename # 注意点号(.)和文件名中间有一空格 |
创建两个 shell 脚本文件。
test1.sh 代码如下
1 | user@PxeCtrlSys:~$ vim test1.sh |
test2.sh 代码如下
1 | user@PxeCtrlSys:~$ vim test2.sh |
为 test2.sh 添加执行权限,运行结果
1 | user@PxeCtrlSys:~$ chmod +x test2.sh |
被包含的文件 test1.sh 不需要可执行权限。
参考学习两篇文章
http://www.runoob.com/linux/linux-shell.html
http://c.biancheng.net/shell/
转载自
吾星喵:https://blog.starmeow.cn/detail/7ef605fc8dba5425d6965fbd4c8fbe1f/
