Shell编程基础

[TOC]

Shell简介

什么是Shell

Shell是在Linux下的命令解释型语言(command-language interpreter),它的中文翻译为“壳”主要是用于人机交互。

Shell种类

Linux Shell的种类很多,目前流行的Shell包括ash、bash、ksh、csh、zsh等,用户可以通过查看/etc/shells 文件中的内容来查看自己主机中当前有哪些种类的Shell。

1
2
3
4
5
6
7
8
9
10
[djangowang@localhost ~]# cat /etc/shells
# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.
/bin/bash
/bin/csh
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh

编写第一个Shell程序

通常写程序我们都会从Hello World开始,编写第一个Shell程序我们也从他开始。关于Hello World的由来我们可以参考( http://blog.puppeter.com/read.php?25 )。以下为第一个Shell脚本程序,通过vim命令编辑hello.sh文件,其中sh为Shell脚本的扩展名文件。

1
2
3
4
5
6
7
#! /bin/bash Bash的命令解释器
# author:djangowang 标识脚本作者的名字
# time : 2021.1.27 标识脚本开发的时间
# filename: hello.sh 标识脚本的名字
# 建议初学者每次写脚本按照以上的书写方式,优势是并行开发过程中能查到脚本的作者和开发时间,方便后续有问题的回溯

echo "hello wolrd" # 调用系统命令打印结果。

变量

变量在Bash中变量顾名思义通常是可变的量,它来源于数学是计算机语言中能储存计算结果或能表示值抽象概念。

变量规范

变量名的命名须遵循如下规则:

  • _ 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头
  • _ 中间不能有空格,可以使用下划线(__)
  • 不能使用标点符号。 不能使用Bash里的关键字(可用help命令查看保留关键字)

Bash变量案例

在Bash中变量通过”$”符来表示,以下是一个Bash脚本的案例。

1
2
3
#!/bin/bash
name="This is Shell script" # 将字符串赋值给name
echo $name # 打印变量$name

这脚本最终的结果就会在屏幕上打印出This is Shell script。

变量四种赋值方式

这四种方式包括:

  • 直接赋值
  • read命令赋值
  • 命令赋值
  • 位置参数赋值

直接赋值
以下是一个Bash脚本,它将字符串“hi my name is djangowang”赋值给变量name并通过echo命令打印变量中的内容。

1
2
3
#!/bin/bash
name="hi my name is djangowang"
echo $name

read命令赋值
read是Bash中的内建命令,它从键盘获取标准输出并赋值给变量。以下是将键盘输入的内容赋值给变量name,并通过echo命令打印变量中的内容。

1
2
3
#!/bin/bash
read name
echo $name

命令赋值
获取系统命令的标准输出并将标准输出内容赋值给变量command,并通过echo命令打印变量中的内容。这里注意命令赋值方式共分为两种见以下案例。

1
2
3
4
5
6
#!/bin/bash
command = `date` # 推荐赋值方式 ,其中“`” 是键盘按键1边上的符号。
echo $command
# 或
command = $(date)
echo $command

位置参数赋值
位置参数赋值是通过通过执行脚本时传递参数赋值给变量。譬如以下脚本名为test.sh内容如下,通过执行/bin/sh test.sh hello,其中hello就是位置参数他会通过$1赋值给command变量,这里注意如果位置变量有空格又需要同时传给位置变量1可以通过“”来扩起来,譬如/bin/sh test.sh hello “hello world”。这里位置变量通过空格作为变量的分割符。

1
2
3
#!/bin/bash
command = $1
echo $command

意位置变量通常为数字$1-$9,10以上要用大括号扩起来如${10},${10}以下是案例。

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
# argc.sh a b c d e f g h i j k
echo $1
echo $2
echo $3
echo $4
echo $5
echo $6
echo $7
echo $8
echo $9
echo ${10}
echo ${11}

以上程序有个问题,如果位置参数要是大于10或更多这样写程序成本会很高且程序易读性也不好,这时我们可以使用shift命令,它用于参数的自动左移。

1
2
3
4
5
6
#!/bin/bash
while [ $# != 0 ]
do
echo "prama is $1,prama size is $#"
shift
done

定义变量类型

在Bash中默认为字符串类型,可以通过declare关键字指定变量的类型,还可以设置变量的属性或者删除变量。 以下介绍变量定义的七种方式:

  • 字符串型
  • 数值型
  • 数组
  • 函数
  • 设置环境变量
  • 只读变量
  • unset变量

字符串型
Bash中的默认数据类型为字符串型。

1
2
3
#!/bin/bash
string="hi my name is djangowang"
echo $string

数值型
在Bash中字符串类型只能用于字符串比较,不能进行数学运算。我们通过declare -i来定义将变量改为数值型,并进行数学运算。

1
declare -i number    # 定义一个数值型

我们来对比一下字符串型与数字型。

1
2
3
4
5
6
7
8
9
#!/bin/bash
# 字符串
n=6/3
echo "n = $n" # n = 6/3

# 数值型
declare -i n
n=6/3
echo "n = $n" # n = 2

数组
数组中可以存放多个值。Bash只支持一维数组,不支持多维数组,初始化时不需要定义数组大小,与大部分编程语言类似数组元素的下标由0开始。

1
declare -a array

数组案例。

1
2
3
4
5
6
7
#!/bin/bash
declare -a array
array=(A B "C" D)
echo "第一个元素为: ${array[0]}"
echo "第二个元素为: ${array[1]}"
echo "第三个元素为: ${array[2]}"
echo "第四个元素为: ${array[3]}"

函数
declare -f 函数名 ,用于显示函数内容。

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
function a(){
echo "test1"
}

function b(){
echo "test1"
}
declare -f # 显示以上函数
declare -f a # 接函数名,显示指定的函数

在Shell编程实战中应用不是很多。

设置环境变量
declare -x指定的变量会成为环境变量,可供Shell以外的程序来使用。

1
2
3
#!/bin/bash
declare -x STRING="hello world" # 定义一个string的环境变量,建议环境变量为大写
export -p # 列出所有的Shell赋予程序的环境变量

只读变量
declare -r var1与readonly var1作用相同。当设置只读变量后,变量内容不可以修改。

1
2
3
4
declare -r var1    # 设置一个只读变量
#或
readonly var1
readonly -p # 用于显示只读变量的清单

案例

1
2
3
4
#!/bin/bash
url="http://blog.puppeter.com/"
declare -r url # 或readonly url变量
url="http://blog.puppeter.com/" # 当修改变量时会报错误“/bin/sh: NAME: This variable is read only”

unset变量
unset用于删除变量。他有两个参数-f(仅删除函数)-v(仅删除变量)默认值。

1
2
3
4
5
#!/bin/bash
foo="hello world"
echo $foo # 输出hello world
unset foo # 删除foo变量
echo $foo # 为空

变量类型

在Bash中的变量是有作用域的,分别为:

  • 局部变量
  • 环境变量
  • 内部变量

局部变量

局部变量在脚本或命令中定义,仅在当前Bash实例中有效,其他Bash启动的程序不能访问局部变量。局部变量的关键字为”local”,以下为局部变量案例。

1
2
3
4
5
6
7
8
9
#!/bin/bash
function hello()
{
local text="Hello World!!!" # 定义局部变量
echo $text
}
text="this is test"
hello
echo $text # 可以试着去掉函数中的local,再执行本脚本的效果

前者输出结果为。

1
2
Hello World!!!
this is test

去掉local输出结果为。

1
2
Hello World!!!
Hello World!!!

环境变量

所有的程序包括Bash启动的程序都能访问环境变量,有些程序需要环境变量来保证其正常运行。在Bash中可以通过以下三个命令来查看环境变量,他们区别在于:

  • set 用来显示本地变量
  • env 用来显示环境变量
  • export -p 用来显示和设置环境变量

注:变量和环境变量的区别是:变量不能被子进程继承,而环境变量会被子进程继承。

我们还可以通过以下两个文件来设置环境变量:

  • /etc/profile
  • $HOME/.bash_profile

譬如bash_profile文件内容,

我们可以将自己的环境变量放在PATH后。

1
2
3
4
5
6
7
8
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# User specific environment and startup programs
PATH=$PATH:$HOME/bin
export PATH

内部变量

内部变量是Bash程序设置的特殊变量。Bash变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了Bash的正常运行,以下包含Bash的。

内部变量 解释
$BASH_VERSION Bash版本
$HOSTNAME hostname
$HOME 宿主目录(家目录)
$PATH 环境变量
$RANDOM 随机整数

Bash符号相关

在Bash中存在一些特殊符号,他们主要用于标准输出时的一些格式展现,如以下:
特殊符号对照表,表1。

符号 含义
\n 新行
\r 回车
\t 制表符
\v 垂直的制表符
\b 后退符
\a 警告(蜂鸣或是闪动)

在Bash中这些特殊符号主要用于以下两个命令的场景:

场景1:echo命令是我们学习Bash编程中一个很常用的命令,他用于打印信息到标准输出,譬如。

1
[root@blog.puppeter.com_centos ~]#  echo "hello world"    # 打印hello world到标准输出

目前echo有两个参数:

  • -n 不解析参数内的特殊符号
  • -e 默认值,解析参数内的特殊符号
1
2
[root@blog.puppeter.com_centos ~]# echo -n "hello\tworld"    # 不解析参数内制表符,同时不执行echo后的\n,特殊符号见表1
[root@blog.puppeter.com_centos ~]# echo -e "hello\tworld" # 解析参数中的制表符。

场景2:我们再来看一下Printf命令。与echo相同的都是打印内容到屏幕上,但printf命令模仿 C 程序库(library里的 printf() 程序,它由 POSIX 标准所定义,因此使用printf的脚本比使用echo移植性好,以下为案例。

1
2
3
4
5
#!/bin/bash
printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg
printf "%-10s %-8s %-4.2f\n" wds 男 66.1
printf "%-10s %-8s %-4.2f\n" djangowang 男 77.6543
printf "%-10s %-8s %-4.2f\n" hanmeimei 女 57.9876

我们再来看一下Printf命令。与echo相同的都是打印内容到屏幕上,但printf命令模仿 C 程序库(library里的 printf() 程序,它由 POSIX 标准所定义,因此使用printf的脚本比使用echo移植性好,以下为案例。

1
2
3
4
5
#!/bin/bash
printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg
printf "%-10s %-8s %-4.2f\n" wds 男 66.1
printf "%-10s %-8s %-4.2f\n" djangowang 男 77.6543
printf "%-10s %-8s %-4.2f\n" hanmeimei 女 57.9876

Read命令

read是Bash的内建命令,主要用于从键盘读取内容赋值给变量,它有以下常用参数:

  • -p :指定多个变量
    1
    2
    3
    4
    5
    #!/bin/bash

    read -p “please input your name " name
    echo "your name is $name"
    exit 0
  • -n :计数输入的字符
    1
    2
    3
    #!/bin/bash
    read -n6 -p “please input your password(lengh must be over 6 numbers)" passwd
    exit 0
  • -s :隐藏输入,不回显内容到终端
    1
    2
    3
    #!/bin/bash
    read -s -n6 -p “please input your password(lengh must be over 6 numbers)" passwd
    exit 0
  • -t :超时等待时间
    1
    2
    3
    4
    5
    6
    7
    8
    #!/bin/bash
    read -t 5 -p “please input your name" name
    if [ $? -eq 0 ];then
    echo "your name is $name"
    else
    echo "timeout"
    fi
    exit 0

条件语句

  • if..then..fi
  • if..then..else..fi
  • if..then..elif..fi

if..then..fi

首先来看一下if..then..fi的语法,它有多种书写方式 。
方式1

1
2
3
4
#!/bin/bash
if [ 条件语句 ];then # 推荐书写方式
执行内容
fi

方式2

1
2
3
4
if [ 条件语句 ]
then
执行内容
fi

方式3

1
2
3
if (());then
执行内容
fi

方式4

1
2
3
if command ;then
执行内容
fi

注:初学者一定要注意以上if..then语法中,条件语句两边都是有空格的,缺少一个空格都会报错且不容易被注意到。
再来看一下关于if..then..fi的案例。
1.if..then的[]案例,比较两个值是否相等,此方式主要用于字符串匹配。

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh
a=10
b=20

if [ $a == $b ];then # 如果if和then写在一行,需要通过;来进行分割
echo "a is equal to b"
fi

if [ $a != $b ]
then
echo "a is not equal to b"
fi

2.if..then的(())案例,数学方式比较大小,此方式主要用于数学方式的比较。

1
2
3
4
5
#!/bin/bash
i=100
if ((10 <$i));then # 数学比较方式
echo "true"
fi

3.if..command,判断是否为目录,此方式主要用于调用命令来判断命令返回最终结果。

1
2
3
4
5
#!/bin/bash
dir=/home/
if cd "$dir" 2>/dev/null; then # "2>/dev/null" 会隐藏错误信息.
echo "Now in $dir."
fi

if..then..else..fi

if..then..else..fi的语法。
方式1

1
2
3
4
5
6
#!/bin/bash
if [ 条件语句 ];then # 推荐书写方式
执行内容
else
执行内容2
fi

方式2

1
2
3
4
5
6
if [ 条件语句 ]
then
执行内容
else
执行内容2
fi

再来看一下if..then..else..fi的案例。

1
2
3
4
5
6
7
8
#!/bin/sh
a=10
b=20
if [ $a == $b ];then
echo "a is equal to b"
else
echo "a is not equal to b"
fi

if..then..elif..fi

if..then..elif..fi的语法:
方式1

1
2
3
4
5
6
7
if [ 条件语句 ];then
执行内容
elif [ 条件语句 ];then
执行内容
else
执行内容
fi

方式2

1
2
3
4
5
6
7
8
9
if [ 条件语句 ]
then
执行内容
elif [ 条件语句 ]
then
执行内容
else
执行内容
fi

if..then..elif..fi的案例。
案例1

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
a=10
b=20
if [ $a -eq $b ] ;then
echo "a is equal to b"
elif [ $a -gt $b ] ;then # a 大于 b 见表1
echo "a is greater than b"
elif [ $a -lt $b ];then # a 小于 b 见表1
echo "a is less than b"
else
echo "None of the condition met"
fi

文件比较符。

[ ]括号 (())扩容 含义
-eq == 等于
-ne != 不等于
-gt > 大于
-ge >= 大于等于
-lt < 小于
-le <= 小于等于

案例2
通过条件语句实现的计算器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash
# author:djangwoang
# filename:jisuanqi.sh

echo "please input your first number"
read a
echo "please input + - * /"
read b
echo "please input your second number"
read c

if [ "x$b" == "x" ];then
echo "please input + - * /"
elif [ "$b" == "+" ];then
tmp=$((a+c))
elif [ "$b" == "-" ];then
tmp=$((a-c))
elif [ "$b" == "*" ];then
tmp=$((a*c))
elif [ "$b" == "/" ];then
tmp=$((a/c))
else
echo "please input + - * /"
fi

echo "result is:${tmp}"

循环语句

Shell支持五中循环方式:

  • while循环
  • for循环
  • for..in循环
  • until循环
  • select循环

while循环

首先来看一下while循环的语法。
方式1

1
2
3
while [ 条件表达式 ];do    # 推荐
执行内容
done

方式2

1
2
3
4
while [ 条件表达式 ]
do
执行内容
done

方式3

1
while [ 条件表达式 ];do 执行内容 ;done    # Bash语句大都可以写作一行,只不过可读性差

方式4

1
2
3
while command;do
执行内容
done

再来看一下while循环的案例。
1.打印1-100的数字。

1
2
3
4
5
6
#!/bin/bash
i=1
while [ $i -le "100" ];do
echo $i
i=$((i+1))
done

2.打印1-100间的偶数。

1
2
3
4
5
6
7
8
9
#!/bin/bash
i=1
while [ $i -le "100" ];do
tmp=$((i%2))
if [ $tmp -eq 0 ];then
echo $i
fi
i=$((i+1))
done

3.打印/etc/passwd信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash    
while read line # 推荐
do
echo $line
done < /etc/passwd

# 或

#!/bin/bash
cat /etc/passwd | while read line
do
echo $line
done

4.死循环
死循环中条件表达式永远为真,如果要退出死循环可以用ctrl+c方式。

1
2
3
4
#!/bin/bash
while true ;do
echo "hello world"
done

for循环

for循环的语法。
方式1

1
2
3
for (( ; ; ));do    # 推荐
执行内容
done

方式2

1
2
3
4
for (( ; ;))
do
执行内容
done

方式3

1
for ((;;));do 执行内容 ;done 

for循环的案例。
1.打印1-100的数字。

1
2
3
for ((i=1; i<=100; i ++)); do
echo $i
done

2.打印1-100间的奇数。

1
2
3
4
5
6
7
#!/bin/bash
for((i=1;i<=100;i++));do
tmp=$((i%2))
if [ $tmp -ne 0 ];then
echo $i
fi
done

3.死循环。死循环中条件表达式永远为真,如果要退出死循环可以用ctrl+c方式.

1
2
3
4
#!/bin/bash
for((;;));do
echo "hello world"
done

for..in 循环

for..in循环的语法。
方式一

1
2
3
for 变量 in 条件语句;do    # 推荐
执行语句
done

方式二

1
2
3
4
for 变量 in 条件语句
do
执行语句
done

方式三

1
for 变量 in 条件语句;do 执行语句 ;done 

案例
1.打印1-5的数字。

1
2
3
for loop in 1 2 3 4 5 ;do
echo $loop
done

2.打印1-100间的数字。

1
2
3
for loop in `seq 1 100`;do
echo $loop
done

3.创建1-100的文件夹。

1
2
3
for loop in `seq 1 100`;do
mkdir $loop
done

until循环

until 循环执行一系列命令直至条件为 true 时停止。until 循环与 while 循环在处理方式上刚好相反,一般while循环优于until循环,但在某些时候,也只是极少数情况下,until 循环更加有用。首先来看一下until循环的语法。
方式一

1
2
3
until command;do    # 推荐
执行语句
done

方式二

1
2
3
4
until command
do
执行语句
done

select循环

select 是个无限循环,因此要记住用break命令退出循环或用exit命令终止脚本,也可以按ctrl+c 退出循环。我们首先看select的语法。
方式一

1
2
3
select name   [in   list ];do    # 推荐 
执行语句
done

方式二

1
2
3
4
select name   [in   list ] 
do
执行语句
done

案例,通常我们使用select用来做列表,案例如下。

1
2
3
4
5
6
#!/bin/bash
echo "What is your favourite OS?"
select var in "Linux" "Windows" "Free BSD" "Other"; do
break;
done
echo "You have selected $var"

分支语句

Shell的分支语句其实就是case,我们先来看一下它的语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
case 匹配内容 in
条件1)
执行内容1
执行内容2
;;
条件2)
执行内容1
执行内容2
;;
条件3)
执行内容1
执行内容2
;;
*)
默认执行
;;
esac

案例1,计算器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
if [ $# -ne 3 ];then
echo "参数个数应该为3,例如:$0 1 + 2"
exit 1;
fi

case $2 in
+)
echo "scale=2;$1+$3" | bc
;;
-)
echo "scale=2;$1-$3" | bc
;;
\*)
echo "scale=2;$1*$3" | bc
;;
/)
echo "scale=2;$1/$3" | bc
;;
*)
echo "$2 不是运算符"
;;
esac
exit 0

案例2,位置参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash
name=`basename $0 .sh`
case $1 in
START|start)
echo "start..."
;;
STOP|stop)
echo "stop ..."
;;
RELOAD|reload)
echo "reload..."
;;
*)
echo "Usage: $name [start|stop|reload]"
exit 1
;;
esac
exit 0

本章小结

习题