Linux学习笔记(九)


Shell编程

脚本

shell脚本工作原理:

  • loader程序加载脚本,发现不是elf可执行文件,返回错误
  • bash收到错误,然后打开文件头部,发现是脚本
  • 调用脚本执行

几乎所有编程语言的教程都是从使用著名的“Hello World”开始的,我们的第一个 Shell 脚本也输出“Hello World”。

使用vim创建一个新的文本文件,并输入

#!/bin/sh
echo "Hello World !"  #这是一条语句

第 1 行的#!是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell;后面的/bin/sh就是指明了解释器的具体位置。然后将文件修改为可执行文件 chmod 777 filename 可以为所有用户赋予读写执行的权限,如果有其他需求修改即可。

shell的注释以#开头,#后面的内容即为注释

脚本调试

在编写较长并且复杂的脚本文件容易出现错误,且脚本文件是非编译程序,不配置的话是不会有编译器差错的。因为只能靠运行程序并尝试解释屏幕显示错误。

set命令选项

选项功能
-n读取命令但不执行,用来检查脚本中的语法错误
-v输出参数替换前的命令
-x输出的是带参数展开后的命令

如果想要关闭上述命令,将-换为+即可。

变量

关于变量赋值已经在八中讲过了,现在我们来学习一下怎么进行变量替换操作,既只有某种条件发生时才进行替换,这里举出几个最常用的:

替换操作功能
${value:-word}如果变量不存在(或为空),返回word,否则返回变量的值
${value:=word}如果变量不存在(或为空),设置VAR变量为word,返回word
${value:?word}如果变量不存在(或为空),打印message,退出,但交互shell不会退出
${value:+word}如果变量存在,返回word;否则返回null

删除变量则使用unset VARNAME即可

命令行参数

位置参数

运行 Shell 脚本文件时我们可以给它传递一些参数,这些参数在脚本文件内部可以使用$n的形式来接收,例如,$1 表示第一个参数,$2 表示第二个参数,依次类推,最多可以有10个命令行参数,如果参数个数太多,达到或者超过了 10 个,那么就得用${n}的形式来接收了,例如 ${10}、${23}。{ }的作用是为了帮助解释器识别参数的边界,这跟使用变量时加{ }是一样的效果。

同样,在调用函数时也可以传递参数。Shell 函数参数的传递和其它编程语言不同,没有所谓的形参和实参, 在定义函数时也不用指明参数的名字和数目。 换句话说,定义 Shell 函数时不能带参数,但是在调用函数时却可以传递参数,这些传递进来的参数,在函数内部就也使用$n的形式接收,例如,$1 表示第一个参数,$2 表示第二个参数,依次类推。

这种通过$n的形式来接收的参数,在 Shell 中称为位置参数,除了 $n,Shell 中还有 $#、$*、$@、$?、$$ 几个特殊参数。

给脚本文件传递位置参数
#!/bin/sh
echo "Language: $1"
echo "URL: $2"

运行并附带参数:

给函数传递位置参数

#!/bin/sh
#定义函数
function func(){
    echo "Language: $1"
    echo "URL: $2"
}
#调用函数
func shell https://github.com

特殊参数

变量含义
$0当前脚本的文件名
$n(n≥1)传递给脚本或函数的参数。n 是一个数字,表示第几个参数
$#传递给脚本或函数的参数个数
$*传递给脚本或函数的所有参数,将所有参数使用$IFS隔开,形成一个字符串
$@传递给脚本或函数的所有参数。当被双引号” “包含时,$@ 与 $* 稍有不同
$?上个命令的退出状态,或函数的返回值
$$表示当前进程id

$* 和 $@ 都表示传递给函数或脚本的所有参数,这里重点区分一下当,$*和 $@ 不被双引号” “包围时,它们之间没有任何区别,都是将接收到的每个参数看做一份数据,彼此之间以空格来分隔。

但是当它们被双引号” “包含时,就会有区别了:

  • "$*"会将所有的参数从整体上看做一份数据,而不是把每个参数都看做一份数据。
  • "$@"仍然将每个参数都看作一份数据,彼此之间是独立的。

