Linux 中 shell 脚本的语法

既然要说一门编程语言,自然就避不开语法,只有当源文件的内容符合语法规范的时候,才能保证程序有正常执行的可能。

测试判断

test

利用 test 命令可以检测文件或是相关的属性,主要的参数为:

参数含义
文件类型判断:test -e filename
-e判断 filename 是否存在
-f判断 filename 是否存在且为 file
-d判断 filename 是否存在且为 directory
-b

判断 filename 是否存在且为 block device 设备

-c

判断 filename 是否存在且为 character device 设备

-S判断 filename 是否存在且为 socket 文件
-p判断 filename 是否存在且为 FIFO 文件
-L判断 filename 是否存在且为 link 文件
文件权限检测:test -r filename
-r判断 filename 是否存在且具有 r 权限
-w判断 filename 是否存在且具有 w 权限
-x判断 filename 是否存在且具有 x 权限
-u判断 filename 是否存在且具有 SUID 权限
-g判断 filename 是否存在且具有 SGID 权限
-k判断 filename 是否存在且具有 Sticky bit 权限
-s判断 filename 是否存在且为非空文件
文件比较:test file1 -nt file2
-nt判断 file1 是否比 file2 新
-ot判断 file1 是否比 file2 旧
-ef判断 file1 与 file2 是否为同一文件,主要在判断两个文件是否指向同一个 inode
整数比较:test n1 -eq n2
-eq相等
-ne不等
-gt

n1 大于 n2

-ltn1 小于 n2
-ge

n1 大于等于 n2

-len1 小于等于 n2
字符串判断
test -z string判断 string 是否为空,空返回 true
test -n string判断 string 是否为非空,空返回 false,-n可省略
test str1 == str2判断 str1 是否等于 str2,相等返回 true
test str1 != str2判断 str1 是否不等于 str2,相等返回 false
多重条件判断:test -r filename -a -x filename
-a

条件同时成立,即 filename 同时具有 r 和 x 的权限时,才返回 true

-o任一条件成立,即 filename 同时具有 r 或 x 的权限时,才返回 true
条件反向,不具有相应条件时,返回 true

shell 脚本名为 test.sh,内容为:

# Description: using shell script to test command test.
# Version: 1.0
# Author: wood
# Address: None
# Copyright: None
# History: None

PATH=${PATH}
export PATH

str1=${PWD}
str2='/tmp/test'

# target shell script
filename='exp.sh'

# test current dir and str2 is same or not,if not, change dir
test ${str1} == ${str2} || cd ${str2} 

# test filename is a file or not,if not, echo error message
test -f ${filename} || echo -e "NO file named exp.sh"

# test filename has a x perm, if not, change perm
test -x ${filename} || chmod +100 ${filename} 

# execute filename
bash "./${filename}"

执行结果为:

wood@ubuntu:/tmp/test$ ll
total 16
drwxrwxr-x  2 wood wood 4096 Apr  7 12:55 ./
drwxrwxrwt 14 root root 4096 Apr  7 12:56 ../
-rw-rw-r--  1 wood wood  195 Apr  7 10:49 exp.sh
-rw-rw-r--  1 wood wood  380 Apr  7 12:55 test.sh
wood@ubuntu:/tmp/test$ cd ..
wood@ubuntu:/tmp$ chmod +100 ./test/test.sh 
wood@ubuntu:/tmp$ ./test/test.sh 
Hello world
wood@ubuntu:/tmp$ source ./test/test.sh 
Hello world
wood@ubuntu:/tmp/test$

在脚本中,我们判断当前工作目录是否是 /tmp/test,如果不是就想要使用 cd 命令跳转到 /tmp/test。但是直接执行却并没有发生跳转,而使用 source 命令才发生了跳转,这说明这两种执行方式是有区别的。

直接执行

此种情况下的命令执行(如相对路径/绝对路径,bash,修改 ${PATH}等),会在当前的进程中重开一个子进程,调用的命令都在此子进程中运行。而子进程结束时,子进程中的各个变量和各项操作都会结束而不会对父进程产生影响。因此脚本中的 cd 命令对于父进程是无效的。

source

我们在 bash 的操作环境中曾经提到过,使用 source 命令可以使得当前修改的环境配置文件立即生效。

而在 shell 脚本的执行过程中,使用此命令可以使该脚本在当前进程,也就是父进程中运行,直到结束,因此使用 source 命令,cd 命令会有效。

判断符号[]

中括号 [] 也能起到判断的作用。

