学习shell脚本开发之前,必须得有一些基础的Linux相关知识,比如环境变量和shell指令的等内容。
了解Linux的系统环境变量和shell命令可以看看我之前发过的两篇文章。
Shell命令(Linux操作命令笔记)
Linux学习笔记之二(环境变量)
再说回shell脚本和shell命令,简单来说,我们在Linux终端中敲下的大多数指令都是shell指令,比如cd , ls, pwd之类的。而 shell脚本就是在一个文件中把这些shell指令依次写在一起,然后让其自动依次执行。这样我们就不需要在终端一条条的去执行了。来看个例子吧。
例程目的:我想切换到$HOME/target目录,并且列出target目录下的所有文件。
首先我先新建一个test.sh的文件,然后往里面写入对应的shell指令。shell脚本文件的第一句话必须是:
#! /bin/bash
写这句话相当于在告诉Linux系统,我写的这个文件是shell脚本,请用bash解释器来解释这个脚本。
最后执行这个脚本就可以看到我target文件夹下的所有文件了。
常用的shell指令不妨移步
等于号
-eq
note: equal
不等号
-ne
note: not equal
大于号
-gt
note: greater than
大于或等于号
-ge
note: greater than or equal to
小于号
-lt
note: less than
小于等于号
-le
note: less than or equal to
逻辑与
-a
eg: [ $a -eq $b -a $a -eq $c ] #a等于b于a等于c
逻辑或
-o
eg: [ $a -eq $b -o $a -eq $c ] #a等于b或a等于c
比较运算符号大概就这些,note后面是缩写符号的整体意思,希望能够帮助记忆。但一点需要值得注意,以上这些特殊字符之用于判断整数与整数之间的关系,而不能用于判断字符串。如果想要判断字符串还得用 = = == == , ! = != !=号这些,同时这些常规符号也兼容整数判断。至于逻辑运算符则也可以用&&, ||代替上述的-a和-o。
下面举几个例子来看一下-eq和 = = == ==的区别。
name="lihua"
age=18
[ "$name" -eq "lihua" ]; ❌
[ "$name" == "lihua" ]; ✔
[ "$age" == 18 ]; ✔
此外,在用$符号对变量取值的时候,最后还是加上一对双引号,这样可以避免很多错误。比如下面这个例子:
name="li hua"
[ $name == "li hua" ] ❌
[ "$name" == "li hua" ] ✔
如果不加双引号,那$name等于li hua,第一条语句就变成了下面这个样子,显然会报错。
li hua == "li hua"
加了引号就变成。
"li hua" == "li hua"
并且加引号只是把这个变量视为一个整体,并不改变数据本来的属性,是字符串的还是字符串,是整形的还是整形。
以上这些都是一些常见的单选项标记符。判断为真则返回1,否则返回。常用于if语句中,比如我们想判断home目录下的object_file是不是可执行文件,可以做如下操作。
if [ -x $HOME/object_file ];then
echo "it is a execuate file"
fi
除了以上这些特殊标记符,还有许多特殊状态变量,如下:
看来$ 这个符号在Linux中还挺重要的,个人感觉它有点”取值“的意味,我们取一个变量也会用到$, 比如 $variable 。
最后我写一个脚本把这些特殊状态变量都用上,来看看以上这些特殊变量到底怎么用的。
执行一下看看效果:
下面介绍一些出现频率比较高的命令,但我仅讲一些常见的应用,不求全面详细。
echo常用于打印变量。可以选择是否添加e和n参数,前者使echo可以识别转移字符,后者让echo打印之后不换行(echo默认打印后换行)。
举几个例子:
echo "hello"
echo -e "hello\n" #可以识别\n
echo -n "hello" #打印后不换行
echo -en "hello\t" #识别转义字符且不换行
echo -ne "hello\t" #效果同上
从上面例子可以看出,echo的参数可以自由组合,且不必考虑顺序,实现效果叠加。其他命令也同样可以。
export可以将变量转化成环境变量,所以我们经常在配置环境变量的时候用到这个命令。
export PATH=$PATH:$HOME/
在shell脚本中使用read命令正如在C语言中使用scanf命令或在python中使用input一样,可以获取用户的输入字符串。它可选的两个参数有p和t两个参数,前者可以显示提示符,后者可以设置等待用户输入的时间。
上个例子,这里我编写一个read.sh的脚本。
#! /bin/bash
read -p "please enter a number: " num_1
read -p "please enter another number: " -t 5 num_2
echo -e "\nthe first number is $num_1"
if [ -n "$num_2" ];then
echo "the second number is $num_2"
else
echo "there is no the second number"
fi
declare和typeset的功能基本一样,所以这里只说declare。
由于shell和python一样,都是弱定义型语言,即变量的类型取决于它所接受到值的类型,无需提前规定好。但弱定义偶尔也有一些不好地方,比如我们本来希望它是一个整型变量,它却因各种原因变成字符串类型。比如read会把数字也视为字符串。。
而declare的主要作用就是声明变量类型,让变量的类型不至于”跑偏“。它常用的参数有以下这些:
看个例子:
declare -i num
read -p "please enter a number: " num
这样就可以保证num这个变量就是一个整型了。
再来看看数组怎么用:
#! /bin/bash
declare -a num
read -p "please enter a number: " num[0]
read -p "please enter another number: " -t 5 num[1]
echo -e "\nthe first number is ${num[0]}"
if [ -n "$num[1]" ];then
echo "the second number is ${num[1]}"
else
echo "there is no the second number"
fi
其实还是用[ ]来索引数组里面的值,但由于$的优先级高于[ ],所以记得用{ }来使num先索引再取值。除了用declare定义,我们也可以用弱定义的方式定义一个固定长度的数组。
num[0]=1
num[1]=2
..
声明一个变量是一个局部变量,由于shell语言中不同于大多数程序语言,在其函数内部声明的变量不会被自动视为局部变量。所以有时候为了使函数中的变量是局部变量就会用到local命令。
比如下面这个例子,在test这个函数内声明了一个var的局部变量,如果在函数外打印它就会报错。
#! /bin/bash
function test{
local var=1
}
echo "$var"
unset就比较简单了,其功能是删除一个变量。如下,删除var变量之后再调用echo是会报错的。
#! /bin/bash
var="test"
unset var
echo "$var"
看到unset便自然的想到是否有set,其实是有的,只不过它们的功能大相径庭,set主要用于显示所有环境变量。所以与unset相反的作用的更像是typeset。
基本用法如下,注意该空格的地方不能省略,不该空格的地方不能多加。
#! /bin/bash
age=18
if [ "$age" -gt 18 ];then
echo "adult"
else
echo "child"
fi
在shell脚本中的中括号的主要用于条件测试,我们在看看if和逻辑预算符号一起使用的例子。
#! /bin/bash
age=21
if [ "$age" -eq 18 ] && [ "$age" -lt 20 ];
then
echo "yes"
else
echo "no"
fi
#! /bin/bash
for i in {1..4};
do
echo "$i"
done
shell中的大括号用处挺多的,但这里的大括号表示生成一系列可能的集合,即1,2,3,4。另外,在也可借助双引号的能力来像C语言那样实现从1到4的遍历。
for((i=1; i<=4; i++));
do
echo "$i"
done
#! /bin/bash
counter=0
while [ "$counter" -le 5 ]
do
echo "Counter: $counter"
((counter++))
done
counter++外面必须包裹两个小括号,因为双括号用于逻辑运算和数学操作。
fruit="apple"
case "$fruit" in
"apple")
echo "You chose an apple.";;
"banana")
echo "You chose a banana.";;
"orange"|"grape")
echo "You chose an orange or a grape.";;
*)
echo "You chose something else.";;
esac
其中,“ * ) ”表示默认。在这里指如果fruit不是apple, banana, orange, grape其中一个就执行 “ * ) ”之后的操作。
shell中的函数定义就比较奇怪了,主要体现在参数传输这一块。我们可以直接看看代码。
#! /bin/bash
function name()
{
name="$1"
age="$2"
echo "his name is $name"
echo "his age is $age"
}
name "xaioming" "18"
可以看到,上述例程所定义的函数之前有一个function的关键字,但其实这个是可有可无的。其次,可以用"$1"之类的符号来获得传入的参数。另外,即便调用该函数的时候传入了两个参数,但只拿除一个来用也无所谓。
#! /bin/bash
name()
{
name="$1"
#age="$2"
echo "his name is $name"
#echo "his age is $age"
}
name "xaioming" "18"
还有几点需要注意的,其一就是shell脚本和python一样,调用该函数必须在该函数定义之后。其二,在函数里面定义的变量如何不用local声明,那么是会被认为是全局变量的,这样可能会照成很多麻烦。
前面提到shell脚本的编写逻辑和python有点像,调用函数之前必须在先定义函数。那么当定义的函数多了,就势必导致代码变成非常混乱和难读,这时候就需要学会多脚本链接了。
在一个名为script_1.sh的脚本写一个函数:
#! /bin/bash
function hello()
{
name="$1"
echo "hello, i am $name"
}
在一个名为script_2.sh的脚本调用hello函数,并且传入名字:
#! /bin/bash
source ./script_1.sh
hello "li hua"
当你在script_2.sh中执行source script_1.sh这句代码的时候,它会引入script_1.sh的文件的代码并执行它。