比如传递了 5 个参数,那么对于”$*”来说,这 5 个参数会合并到一起形成一份数据,它们之间是无法分割的;而对于”$@”来说,这 5 个参数是相互独立的,它们是 5 份数据。

如果使用 echo 直接输出”$*”和”$@”做对比,是看不出区别的;但如果使用 for 循环来逐个输出数据,立即就能看出区别来,这里先提前使用for循环,如果不了解的话请在学习后再来回顾。

#!/bin/sh
echo "print each param from \"\$*\""
for var in "$*"
do
    echo "$var"
done
echo "print each param from \"\$@\""
for var in "$@"
do
    echo "$var"
done

例子

$? 获取上一个命令的退出状态,所谓退出状态,就是上一个命令执行后的返回结果。退出状态是一个数字,一般情况下,大部分命令执行成功会返回 0,失败返回 1,这和C语言的 main() 函数是类似的,除了用于获取上一个命令退出状态外,还可以用来获取函数的返回值。这里仅讲获取函数的返回值,前一种情况类比即可。

#!/bin/bash
#得到两个数相加的和
function add(){
    return $(expr $1 + $2)
}
add 23 50  #调用函数
echo $?  #获取函数返回值

算术表达式

Shell中变量都是字符串,计算算术的时候使用$(())作为算术表达式并获取值(注意,双小括号运算仅在Bash shell中运行),否则将视作两个字符串操作。

echo $((3+2))

除了算术运算外还可以进行逻辑运算,规则与其他编程语言相同,不再累述了(值得注意的是对于字符串而言=也可以表示等于)。

逻辑连接

使用&&和||连接两条命令,&&表示and,||表示or,相似的,在shell中也有逻辑短路。

  • Command1 && command2,当command1正确执行,才会执行command2
  • Command1 || command2,当command1执行错误,才会执行command2

eval

eval命令将会首先扫描命令行进行所有的替换,然后再执行命令。该命令使用于那些一次扫描无法实现其功能的变量。该命令对变量进行两次扫描。这些需要进行两次扫描的变量有时候被称为复杂变量,eval告诉shell再跑一遍命令解释流程。

eval echo \$$# 取得最后一个参数

cat last
eval echo \$$#
./last one two three four
four

第一遍扫描后,shell把反斜杠去掉了。当shell再次扫描该行时,它替换了$4的值,并执行echo命令。

shift

位置参数可以用shift命令左移。比如shift 3表示原来的$4现在变成$1,原来的$5现在变成$2等等,原来的$1、$2、$3丢弃,$0不移动。不带参数的shift命令相当于shift 1。

我们知道,对于位置变量或命令行参数,其个数必须是确定的,或者当 Shell 程序不知道其个数时,可以把所有参数一起赋值给变量$*。若用户要求 Shell 在不知道位置变量个数的情况下,还能逐个的把参数一一处理,也就是在 $1 后为 $2,在 $2 后面为 $3 等。在 shift 命令执行前变量 $1 的值在 shift 命令执行后就不可用了。

until [ $# -eq 0 ]
do
echo "第一个参数为: $1 参数个数为: $#"
shift
done
执行以上程序x_shift.sh:
./x_shift.sh 1 2 3 4

结果显示如下:

第一个参数为: 1 参数个数为: 4
第一个参数为: 2 参数个数为: 3
第一个参数为: 3 参数个数为: 2
第一个参数为: 4 参数个数为: 1

条件分支

语法格式为:

if 条件; then
    command lines
[elif 条件
    command lines]
[else
    command lines]
fi

条件是一段命令,测试的是$?是否是0,为0满足条件,条件外面的括号是必须的,且条件必须用空格包围起来。

测试条件

测试字符串表达式

[]和test是等价的

逻辑运算符有:

  • -a与运算符
  • -o或运算符
  • -!非运算符
操作符示例说明
=“$STR1” = “STR2”两个字符串是否相同
!=“$STR1” != “STR2”两个字符串是否不同
-n-n “$STR”字符串不是null
-z-z “$STR”字符串是null

在引用变量做字符串测试时,一定要加引号(保证正确的检验),操作符两侧必须有空格,[ … ]中括号必须有空格

文件测试表达式
操作符示例说明
-r-r filename文件是否可读
-w-w filename文件是否可写
-s-s filename文件是否存在并且长度非0
-f-f filename文件是否为普通文件
-d-d filename文件是否为目录文件

例子:[ -e /var/log/syslog ] 判断是否存在

算术表达式测试

利用$(())计算算算术表达式

即[ $((2+3)) = 5 ]

#!/bin/sh
printf "Are you ok?\n"
printf "Input y for yes and no for no: "
read ANSWER
if [ "$ANSWER" = y ]; then
    printf "Glad to hear that!\n"
else
    printf "Go home!\n"
fi

case语句

case $1 in
    -f);;
    -d | --directory);;
    *);;