wood@ubuntu:/tmp/test$ ll
total 16
drwxrwxr-x  2 wood wood 4096 Apr  7 12:55 ./
drwxrwxrwt 14 root root 4096 Apr  7 13:16 ../
-rwxrw-r--  1 wood wood  195 Apr  7 10:49 exp.sh*
-rwxrw-r--  1 wood wood  380 Apr  7 12:55 test.sh*
wood@ubuntu:/tmp/test$ [ -f test.sh ]
wood@ubuntu:/tmp/test$ echo $?
0

只是中括号在 Linux 使用的地方比较多,因此在使用中括号作为判断时,应当注意:

  • 中括号两端应当有空格进行分隔
  • 中括号中的每个组件之间也需要有空格进行分隔
  • 中括号中的变量,最好用双引号括住
  • 中括号中的常量,最好也用单引号或者双引号括住

这里我们将上边的程序用判断符号 [] 进行改写:

# Description: using shell script to test symbol [].
# Version: 1.0
# Author: wood
# Address: None
# Copyright: None
# History: None

PATH=${PATH}
export PATH

str1=${PWD}
str2='/tmp/test'

# target shell script
filename='exp.sh'

# test current dir and str2 is same or not,if not, change dir
​[ "${str1}" == "${str2}" ] || cd ${str2} 

# test filename is a file or not,if not, echo error message
[ -f "${filename}" ] || echo -e "NO file named exp.sh"

# test filename has a x perm, if not, change perm
[ -x "${filename}" ] || chmod +100 ${filename} 

# execute filename
bash "./${filename}"

执行结果为:

wood@ubuntu:/tmp/test$ ll
total 16
drwxrwxr-x  2 wood wood 4096 Apr  7 13:22 ./
drwxrwxrwt 14 root root 4096 Apr  7 13:23 ../
-rw-rw-r--  1 wood wood  195 Apr  7 10:49 exp.sh
-rw-rw-r--  1 wood wood  385 Apr  7 13:22 test.sh
wood@ubuntu:/tmp/test$ cd ..
wood@ubuntu:/tmp$ chmod +100 ./test/test.sh 
wood@ubuntu:/tmp$ ./test/test.sh 
Hello world
wood@ubuntu:/tmp$ source ./test/test.sh 
Hello world
wood@ubuntu:/tmp/test$ cat test.sh 

shell 脚本的参数

我们使用其它编程语言编制的程序有时候是能够进行参数传递的,同样 shell 脚本也是能够传递参数的。

对于 shell 脚本,参数情况为:

scriptname opt1 opt2 opt3 opt4
#  ${0}    ${1} ${2} ${3} ${4}

从上边看,脚本名算是一个参数,跟在脚本后边以空格分隔的项目就是其余的参数,参数从 0 开始,逐次递增。

在脚本当中也是使用特殊的变量来进行参数调用:

$#

表示脚本名后跟的参数数目
$@表示 “${1}” “${2}” “${3}” “${4}”,每个变量都是独立的,通常情况下使用该形式
$*表示 "${1}c${2}c${3}c${4}",c 为分隔符,默认为空格
# Description: using shell script to test shell opt.
# Version: 1.0
# Author: wood
# Address: None
# Copyright: None
# History: None

PATH=${PATH}
export PATH

echo "The script opt number is ${#}"
echo "The first opt is ${0}"
echo "The second opt is ${1}"
echo "The third opt is ${2}"
echo "The forth opt is ${3}"

上述脚本执行结果为:

wood@ubuntu:/tmp/test$ ./opt.sh a b c
The script opt number is 3
The first opt is ./opt.sh
The second opt is a
The third opt is b
The forth opt is c

将上边的程序中多加一个 shift:

# Description: using shell script to test shell opt.
# Version: 1.0
# Author: wood
# Address: None
# Copyright: None
# History: None

PATH=${PATH}
export PATH

echo "The script opt number is ${#}"
echo "The first opt is ${0}"
echo "The second opt is ${1}"
echo "The third opt is ${2}"
echo "The forth opt is ${3}"
shift
echo "The script opt number is ${#}"
echo "The first opt is ${0}"
echo "The second opt is ${1}"
echo "The third opt is ${2}"
echo "The forth opt is ${3}"

上边脚本的执行结果为:

wood@ubuntu:/tmp/test$ ./opt.sh a b c d
The script opt number is 4
The first opt is ./opt.sh
The second opt is a
The third opt is b
The forth opt is c
The script opt number is 3
The first opt is ./opt.sh
The second opt is b
The third opt is c
The forth opt is d

从上边结果可以看出,shift 能够使 scriptname 后边的参数依次消失,默认为 1。shift 后跟数字 n 就是消失 n 个参数。

条件判断

if...than

单层判断

# a conditional statement
if [ conditional ]; then
    statement
fi

# several conditional statement
if [ conditional1 -o conditional2 ]; then
    statement
fi

# several conditional statement
if [ conditional1 ] || [ conditional2 ]; then
    statement
fi

