shell script可以将一些指令汇总起来一次执行(有点像windows下的.bat文件)。但是不仅仅如此,它还有更强大的功能。
前面我们已经了解过了shell就是一些可执行的命令,或者说可执行程序。我们在文件里面编写我们想要完成的逻辑,运行程序就能完成编写的内容。比如说每次开机都要读取环境变量是不是很麻烦?把他写入到shell script就好了。
shell script 更提供数组、循环、条件与逻辑判断等重要功能。且无需编译就能运行,即写即用,非常方便。
当然是为了简化我们对计算机的操作啦。而且学习shell script后我们就能看懂很多系统的配置文件了,可以自己修改配置哦。
其实前面我们应该已经编写过了。很简单: 首先编写程序,然后将文件设置成可执行。就OK了,当然如果想要在任何位置都能执行脚本的话就得把脚本的路径添加到环境变量。当然最好是我们新建一个文件夹,然后我们自己写的所有脚本都放到这个文件夹下。只需要将这个文件夹的路径添加至环境变量就行了。
cd ~
mkdir bin
cd bin
PATH = "$PATH":~/bin
vim hello.sh
hello.sh:
#!/bin/bash #固定格式
echo "Hello World!"
chmod +x * #当前目录下所有的文件都变成可执行文件
hello.sh #执行文件
是不是很简单呢?但是我们发现上面这种设置环境变量的方法在我们重新登陆后就失效了,也就是说我们每次都要执行PATH = "$PATH":~/bin
太麻烦了。怎么办?修改~/.bashrc文件。
vim ~/.bashrc
#在文件的最后添加
PATH="$PATH":~/bin
#保存退出
但是注意,这个文件是每次登录的时候执行的,也就是说修改后并没有生效,如想要立即生效,请执行source ~/.bashrc
当然也有可能你的这个文件并不会在登陆的时候执行。解决方法请看前面的笔记。
养成良好的编程习惯是很有必要的,程序并不是能够运行就行了的,以后可能还需要维护,可能自己还要重温,所以添加一定的注释是有必要的。shell中的注释符号是 # 。
还有可以记录程序完成的功能,程序的最后修改时间 甚至是程序的编写者。
变量内容由使用者决定
也就是参数由用户输入。要用到的指令: read
==要求:==请你以 read 指令的用途,撰写一个 script ,他可以让使用者输入:1 first name 与 2. last name, 最后并且在屏幕上显示:『Your full name is: 』的内容:
#!/bin/bash
echo "input your firstname:"
read firstname
echo "input your lastname:"
read lastname
echo "Your full name is:"$firstname-$lastname
一点注释都没写 ~~
利用 date 进行档案的建立
想象一个状况,如果我每天要进行备份,而备份的数据又不想被覆盖掉,也就是说, 我想要将每天备份的数据放在不同的档案中。哇!这真困扰啊?难道要我每天去修改 script ? 不需要啊!因为每天的『日期』并不相同,所以我可以将档名取成类似: backup.20050802 , 不就可以每天一个不同档名了吗
==要求:==我想要建立三个空的档案,档名最开头由使用者输入决定,假设使用者输入 filename 好了, 那今天的日期是 2005/08/23 ,我想要以前天、昨天、今天的日期来建立这个档案,亦即 filename_20050821, filename_20050822, filename_20050823 ,该如何是好?
我们直接使用date 会打印英文,而我们需要数字。通过date -I就能实现了
#!/bin/bash
#backup script
echo "input your backup filename:"
read filename
#真的文件名需要加上日期,所以在后面加上date -I,
#而引号的作用就相当于执行引号里面的指令,返回结果
file1="$filename"-`date -I`
#create file
touch ${file1}
但是上面的代码并没有符合题目的要求,我的日期中间有 - 。
输入两个数,然后输出他们的乘积
这个好像很简单,但是需要知道 计算式子要在$((式子))里面。还有非常要注意的就是变量的赋值 = 左右不能有空格,我已经在这上面得到很多次了。
#!/bin/bash
#input two number A , B
#output A * B
echo -e "input tow number:\n"
read -p "your first number:" first
read -p "the second number:" second
res=$(($first*$second))
echo $res
但是这样只能计算两个整数,如果你输入小数的话就会提示你格式错误哦。
善用判断式
前面讲过了 问号变量 ?:(关于上个执行指令的回传码),一般来说,如果成功的执行该指令,则会回传一个 0 值,如果执行过程发生错误,就会回传『错误代码』才对!一般就是以非为 0 的数值来取代。
在shell脚本中我们可以好好的利用这个回传值。
例如:我们想要知道~/qqq这个目录是否存在,我们可以先ls ~/qqq
然后再利用$?
进行判断。
这很复杂,我们可以直接通过 test指令来进行判断。
test - check file types and compare values
-e 参数能测试一个file是否存在,更多test的指令可以参考man test
test -e ~/qqq && echo "yes" || echo "no"
OK!现在我们就利用 test 来帮我们写几个简单的例子。首先,判断一下, 让使用者输入一个档名,我们判断:
一定要好好要运用test命令哦!
-O FILE
FILE exists and is owned by the effective user ID-r FILE
FILE exists and read permission is granted-w FILE
FILE exists and write permission is granted-x FILE
FILE exists and execute (or search) permission is granted
#!/bin/bash
#input a path,if doesnt exist ,printf("File doesnot exist")
#else
read -p "input a file name:" name
#如果文件不存在就输出提示并推出
test -e $name || echo "the file $name doesnot exist!"
test -e $name || exit 0
#若这个档案存在,则判断他是个档案或目录,结果输出『Filename is regular file』或 『Filename is directory』
test -f $name && echo "Filename is regular file"
test -d $name && echo "Filename is directory"
#判断一下,执行者的身份对这个档案或目录所拥有的权限,并输出权限数据!
test -r $name && echo "file is readable"
test -w $name && echo "file is writable"
test -x $name && echo "file is excutable"
利用判断符号 [ ]
除了要利用好test指令之外,我们还可以利用符号 [ ] 来进行数据的判断,举例来说。如果我们想要知道%HOME这个变量是否为空,可以这样做 : [ -z "$HOME" ]
**注意!!!**使用 [ ] 的时候每个组件中间都需要有空格来隔开,否则的话就会有语法错误
所以说,您最好要注意:
在中括号 [] 内的每个组件都需要有空格键来分隔;
在中括号内的变量,最好都以双引号来设定;
在中括号内的常数,最好都以单或双引号来设定。
[ ] 的使用和test几乎一模一样,只是中括号比较常用在条件判断式if … then … fi中。
案例
#!/bin/bash
#让使用者选择 Y 或 N
echo "input y/Y or n/N:"
read choice
#如果使用者输入 Y 或 y 时,就显示『 OK, continue 』且退出
[ $choice == "y" ] || [ $choice == "Y" ] && echo "OK,continue" && exit 0
#如果使用者输入 n 或 N 时,就显示『 Oh, interrupt !』且退出
[ $choice == "n" ] || [ $choice == "N" ] && echo "OK,interrupt !" && exit 0
#如果不是 Y/y/N/n 之内的其它字符,就显示『I don't know what is your choise』
echo "i dont know what is your choice."
其实,当我们执行一个 shell script 时,在这个 shell script 里面就已将帮我们做好一些可用的变量了。 举例来说,在不久的将来,您就会发现,当我们要启动一个系统服务时,可能会下达类似这样的指令:/etc/init.d/crond restart
那是啥玩意儿?呵呵!就是『向 /etc/init.d/crond 这个 script 下达 restart 的指令』, 咦!我们不是都使用 read 来读取使用者输入的变量内容吗?为啥我可以直接在 script 后面接上这个参数?这是因为 shell script 帮我们设定好一些指定的变量了!变量的对应是这样的:
/path/to/scriptname opt1 opt2 opt3 opt4 ...
$0 $1 $2 $3 $4 ...
这是什么意思呢?大家如果有C语言或者java的学习经历的话应该就不难理解了,我们在命令行执行一个可以行文件的时候 不仅可以直接输入可执行文件名来执行,也可以在可执行文件名后面通过空格传入一些参数,然后可以在里面获取到参数并根据不同的参数执行不同的指令。这里也是一样的。反正就理解为 我们如果在运行的文件后面加入了参数的话这些参数就会被保存到$1 $2 …这些变量中就是了。
好了,来做个例子吧~假设我要执行一个 script ,执行后,该 script 会自动列出自己的档案名, 还有后面接的前三个参数,该如何是好?
提示:好好利用test指令。
-n STRING
the length of STRING is nonzero-z STRING
the length of STRING is zero
#!/bin/bash
echo $0
[ -n "$1" ] && echo "$1" ||exit 0
[ -n "$2" ] && echo "$2" ||exit 0
[ -n "$3" ] && echo "$3" ||exit 0
注意!!![ ] 里面$1必须要有双引号引起来,-n必须要对STRING进行判断
看到条件判断式就想到if
if … then
这是最常见的判断句式了,注意格式
if [ 条件判断式 ]; then
当条件判断式成立时,可以进行的指令工作内容;
fi
if [ 条件判断式1 ] && [ 条件判断式2 ]; then
当条件判断式成立时,可以进行的指令工作内容;
fi
中间的[ 条件判断式 ]
就是我们前面讲的。
if [ 条件判断式 ]; then
当条件判断式成立时,可以进行的指令工作内容;
else
当条件判断式不成立时,可以进行的指令工作内容;
fi
if [ 条件判断式一 ]; then
当条件判断式一成立时,可以进行的指令工作内容;
elif [ 条件判断式二 ]; then
当条件判断式二成立时,可以进行的指令工作内容;
else
当条件判断式一与二均不成立时,可以进行的指令工作内容;
fi
case … esac
通过上面的if … fi我们就能发现每个判断语句的结束都是它开始的reverse(逆转)
相信有其他语言基础的同学也不会对case陌生,但是一定要注意bash中的格式,其中的 *
代表0个或多个任意字符
case $变量名称 in
"第一个变量内容")
程序段
;;
"第二个变量内容")
程序段
;;
*)
不包含第一个变量内容与第二个变量内容的其它程序执行段
exit 1
;;
esac
利用 function 功能
没错,就是函数,相信有其他语言基础的同学也不会模式咯。就是我们可以把一写实现的功能类似但是又重复执行的代码提取到一个函数中,这样在我们需要这个功能的时候直接调用函数就行了,这样既能减少代码量,也能提高程序的可读性。
function fname() {
程序段
}
要注意的是,在 shell script 当中, function 的设定一定要在程序的最前面, 这样才能够在执行时被找到可用的程序段喔!
方法的调用只需要方法名就行了,不需要 括号。并且函数也有内置变量(就是前面说到的$0 $1 $2之类的,其中$0是函数名,$1是第一参数…)
循环 (loop)
while循环
循环就是根据根据某个表达式是否成立来决定是否执行循环体。
while [ condition ]
do
程序段落
done
until [ condition ]
do
程序段落
done
其中第一个是满足condition的时候执行程序段落,直到不满足后退出。而第二段是不符合condition的时候执行程序段落,直到符合condition后退出。
做一个简单的练习:当用户输出yes或者YES的时候才退出程序,否则就一直接收用户输入并显示。很简单:
#!/bin/bash
read -p "input your string:(input yes or YES to exit)" str
while [ "$str" != "yes" ] && [ "$str" != "YES" ]
do
echo -n "your input is :"
echo -e "\033[31;47m$str\033[0m"
read -p "input your string:(input yes or YES to exit)" str
done
练习:使用循环计算1+2+3+4 … +100并输出最终结果。
#!/bin/bash
declare -i index=1
declare -i sum=0
while [ "$index" != "101" ]
do
sum=$(($sum+index))
index=$(($index+1))
done
echo -e "result is :"$sum
也可以如下:
#!/bin/bash
declare -i index=1
declare -i sum=0
declare -i edge=100
while [ $index -le $edge ]
do
sum=$(($sum+$index))
index=$(($index+1))
done
echo -e $sum
现在稍微改进一下程序,如果让用户输入一个正整数,然后输出 从0到该正整数的和。想必也是小菜一碟啦~~
#!/bin/bash
#让用户输入一个正整数,然后输出0到这个正整数的和
declare -i edge
declare -i index=0
declare -i sum=0
read -p "input a positive integer:" edge
while [ $edge -le $index ]
do
read -p "you MUST input a positive integer:" edge
done
while [ $index -le $edge ]
do
sum=$(($sum+$index))
index=$(($index+1))
done
echo "the sum form 1 to $edge is :"$sum
for循环
相信大家对for循环也并不陌生,只是要注意在bash中的使用格式
for…do…done
for (( 初始值; 限制值; 执行步阶 ))
do
程序段
done
同样都是do 和done之间执行程序段,但是要注意执行程序段的条件,也就是要特别注意for (( 初始值; 限制值; 执行步阶 ))
这个语句。我们在C语言的for是这么用的:for(i=0;i<100;i++)。一共有两个分号把括号分为三个部分,其中第一部分是初始条件,第二部分是判断是否执行的条件,如果满足第二部分的话就执行方法体,第三部分是执行完方法体之后要执行的部分,也就是出口条件。虽然上面的说法有点看不懂,但是其实这三部分都是一样的。
练习:使用for循环输出1到100的和。
#!/bin/bash
#使用for循环计算1到 100的和
declare -i index=1
declare -i sum=0
for ((;index<101;index++))
do
sum=$(($sum+$index))
done
echo $sum
通过上面的方式,我们可以控制循环的次数。但是for循环的功能还不仅如此。我们还可以像如下的形式使用for:
for var in con1 con2 con3 ...
do
程序段
done
其中var是一个变量,con1…是常量。上面循环的意思就是第一次循环var的值是con1,第二次循环var的值为con2…依此类推。
shell script 的追踪与 debug
scripts 在执行之前,最怕的就是出现问题了!那么我们如何 debug 呢?有没有办法不需要透过直接执行该 scripts 就可以来判断是否有问题呢!?呵呵! 当然是有的!我们就直接以 bash 的相关参数来进行判断吧!
[root@linux ~]# sh [-nvx] scripts.sh
参数:
-n :不要执行 script,仅查询语法的问题;
-v :再执行 sccript 前,先将 scripts 的内容输出到屏幕上;
-x :将使用到的 script 内容显示到屏幕上,这是很有用的参数!
例如我们可以通过sh -n sh16.sh
来判断sh16.sh中是否有语法错误。 若语法没有问题,则不会显示任何信息!如果有语法错误,则会显示语法错误,但是注意,这里只能是语法错误,如果是command not found等错误的话也是不会提示的哦!!大家可以自己试试。