一、 shell脚本介绍
什么是shell命令?
Shell 和 GUI 一样是用户和操作系统之间的接口。
Shell 作为一种用户接口,它实际上是一个能够解释和分析用户键盘输入,执行输入中的命令,然后返回结果的一个解释程序。
$ ls
$ cd
$ pwd
什么是shell脚本
一系列的shell命令的集合, 还可以加入一些逻辑操作(if else for) 将这些命令放入一个文件中.
- 是一个文件
- 有n个shell命令
- 可以加入逻辑
- 需要在linux的终端中执行
# 举例 test.sh
ls # 多条shell命令
pwd
if [ xxx ]
cd ..
shell脚本的基本格式
-
命名格式
- 一般命名规则 : xxxxx.sh (建议以.sh为后缀命名)
-
书写格式
# test.sh #是shell脚本中的注释 # 第一行指定解析shell脚本的时候使用的命令解析器,如果忘了了写, 使用默认的命令解析器 /bin/sh #!/bin/bash # 指定命令解析器 /bin/sh也可以 bash是在sh的基础上开发的 # 一系列的shell命令 ls pwd cp rm
-
shell脚本的执行
# shell脚本编写完成之后, 必须添加执行权限 chmod u+x xxx.sh # u+x u是文件所有者,x是执行权限 # 执行shell脚本 ./xxx.sh 或者 sh test.sh
二、shell变量
命名规则
-
和其他语言变量命名规则类似,注意不能有bash关键字
- 字母,数字,下划线组成,字母区分大小写
- 首个字符必须是字母
- 不能使用bash里的关键字(help命令查看保留关键字)。
普通变量(本地变量)
# 定义变量, 定义完成, 必须要赋值, =前后不能有空格
# 普通变量只能在当前进程中使用
temp=666
# 普通变量取值
value=123 # 默认以字符串处理
value1 = "123 456"
echo $value
# 如何取变量的值:
- $变量名
- ${变量名}
两句效果一样,均为输出变量的值。变量名外面的花括号是可选的,加花括号是为了帮助解释器识别变量的边界。
环境变量
# 可以理解问全局变量, 在当前操作系统中可以全局访问
# 分类
- 系统自带的(使用env命令查看)
- PWD
- SHELL
- PATH
- HOME
- 用户自定义的
- 将普通变量提升为系统级别的环境变量
GOPATH=/home/gtt/go/src # 定义一个普通环境变量
set GOPATH=/home/gtt/go/src # 系统环境变量
export GOPATH=/home/gtt/go/src
~/.bashrc # 刷新环境变量
位置变量
位置变量顾名思义就是和位置有关的变量:
- $0: 执行的脚本文件的名字
- $1: 第一个参数
- $2: 第2个参数
- $3: 第三个参数
- …
应用:执行脚本的时候, 可以给脚本传递参数, 在脚本内部接收这些参数, 需要使用位置变量
# 建立脚本test.sh
#!/bin/bash
echo "hello , world, $0"
echo "第一个参数: $1"
echo "第2参数: $2"
echo "第3个参数: $3"
echo "第4个参数: $4"
echo "第5个参数: $5"
echo "第6个参数: $6"
# 执行test.sh
$ ./test.sh 11 22 3 4 5 6 aa bb
hello , world, ./test.sh
第一个参数: 11
第2参数: 22
第3个参数: 3
第4个参数: 4
第5个参数: 5
第6个参数: 6
特殊变量
$$: 脚本进程执行之后对应进程的ID
$#: 获取传递的参数的个数
$@: 给脚本传递的所有的参数
$?: 脚本执行完成之后的状态, 失败>0 or 成功=0
举例:
# test.sh
#!/bin/bash
echo "hello , world, $0"
echo "第一个参数: $1"
echo "第2参数: $2"
echo "第3个参数: $3"
echo "第4个参数: $4"
echo "第5个参数: $5"
echo "第6个参数: $6"
echo "传递的参数个数: $#"
echo "传递的所有的参数: $@"
echo "当前脚本的进程ID: $$"
$ ./test.sh aa bb cc dd ee ff 8 9 0
hello , world, ./test.sh
第一个参数: aa
第2参数: bb
第3个参数: cc
第4个参数: dd
第5个参数: ee
第6个参数: ff
传递的参数个数: 9
传递的所有的参数: aa bb cc dd ee ff 8 9 0
当前脚本的进程ID: 47946
# 脚本执行状态查看
$ echo $?
0 -> 成功
非0 -> 失败
数组
数组定义
array_name=(value1 value2 ... valuen)
# 举例:
array=(A B "C" D)
元素使用
${array_name[index]}
# 举例
echo "第一个元素为: ${array[0]}"
注意:观察上面四条echo语句
- 第二个打印语句没有使用花括号,shell命令解析器就没有解析成数组的第0个元素
- 而是把数组的第一个元素和[0]都打印了出来
获取所有元素
# 使用 * h和 @ 符号
echo "所有元素为: ${array[*]}"
echo "所有元素为: ${array[@]}"
其他
取命令执行之后的结果值
# 取值的两种方式:
var=$(shell命令)
var=`shell命令` #反单引号(esc同一个)
引号的使用(单引号,双引号)
# 双引号
echo "当前文件: $var" # 打印的时候会将var中的值取出并输出
# 单引号
echo '当前文件: $var' # 将字符串原样输出
$*与$@区别
#作用:都是引用所有参数。
#不同点:例如在脚本运行时写了三个参数 1、2、3,,则 “ * “ 等价于 “1 2 3”(传递了1个参数),而 “@” 等价于 “1” “2” “3”(传递了3个参数)。
三、基本运算
Shell支持的运算符:
- 算数运算符
- 关系运算符
- 布尔运算符
- 字符串运算符
- 文件测试运算符
数值运算
在bash中,变量的默认类型是字符串类型,而字符串类型是不能进行数值运算的,所以shell提供了很多方式来实现数值运算。
expr 是一款表达式计算工具,使用它能完成表达式的求值操作。
# 例如,两个数相加(注意使用的是反引号 ` 而不是单引号 ‘):
#!/bin/bash
val=`expr 2 + 2`
echo "2+2=:$val"
注意:
- 表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2。。
- 完整的表达式要被“反单引号” ` 包含,注意这个字符不是常用的单引号,在 Esc 键下边。
数值关系运算
a=10
b=20
if [ $a -eq $b ]
then
echo "$a -eq $b : a 等于 b"
else
echo "$a -ne $b : a 不等于 b"
fi
这里用到了 -eq,判断两个数值是否相等
常见数值关系测试运算符
nt1 -eq int2 如果int1 等于int2,则返回真 int1 -ne int2 如果int1 不等于int2,则返回真 int1 -lt int2 如果int1 小于int2,则返回真 int1 -le int2 如果int1 小于等于int2,则返回真 int1 -gt int2 如果int1 大于int2,则返回真 int1 -ge int2 如果int1 大于等于int2,则返回真
布尔运算
a=10
b=20
if [ $a != $b ]
then
echo "$a != $b : a 不等于 b"
else
echo "$a == $b : a 等于 b"
fi
测试时使用的逻辑操作符
-a 逻辑与,操作符两边均为真,结果为真,否则为假。&& -o 逻辑或,操作符两边一边为真,结果为真,否则为假。|| ! 逻辑否,条件为假,结果为真。
比较 -a 与 &&, -o 与 ||, ! test 与 test !
-a
和-o
作为测试命令的参数用在测试命令的内部,&&
和||
则用来运算测试的返回值,!
为两者通用。
字符串运算
a="abc"
b="efg"
if [ $a = $b ] # 判断两个字符串是否相等
then
echo "$a = $b : a 等于 b"
else
echo "$a = $b : a 不等于 b"
fi
常见字符串测试
-z string 字符串string 为空串(长度为0)时返回真 -n string 字符串string 为非空串时返回真 str1 = str2 字符串str1 和字符串str2 相等时返回真 str1 == str2 同 = str1 != str2 字符串str1 和字符串str2 不相等时返回真 str1 < str2 按字典顺序排序,字符串str1 在字符串str2 之前 str1 > str2 按字典顺序排序,字符串str1 在字符串str2 之后
文件测试运算
file="C:/helloworld.sh"
if [ -r $file ] # 测试问价是否可读
then
echo "文件可读"
else
echo "文件不可读"
fi
文件状态测试
-b filename 当filename 存在并且是块文件时返回真(返回0) -c
filename当filename 存在并且是字符文件时返回真 -d
pathname当pathname 存在并且是一个目录时返回真 -e
pathname当由pathname 指定的文件或目录存在时返回真 -f
filename当filename 存在并且是正规(普通)文件时返回真 -g
pathname当由pathname 指定的文件或目录存在并且设置了SGID 位时返回真 -h/-L
filename当filename 存在并且是符号链接文件时返回真 (或 filename) -k
pathname当由pathname 指定的文件或目录存在并且设置了"粘滞"位时返回真 -p
filename当filename 存在并且是命名管道时返回真 -r
pathname当由pathname 指定的文件或目录存在并且可读时返回真 -s
filename当filename 存在并且文件大小大于0 时返回真 -S
filename当filename 存在并且是socket 时返回真 -t
fd当fd 是与终端设备相关联的文件描述符时返回真 -u
pathname当由pathname 指定的文件或目录存在并且设置了SUID 位时返回真 -w
pathname当由pathname 指定的文件或目录存在并且可写时返回真 -x
pathname当由pathname 指定的文件或目录存在并且可执行时返回真 -O
pathname当由pathname 存在并且被当前进程的有效用户id 的用户拥有时返回真(字母O 大写) -G
pathname当由pathname 存在并且属于当前进程的有效用户id 的用户的用户组时返回真 file1 -nt
file2file1 比file2 新时返回真 file1 -ot
file2file1 比file2 旧时返回真 f1 -ef
f2files f1 and f2 are hard links to the same file
条件测试
test
test <测试 表达式>
# test命令和 < 测试表达式 >之间至少有一个空格
[]
[ < 测试表 达式> ]
# 该方法和 test 命令的用法一样, [] 的边界和内容之间至少有一个空格
[[]]
[[ < 测试 表达式> ]]
# 比 test 和 [] 更新的语法格式。 [[]] 的边界和内容之间至少有一个空格。 [[]] 中可以使用通配符等进行模式匹配
四、一些命令
echo命令
# 用法
echo string
# 显示字符串,双引号可以省略,默认就是字符串格式
echo "hello world"
echo hello world
# 显示双引号
echo "\"hello world\""
echo \"hello world\"
# 显示换行,转义字符
# -e 开启转义
echo -e "OK! \n"
echo It is a test
#显示变量的值
name=xiaoming
echo "$name is my name"
#显示结果定向至文件
echo It is a test > myfile
#原样输出字符串,不进行转义或取变量(用单引号)
echo '$name\"'
# 显示执行结果
echo `date`
显示变量的值:
printf命令
# 用法
printf format-string [arguments...]
# format-string:为格式控制字符串
# arguments:为参数列表。
# 例子:
printf "%-10s %-8s %-4s\n" Name Sex Weight-kg
printf "%-10s %-8s %-4.2f\n" GuoJing male 66.1234
printf "%-10s %-8s %-4.2f\n" YangGuo male 48.6543
printf "%-10s %-8s %-4.2f\n" GuoFu female 47.9876
# 输出:(通过格式控制,输出非常整齐)
Name Sex Weight-kg
GuoJing male 66.12
YangGuo male 48.65
GuoFu female 47.99
printf的转义序列
序列 说明 \a 警告字符,通常为ASCII的BEL字符 \b 后退 \c 抑制(不显示)输出结果中任何结尾的换行字符(只在%b格式指示符控制下的参数字符串中有效),而且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略 \f 换页(formfeed) \n 换行 \r 回车(Carriage return) \t 水平制表符 \v 垂直制表符 两个反斜杠 一个字面上的反斜杠字符 \ddd 表示1到3位数八进制值的字符。仅在格式字符串中有效 \0ddd 表示1到3位的八进制值字符
输入,输出重定向
通常linux系统命令的输入和输出都是终端(标准输入,输出),有时候我们需要重新定向到其他地方。
命令 | 说明 |
---|---|
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)。
输出重定向
# 命令执行command1然后将输出的内容存入file1
# 注意任何file1内的已经存在的内容将被新内容替代。如果要将新内容添加在文件末尾,使用>>追加操作符。
command1 > file1
输入重定向
# 从文件获取输入
command1 < file1
输入输出同时重定向
# 执行command1,从文件infile读取内容,然后将输出写入到outfile中
command1 < 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
command 2 > file # 将错误信息保存到文件
# 将 stdout 和 stderr 合并后重定向到 file
command > file 2>&1
Here Document
Here Document 是 Shell 中的一种特殊的重定向方式,用来将输入重定向到一个交互式 Shell 脚本或程序。
# 将两个 delimiter 之间的内容(document) 作为输入传递给 command。
command << delimiter
document
delimiter
/dev/null 文件
/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到”禁止输出”的效果。
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null
# 将command的执行输出丢弃
command > /dev/null 2>&1
五、程序控制语句
if条件判断
# if语句
# 注意事项:
# if 和 []直接有一个空格
# [ 条件 ] : 条件的前后都有空格
# else if => elif
if [ 条件判断 ];then #命令写到同一行需要加分号
逻辑处理 -> shell命令
xxx
xxxx
xxxx
fi # 结束
# 另一种写法
if [ 条件判断 ]
then
逻辑处理 -> shell命令
xxx
xxx
fi
# if ... elif .. fi
if [ 条件判断 ];then
逻辑处理 -> shell命令
xxx
xxxx
xxxx
elif [ 条件判断 ];then
shell命令
elif [ 条件判断 ];then
shell命令
elif [ 条件判断 ];then
shell命令
else
shell命令
fi
#!/bin/bash
# 需要对传递到脚本内部的文件名做判断
if [ -d $1 ];then
echo "$1 是一个目录!"
elif [ -s $1 ];then
echo "$1 是一个文件, 并文件不为空"
else
echo "$1 不是目录, 有可能不存在, 或文件大小为0"
fi
for和while循环
# shell中的循环 for/ while
# 语法: for 变量 in 集合; do;done
for var in 集合;do
shell命令
done
=================
while 测试条件
do
shell命令
done
#!/bin/bash
# 对当前目录下的文件进行遍历
list=`ls` #反单引号
for var in $list;do
echo "当前文件: $var"
echo '当前文件: $var' # 单引号,原样输出
done
# 运行脚本
$ ./for.sh
当前文件: a
当前文件: $var
当前文件: abc
当前文件: $var
当前文件: for.sh
当前文件: $var
当前文件: if.sh
当前文件: $var
当前文件: test.sh
当前文件: $var
六、shell函数
# 注意:没有函数修饰, 没有参数列表
# 格式
funcName(){
# 得到第一个参数
arg1=$1
# 得到第2个参数
arg2=$2
函数体 = shell命令 + 逻辑循环和判断
}
# 没有参数列表, 但是可以传参
# 函数调用
funcName aa bb cc dd #调用没有括号
# 函数调用之后的状态:
0 -> 调用成功
非0 -> 失败
#!/bin/bash
# 判断传递进行来的文件名是不是目录, 如果不是, 创建...
# 定义函数
is_directory()
{
# 得到文件名, 通过参数得到文件名
name=$1
if [ -d $name ];then
echo "$name 是一个目录!"
else
# 创建目录
mkdir $name
if [ 0 -ne $? ];then
echo "目录创建失败..."
exit
fi
echo "目录创建成功!!!"
fi
}
# 函数调用
is_directory $1
# 有返回值函数
#!/bin/bash
funWithReturn(){
echo "Two nums add each other..."
echo "Input the first num1:"
read num1
echo "Input the second num2:"
read num2
echo "Two nums are $num1 and $num2"
return $(($num1+$num2))
}
# 函数调用
funWithReturn
# 使用参数
echo "num1 + num2 = $?"
七、文件包含
和其他语言一样,Shell 也可以包含外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。
. filename # 注意点号(.)和文件名中间有一空格
或
source filename
参考学习资料
https://www.bookstack.cn/read/ShellBasicProgramming/README.md