多重判断

# a branch
if [ conditional ]; then
    statement
else
    statement
fi

# several branch
if [ conditional1 ]; then
    statement
elif [ conditional2 ]; then
    statement
else
    statement
fi

case...esac

如果说 if...then 语句类似与 C/C++ 中的 if...then,那么 case...esac 就有点像 C/C++ 中的 switch 语句了,结构为:

case ${var} in
  "var content1")
      statement
      ;;
  "var content2")
      statement
      ;;
  ...
  *)
      statement
      exit 1
      ;;
esac

循环

while do done

该方式为当条件成立时,就进行循环,直到条件不成立停止:

while [ conditional ]
do
    statement
done

until do done

该方式为当条件成立时,就终止循环,否则就一直循环:

until [ conditional ]
do
    statement
done

for...do...done

该方式为按照预设的循环模式运行:

for var in var1 var2 var3 ...
do
    statement
done

除了上边的形式,该循环还有另外一种形式:

for ((init;condtional;assignment))
do
    statement
done

这种形式有点类似与 C/C++ 中的 for 循环,(inti;conditional;assignment) 的写法与 C/C++ 中的 for 循环写法完全相同。

函数

一般情况下,编程语言都是这样的顺序,顺序结构,条件结构,循环结构,之后就是函数,因为函数可以实现自定义的功能,扩展性更强。Linux 中的 shell 脚本函数写法为:

function fname(){
    statement
}

值得注意的是,我们之前提到过 shell 脚本的执行顺序为从上到下,从左到右,因此 shell 脚本中的函数部分需要写在脚本的最前边,这样才能够找到要执行的函数。

而函数都是可以有参数的,shell 脚本函数中的参数形式与别的编程语言有点不同,看下边名为 func.sh 的脚本:

# Description: using shell script to implement function.
# Version: 1.0
# Author: wood
# Address: None
# Copyright: None
# History: None

PATH=${PATH}
export PATH

function foo(){
    echo "The script opt number is ${#}"
    echo "The first opt is ${0}"
    echo "The second opt is ${1}"
    echo "The third opt is ${2}"
    echo "The forth opt is ${3}"
}

echo "The script opt number is ${#}"
echo "The first opt is ${0}"
echo "The second opt is ${1}"
echo "The third opt is ${2}"
echo "The forth opt is ${3}"

foo ${0} ${1} ${2} ${3}

上边脚本的执行结果为:

wood@ubuntu:/tmp/test$ ./func.sh a b c
The script opt number is 3
The first opt is ./func.sh
The second opt is a
The third opt is b
The forth opt is c
The script opt number is 4
The first opt is ./func.sh
The second opt is ./func.sh
The third opt is a
The forth opt is b

从上边基本能够看出脚本函数的传递规则。

另外这种函数的写法跟 MATLAB 中的函数写法好像有点类似,不知道是谁参考谁的。

跟踪调试

基本上,程序都编写完毕之后,都要进行调试,对于 shell 脚本,调试方式为:

sh [-nvx] scriptname

上边的参数为:

-n不执行脚本,只查询语法问题
-v执行脚本前,先将脚本文件内容输出到屏幕
-x将使用到的脚本内容显示到屏幕上

这里我们用之前会调用脚本 exp.sh 的脚本 test.sh 来测试一下效果:

wood@ubuntu:/tmp/test$ sh -n test.sh 
wood@ubuntu:/tmp/test$ sh -v test.sh 
# Description: using shell script to implement Hello world.
# Version: 1.0
# Author: wood
# Address: None
# Copyright: None
# History: None

PATH=${PATH}
export PATH

str1=${PWD}
str2='/tmp/test'

filename='exp.sh'

[ "${str1}" == "${str2}" ] || cd ${str2} 
test.sh: 16: [: /tmp/test: unexpected operator

[ -f "${filename}" ] || echo -e "NO file named exp.sh"

[ -x "${filename}" ] || chmod +100 ${filename} 

bash "./${filename}"
Hello world
wood@ubuntu:/tmp/test$ sh -x test.sh 
+ PATH=/opt/Anaconda3/bin:/home/wood/bin:/home/wood/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
+ export PATH
+ str1=/tmp/test
+ str2=/tmp/test
+ filename=exp.sh
+ [ /tmp/test == /tmp/test ]
test.sh: 16: [: /tmp/test: unexpected operator
+ cd /tmp/test
+ [ -f exp.sh ]
+ [ -x exp.sh ]
+ bash ./exp.sh
Hello world

上边的 -n 参数在语法没有问题时不会输出任何信息。

而 -x 参数则将所有的命令都进行了替换,也即是说我们使用 -x 参数看到的内容就已经是执行时的情况了,这对于我们查找错误来说是十分方便的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值