目录
前言
基本语法
开头
注释
变量
基本语法
环境变量
特殊变量
流程控制
if-else语句
case语句
select语句
loop循环表达式
函数
shell运算
算术运算
布尔运算
布尔运算
字符串运算
文件运算
shell命令
sh/bash、source、./的区别
exit和shift
实战:一份简单的java启动脚本
Shell可以认为是使用linux的桥梁,它是由c语言所编写的一个命令解释器,处于内核和用户之间,负责把用户的指令传递给内核并且把执行结果回馈给用户。学会shell编程,可以更方便的管理服务器以及实现相关自动化。本文则为博主学习shell脚本的记录,以基础为主,入门shell的语法。
文件的第一行最好以下面的格式开始,符号#!用来告诉系统它后面的参数是用来执行该文件的程序,当然如果你执意不写也不会报错,因为一般系统有固定的shell,不定这行时会采用当前系统默认的shell中执行,因此这行其实是为了规范和脚本移植问题而写
#!/bin/sh
当编辑好脚本后,如果要执行该脚本,还必须使其可执行。要使脚本可执行: chmod +x filename,这样才能用./filename 来运行,当然你也可以不必每次都赋权限,可直接使用sh filename来执行该脚本。最后还是建议把文件格式改成以.sh结尾。
shell中的注释以#开头,注释的最大好处是能提高代码的可读性,方便其余人进行维护,同时足够多的注释也能方便自己在遗忘的时候回忆起这行代码的意义。批量注释的快捷键与idea中的一样,这里只能给出mac下文本编辑框中的批量快捷键:command+/
所有其他的主流编程语言您都必须使用变量进行操作,shell也不例外。在shell编程中,所有的变量都是字符串,并且您不需要对变量进行申明可直接赋值。下面基本操作都在例子中展现:
#!/bin/sh
# 符号#!用来告诉系统它后面的参数是用来执行该文件的程序
# 给变量赋值,变量与=号之间不要有空格
a="hello world"
echo $a
# 有时候变量名和容易与其他文字混淆,如下b变量,程序会去搜索变量bnd的值,发现未定义因此无法打印
b=2
echo "this is the $bnd"
# 可以使用花括号来避免混淆的问题
echo "this is the ${b}nd"
输出:
由export关键字处理过的变量叫做环境变量,这里暂时不对该变量进行讨论,因为一般来说只在登陆脚本中才会使用到环境变量,日后学习到时在补充。
在shell编程也存在着一系列特殊变量,这些变量往往是用来方便对当前运行脚本作操作,具体介绍及使用在例子中展现:
#!/bin/sh
# $# 是传给脚本的参数个数
echo "number :$#"
# $0 是脚本本身的名字
echo "fileName :$0"
# $1 是传递给该shell脚本的第一个参数
echo "first :$1"
# $2 是传递给该shell脚本的第二个参数
echo "second :$2"
# $@ 是传给脚本的所有参数的列表,每个位置参数都当做独立的元素处理
echo "allArg :$@"
# $* 也是代表传给脚本的所有参数的列表,但是将所有位置参数当做一个字符串处理,稍后举个例子阐述两者的区别
echo "allArgWithOne :$*"
结合输出来看可能更明显:
可能大家发现了$*和$@变量的输出结果好像是一样的,可能看了上面的解释也不是很明显,下面给出一个额外的例子来区分:
#!/bin/sh
# 演示$*和$@的区别
echo "\$*=$*"
echo "-------------"
# 提前使用循环语法
for n in "$*"; do echo $n; done
echo ""
echo "\$@=$@"
echo "-------------"
for n in "$@"; do echo $n; done
输出如下:
基本语法格式为:
if ...; then
...
elif ...; then
...
else
...
fi
绝大数情况下通常使用测试命令来对条件进行测试,例如比较字符串、判断文件是否存在及是否可读等,条件测试表达式为[ ],千万记得方括号两边需留出空格!下面给出例子:
#!/bin/sh
a="hello"
b="hello"
# if-else条件语法,[]表示条件测试,注意需留出空格给方括号,同时=号两边也需要留出空格,否则既不会报错也得不到正确的结果
if [ "$a" = "$b" ]; then
echo "yes"
else
echo "no"
fi
# [ -f ] 判断是否是一个文件
# if [ -f $curPath/test01.sh ]; then
# echo "Start to execute test01.sh"
# sh $curPath/test01.sh
# else
# echo "Doesn't exist test01.sh"
# fi
# [ -n ] 判断变量是否有值,注意加引号
if [ -n "$c" ]; then
echo "yes"
else
echo "no"
fi
输出如下:
基本语法格式如下,其中*)相当于其他语言中的default,;;有点类似于break,但;;是强制性的。
case ... in
...) doSomething ;;
...) doSomething ;;
*) doSomething ;;
esac
举个小例子:
#!/bin/sh
# 取得当前脚本的文件部分,可以是去掉目录,也可以是去掉后缀
name=`basename $0 .sh`
case $1 in
s|start)
echo "start..."
;;
stop)
echo "stop ..."
;;
reload)
echo "reload..."
;;
*)
echo "Usage: $name [start|stop|reload]"
exit 1
;;
esac
# 多bb一句,0是正常退出,1是异常退出
exit 0
输出:
基本语法格式如下,select表达式是一种bash的扩展引用,常用于交互式使用,其自动弹出in语句之后的菜单项,并自动赋值给变量var,select本身就是一种循环,使用break可当用户选择之后跳出循环。但是select本身在循环的时候不再显示菜单项,只循环输入。
select var in ...
do
here $var can be used
break
done
下面给出一个最简单的例子:
#!/bin/sh
# 演示select语法,select语句本身就是一个循环,但是不会循环菜单,因此不是很建议使用其自带的循环
echo "What is your favourite anchor?"
select anchor in "wuwukai" "pdd" "uzi" "zztai"
do
echo "You have selected $anchor"
# 使用break可当选择之后跳出循环
break
done
输出如下,使用了break因此不会循环
可以发现上面输出中的#?,这是shell脚本的默认提示符,在select表达式中,可以使用PS3提示符进行提示,如下:
#!/bin/sh
PS3="Please choose your number[1-4]:" # 设置提示符
echo "What is your favourite anchor?"
select anchor in "wuwukai" "pdd" "uzi" "zztai"
do
echo "You have selected $anchor"
# 使用break可当选择之后跳出循环
break
done
select表达式通常搭配可以搭配其他表达式形成更为复杂的菜单交互,这边以搭配case为例:
#!/bin/sh
PS3="Please choose your number[1-4]:" # 设置提示符
echo "What is your favourite anchor?"
select anchor in "wuwukai" "pdd" "uzi" "zztai"
do
case $anchor in
wuwukai) echo "五五开" ;;
pdd) echo "嫖断吊" ;;
uzi) echo "澡王" ;;
zztai) echo "贼贼态";;
*) continue ;;
esac
echo "You have selected $anchor"
done
shell的循环语法和其它也都大同小异,也都支持continue和break关键词,continue即不执行余下的部分直接跳到下一个循环,break是指跳出当前循环。其基本语法格式如下:
while [ ... ]
do
#statements
done
for (( i = 0; i < 10; i++ ))
do
#statements
done
for var in ...
do
#statements
done
# 演示循环表达式 while和for
# 只要满足条件则执行do下面的语句,可使用break提前退出
for (( i = 1; i <= 10; i++ ))
do
echo "This is $i loop"
done
echo
for i in c b a
do
echo $i
done
echo
i=5
j=0
while [ $i != $j ]
do
echo $j
let j++
done
shell中的死循环格式为如下,它将会一直输出i love u,直到control+c退出
while :
do
echo "i love u"
done
首先所有函数必须先定义再使用,即定义必须在使用前面,在调用函数时支持传参,函数内部获取参数时与调用脚本时获取参数类似,采用$1即可。基本语法如下:
# 一般来说为了方便都不带function前缀
function functionName()
{
[return int;] # 若想返回参数,可以显示加return,return后跟数值n(0-255),函数返回值在外层可通过使用$?获取
}
fun() {
echo "这是我的第一个shell函数"
}
fun
funWithReturn() {
echo "这个函数会对输入的两个数字进行相加运算..."
echo "请输入第一个数字"
read i
echo "请输入第二个数字"
read j
echo "两个数字分别是 $i 和 $j"
return $(($i + $j))
}
funWithReturn
echo "输入的两个数字之和为 $? !"
# 演示所有运算符
# 1、算术运算,指加(+)减(-)乘(*)除(/)和求余(%),共有三种方式:let expr ((...))
a=2
b=3
let "c0=$a+$b" # 可以看到let后面接的是完整表达式,注意等号及运算符两边不可以有空格!
echo $c0 # 5
c1=`expr $a - $b` # 可以看到expr后面接的是具体运算表达式,采用``语法将值计算完后赋给前面变量
echo $c1 # -1
c2=`expr $a-$b` # 运算符两边需带上空格
echo $c2 # 2-3
c3=`expr $a \* $b` # 如果不转义将会报语法错误,注意 * ( ) 都需要进行转义
echo $c3 # 6
c4=$(($a+$b)) # ((...))写法最自由,表达式部分可以带空格也可以不带
echo $c4 # 5
c5=$(($a*$b)) # 且乘号* 左括号( 右括号)均不需要转义
echo $c5 # 6
# 2、关系运算,大致有等于:-eq(equal)/==,不等于:-ne(not equal)/!=,大于:-gt(greater than),
#大于等于:-ge(greater or equal),小于:-lt(less than),小于等于:-le(less or equal)
a=10
b=20 # 需要注意的是关系运算符只支持数组类型,当然如果字符串的值是数字也行
if [ $a == $b ]
then
echo "$a == $b"
else
echo "$a != $b"
fi
if [ $a -lt $b ]
then
echo "$a < $b"
fi
# 另外的就不演示了,当然关系运算还有另外一种表达式(()),类比算术运算,括号里头可直接使用> < >= <=
if ((a <= b))
then
echo "a <= b"
fi
if !(($a >= $b))
then
echo "! ($a <= $b)"
else
echo "error"
fi
也就是我们常说的与和或表达式
# 3、布尔表达式,与:-o(or),或:-a(and)
a=10
b=20
if [ $a -lt $b -a $a -ne $b ]
then
echo "$a < $b and $a != $b"
fi
if [ $a -eq $b -o $a -ge $b ]
then
echo "$a == $b || $a <= $b"
else
echo "都不满足"
fi
文件测试运算符用于检测 Unix 文件的各种属性,比如是否是目录阿、是否可读、可写、字节大小什么的。
虽然你可以在shell脚本中使用任意的linux命令,但还是有一些比较常用的命令,这些命令通常是用来进行文件和文字操作的。这一块的命令列表不作具体展示了,因为其本质就是linux下的命令:
#!/bin/bash
# 符号#!用来告诉系统它后面的参数是用来执行该文件的程序
# 在shell脚本中可以使用任意的unix命令,如常用的cp mv grep find 等等等
# 移动到上层目录
cd ../
# 展示文件列表
ls
# 返回文件所在路径,下面例子将返回/study
dirname /study/text.txt
# 输出text.txt文本信息
cat text.txt
输出:
1、sh a.sh bash a.sh
都是打开一个subshell去进行读取和执行a.sh,同时a.sh不需要有执行权限,子shell不会影响到父shell
2、source a.sh
在当前shell内取读取和执行a.sh,a.sh也不需要有执行权限,父shell可以直接使用子shell中的变量和函数,如果子shell中直接报错即exit 1,那么父shell中余下部分也不会再执行,因此当需要判断子shell的返回结果时最好用sh或bash
3、./ a.sh
打开一个subshell取读取和执行a.sh,需要有执行权限,可通过chmod +x a.sh添加执行权限
shell中脚本的执行结果往往可以使用exit来返回,当exit 0的时候代表程序执行成功,一旦返回不为0则代表程序出错,一般exit 1,比如脚本a调用脚本b时,可以在调用完成后根据特殊变量$?获取脚本b的返回结果来判断程序是否执行成功,一般业务上可自定义错误返回结果,反正只要不为0均可。直接看例子:
test06.sh:
#!/bin/sh
# shift语法测试,只要入参个数大于0,则继续循环
# shift命令用于参数左移,后面的数字代表左移几位,通常用于在不知道传入参数个数的情况下依次遍历每个参数然后
# 进行相应处理
# while [ $# -gt 0 ] # 关系运算常用包含:-gt -lt -ge -le -eq -ne 要求两边必须为数字,否则报错
# do
# echo "第一个参数为:$1,参数总数为:$#"
# shift 2
# done
while [ $# -gt 0 ] # 关系运算常用包含:-gt -lt -ge -le -eq -ne 要求两边必须为数字,否则报错
do
case $1 in
a) echo 第二个参数为:$2
shift 2 ;;
b) echo 第二个参数为:$2
shift 2 ;;
*) echo "无法匹配不再左移"
exit 1 ;;
esac
done
exitTest.sh:
#!/bin/sh
# exit演示
echo "开始 exit演示,调用test06.sh"
source test06.sh a b
if [ $? -eq 0 ]
then
echo "test06.sh执行成功"
else
echo "test06.sh执行失败"
fi
echo
# source test06.sh c 这里也再次验证了source和sh对父shell的影响,要想获得其错误返回结果1,请务必使用sh
# 否则执行完test06.sh直接退出当前脚本
sh test06.sh c
if [ $? -eq 0 ] # $? 是获取上一行语句的返回结果
then
echo "test06.sh执行成功"
elif [ $? -eq 1 ]
then
echo "test06.sh执行失败"
else
echo "fk"
fi
测试如下:
可以说是每一行都带了注释,对初学者很友好了!hhh
#!/bin/sh
# 一份简单的java启动脚本
# 当前脚本所在目录
CUR_PATH=$(cd `dirname $0`; pwd)
# java程序所在的目录
WORK_PATH=$CUR_PATH/..
# 所有的class类文件路径,包括所依赖的jar,linux下可通过:拼接,windows可通过;分隔
CLASS_PATH=:$WORK_PATH
# 需要程序启动的java主程序(main方法类)
MAIN_CLASS=StringTest
# java运行参数,设置最大堆内存/初始化堆内存/新生代内存
JAVA_OPTS="-Xmx16m -Xms16m -Xmn6m"
# 获取进程号
getPid() {
PID=0
# pgrep以名称为依据从运行进程队列中查找进程,并显示查找到的进程id,默认以新行分隔。-f表示使用完整的进程名进行匹配
PID_TEMP=`pgrep -f $MAIN_CLASS`
# 推荐条件表达式都为变量加上引号,这样即使变量没有值,整个表达式也是合法的
if [ -n "$PID_TEMP" ]; then
PID=$PID_TEMP
fi
}
# 启动
start() {
getPid
if [ "$PID" -ne 0 ]; then
# 进程号不为0说明该进程已存在
echo "$MAIN_CLASS may be already started (PID=$PID)"
else
# \c表示不换行
echo "Starting ${MAIN_CLASS}\c"
# -cp表示文件的类搜索路径
# 代表不间断的运行命令,即使终端退出,java程序依然会运行
# > 代表输出日志,而/dev/null文件代表任何东西全都重定向到这边,一般在我们不关心时使用,建议不要生成nohup.out(> output)
nohup java $JAVA_OPTS -cp $CLASS_PATH $MAIN_CLASS > /dev/null 2>&1 &
# 睡眠3s等待进程启完
sleep 3s
getPid
if [ "$PID" -ne 0 ]; then
echo "(PID=${PID})[Success]"
else
echo "[Failed]"
fi
fi
}
# 停止
stop() {
getPid
if [ "$PID" -ne 0 ]; then
# 如果出现乱码请不要偷懒,使用花括号来精确匹配
echo "Stopping ${MAIN_CLASS}(PID=${PID})\c"
kill -9 $PID
# kill执行成功返回0
if [ $? -eq 0 ]; then
echo "[Success]"
else
echo "[Failed]"
fi
else
echo "$MAIN_CLASS may be already stopped"
fi
}
# 进程状态
status() {
getPid
if [ "$PID" -ne 0 ]; then
echo "${MAIN_CLASS} is running(PID=${PID})"
else
echo "${MAIN_CLASS} is not running"
fi
}
# 根据入参判断所要执行的方法
case $1 in
start)
start ;;
stop)
stop ;;
restart)
stop
start ;;
status)
status ;;
*)
# 输出帮助语句,即当前脚本支持何种入参
echo "$0 Usage:[start|stop|restart|status]"
exit 1
esac
如果你能完整的看到这里,相信你在看脚本的时候已经大致能看懂个一二了,剩余的无非是一些巧妙的用法,这里也不再总结,总之自己多练才是重点。互勉。