esac

case、in 和 esac 都是 Shell 关键字,;;和*)就相当于其它编程语言中的 break 和 default。

例子

#! /bin/sh
printf "Are you ok?\n"
printf "Input y for yes and no for no: "
read ANSWER
case $ANSWER in
    [Yy]) printf "Glad to hear that!\n";;
    [Nn]) printf "Go home!\n";;
       *) printf "No such option\n";;
esac

case in 的 pattern 部分支持简单的正则表达式,具体来说,可以使用以下几种格式:

格式说明
*表示任意字符串。
[abc]表示 a、b、c 三个字符中的任意一个。比如,[15ZH] 表示 1、5、Z、H 四个字符中的任意一个
[m-n]表示从 m 到 n 的任意一个字符。比如,[0-9] 表示任意一个数字,[0-9a-zA-Z] 表示字母或数字
|表示多重选择,类似逻辑运算中的或运算。比如,abc

循环

for循环

for循环有C风格的循环也有Python风格的for in循环,这里介绍后者。

for variable in list-of-values; dodone

list-of-values用$IFS隔开,缺省是空格、tab、回车,例子如下

#! /bin/sh
for count in 1 2 3; do
echo “In the loop for $count times”
done

while循环

while循环格式如:

while [ condition ]; dodone

[ condition ]等价于前述的test命令,例子如下

#! /bin/sh
count=1
while [ $((count < 10)) = “1” ]; do
echo $count
count=$((count+1))
done

until循环

Until循环的格式如下,Until表示条件为假时,执行循环体

until [ condition ]; dodone

例子如下:

#! /bin/sh
count=1
until [ $((count < 10)) = “0” ]; do
echo $count
count=$((count+1))
done

函数

函数已经在前面中出现过了,函数引用类似于普通命令,但是函数执行类似于内置命令,不会新fork一个shell。

Shell 函数定义的语法格式如下:

function name() {
    statements
    [return value]
}

调用 Shell 函数时可以给它传递参数,也可以不传递。如果不传递参数,直接给出函数名字即可,如果传递参数,那么多个参数之间以空格分隔,不管是哪种形式,函数名字后面都不需要带括号,和其它编程语言不同的是,Shell 函数在定义时不能指明参数,但是在调用时却可以传递参数,并且给它传递什么参数它就接收什么参数。

Shell 也不限制定义和调用的顺序,你可以将定义放在调用的前面,也可以反过来,将定义放在调用的后面。


文章作者: JoyTsing
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 JoyTsing !
评论
 上一篇
Linux学习笔记(十) Linux学习笔记(十)
df,du与归档压缩等df与dudf显示可用空间,-h用常见的格式显示出大小 du统计文件或者目录的磁盘使用情况,用得比较少。 这些命令通常是在要扩容和挂载时候才使用,配合fdisk -l和lvextend来使用。 tartar 命令
2020-05-11
下一篇 
Linux学习笔记(八) Linux学习笔记(八)
ShellUnix操作系统分为两个部分:内核和应用,内核是UNIX系统的核心并且驻留内存,日常事务比如直接与硬件通信等都由内核完成。除了内核,有一些基本模块也是驻留内存的,这些模块完成重要的功能,比如输入/输出管理,文件管理内存管理等待。
2020-05-08
  目录