[TOC]
# Shell简介
## 什么是Shell
Shell是在Linux下的命令解释型语言(command-language interpreter),它的中文翻译为“壳”主要是用于人机交互。
## Shell种类
Linux Shell的种类很多,目前流行的Shell包括ash、bash、ksh、csh、zsh等,用户可以通过查看/etc/shells 文件中的内容来查看自己主机中当前有哪些种类的Shell。
```
[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脚本的扩展名文件。
```
#! /bin/bash Bash的命令解释器
# author:djangowang 标识脚本作者的名字
# time : 2021.1.27 标识脚本开发的时间
# filename: hello.sh 标识脚本的名字
# 建议初学者每次写脚本按照以上的书写方式,优势是并行开发过程中能查到脚本的作者和开发时间,方便后续有问题的回溯
echo "hello wolrd" # 调用系统命令打印结果。
```
# 变量
变量在Bash中变量顾名思义通常是可变的量,它来源于数学是计算机语言中能储存计算结果或能表示值抽象概念。
## 变量规范
变量名的命名须遵循如下规则:
* \_ 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头
* \_ 中间不能有空格,可以使用下划线(\_\_)
* 不能使用标点符号。 不能使用Bash里的关键字(可用help命令查看保留关键字)
## Bash变量案例
在Bash中变量通过"$"符来表示,以下是一个Bash脚本的案例。
```
#!/bin/bash
name="This is Shell script" # 将字符串赋值给name
echo $name # 打印变量$name
```
这脚本最终的结果就会在屏幕上打印出This is Shell script。
## 变量四种赋值方式
这四种方式包括:
* 直接赋值
* read命令赋值
* 命令赋值
* 位置参数赋值
**直接赋值**
以下是一个Bash脚本,它将字符串“hi my name is djangowang”赋值给变量name并通过echo命令打印变量中的内容。
```
#!/bin/bash
name="hi my name is djangowang"
echo $name
```
**read命令赋值**
read是Bash中的内建命令,它从键盘获取标准输出并赋值给变量。以下是将键盘输入的内容赋值给变量name,并通过echo命令打印变量中的内容。
```
#!/bin/bash
read name
echo $name
```
**命令赋值**
获取系统命令的标准输出并将标准输出内容赋值给变量command,并通过echo命令打印变量中的内容。这里注意命令赋值方式共分为两种见以下案例。
```
#!/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"。这里位置变量通过空格作为变量的分割符。
```
#!/bin/bash
command = $1
echo $command
```
意位置变量通常为数字$1-$9,10以上要用大括号扩起来如${10},${10}以下是案例。
```
#!/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命令,它用于参数的自动左移。
```
#!/bin/bash
while [ $# != 0 ]
do
echo "prama is $1,prama size is $#"
shift
done
```
## 定义变量类型
在Bash中默认为字符串类型,可以通过declare关键字指定变量的类型,还可以设置变量的属性或者删除变量。 以下介绍变量定义的七种方式:
* 字符串型
* 数值型
* 数组
* 函数
* 设置环境变量
* 只读变量
* unset变量
**字符串型**
Bash中的默认数据类型为字符串型。
```
#!/bin/bash
string="hi my name is djangowang"
echo $string
```
**数值型**
在Bash中字符串类型只能用于字符串比较,不能进行数学运算。我们通过declare -i来定义将变量改为数值型,并进行数学运算。
```
declare -i number # 定义一个数值型
```
我们来对比一下字符串型与数字型。
```
#!/bin/bash
# 字符串
n=6/3
echo "n = $n" # n = 6/3
# 数值型
declare -i n
n=6/3
echo "n = $n" # n = 2
```
**数组**
数组中可以存放多个值。Bash只支持一维数组,不支持多维数组,初始化时不需要定义数组大小,与大部分编程语言类似数组元素的下标由0开始。
```
declare -a array
```
数组案例。
```
#!/bin/bash
declare -a array
array=(A B "C" D)
echo "第一个元素为: ${array[0]}"
echo "第二个元素为: ${array[1]}"
echo "第三个元素为: ${array[2]}"
echo "第四个元素为: ${array[3]}"
```
**函数**
declare -f 函数名 ,用于显示函数内容。
```
#!/bin/bash
function a(){
echo "test1"
}
function b(){
echo "test1"
}
declare -f # 显示以上函数
declare -f a # 接函数名,显示指定的函数
```
在Shell编程实战中应用不是很多。
**设置环境变量**
declare -x指定的变量会成为环境变量,可供Shell以外的程序来使用。
```
#!/bin/bash
declare -x STRING="hello world" # 定义一个string的环境变量,建议环境变量为大写
export -p # 列出所有的Shell赋予程序的环境变量
```
**只读变量**
declare -r var1与readonly var1作用相同。当设置只读变量后,变量内容不可以修改。
```
declare -r var1 # 设置一个只读变量
#或
readonly var1
readonly -p # 用于显示只读变量的清单
```
案例
```
#!/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(仅删除变量)默认值。
```
#!/bin/bash
foo="hello world"
echo $foo # 输出hello world
unset foo # 删除foo变量
echo $foo # 为空
```
# 变量类型
在Bash中的变量是有作用域的,分别为:
* 局部变量
* 环境变量
* 内部变量
## 局部变量
局部变量在脚本或命令中定义,仅在当前Bash实例中有效,其他Bash启动的程序不能访问局部变量。局部变量的关键字为"local",以下为局部变量案例。
```
#!/bin/bash
function hello()
{
local text="Hello World!!!" # 定义局部变量
echo $text
}
text="this is test"
hello
echo $text # 可以试着去掉函数中的local,再执行本脚本的效果
```
前者输出结果为。
```
Hello World!!!
this is test
```
去掉local输出结果为。
```
Hello World!!!
Hello World!!!
```
## 环境变量
所有的程序包括Bash启动的程序都能访问环境变量,有些程序需要环境变量来保证其正常运行。在Bash中可以通过以下三个命令来查看环境变量,他们区别在于:
* set 用来显示本地变量
* env 用来显示环境变量
* export -p 用来显示和设置环境变量
*注:变量和环境变量的区别是:变量不能被子进程继承,而环境变量会被子进程继承。*
我们还可以通过以下两个文件来设置环境变量:
* /etc/profile
* $HOME/.bash_profile
譬如bash_profile文件内容,
我们可以将自己的环境变量放在PATH后。
```
# .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编程中一个很常用的命令,他用于打印信息到标准输出,譬如。
```
[root@blog.puppeter.com_centos ~]# echo "hello world" # 打印hello world到标准输出
```
目前echo有两个参数:
* \-n 不解析参数内的特殊符号
* \-e 默认值,解析参数内的特殊符号
```
[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移植性好,以下为案例。
```
#!/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移植性好,以下为案例。
```
#!/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 :指定多个变量
```
#!/bin/bash
read -p “please input your name " name
echo "your name is $name"
exit 0
```
* \-n :计数输入的字符
```
#!/bin/bash
read -n6 -p “please input your password(lengh must be over 6 numbers)" passwd
exit 0
```
* \-s :隐藏输入,不回显内容到终端
```
#!/bin/bash
read -s -n6 -p “please input your password(lengh must be over 6 numbers)" passwd
exit 0
```
* \-t :超时等待时间
```
#!/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**
```
#!/bin/bash
if [ 条件语句 ];then # 推荐书写方式
执行内容
fi
```
**方式2**
```
if [ 条件语句 ]
then
执行内容
fi
```
**方式3**
```
if (());then
执行内容
fi
```
**方式4**
```
if command ;then
执行内容
fi
```
*注:初学者一定要注意以上if..then语法中,条件语句两边都是有空格的,缺少一个空格都会报错且不容易被注意到。*
再来看一下关于if..then..fi的案例。
1.if..then的\[\]案例,比较两个值是否相等,此方式主要用于字符串匹配。
```
#!/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的(())案例,数学方式比较大小,此方式主要用于数学方式的比较。
```
#!/bin/bash
i=100
if ((10 <$i));then # 数学比较方式
echo "true"
fi
```
3.if..command,判断是否为目录,此方式主要用于调用命令来判断命令返回最终结果。
```
#!/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**
```
#!/bin/bash
if [ 条件语句 ];then # 推荐书写方式
执行内容
else
执行内容2
fi
```
**方式2**
```
if [ 条件语句 ]
then
执行内容
else
执行内容2
fi
```
再来看一下if..then..else..fi的案例。
```
#!/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**
```
if [ 条件语句 ];then
执行内容
elif [ 条件语句 ];then
执行内容
else
执行内容
fi
```
**方式2**
```
if [ 条件语句 ]
then
执行内容
elif [ 条件语句 ]
then
执行内容
else
执行内容
fi
```
if..then..elif..fi的案例。
**案例1**
```
#!/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**
通过条件语句实现的计算器。
```
#!/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**
```
while [ 条件表达式 ];do # 推荐
执行内容
done
```
**方式2**
```
while [ 条件表达式 ]
do
执行内容
done
```
**方式3**
```
while [ 条件表达式 ];do 执行内容 ;done # Bash语句大都可以写作一行,只不过可读性差
```
**方式4**
```
while command;do
执行内容
done
```
再来看一下while循环的案例。
1.打印1-100的数字。
```
#!/bin/bash
i=1
while [ $i -le "100" ];do
echo $i
i=$((i+1))
done
```
2.打印1-100间的偶数。
```
#!/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信息。
```
#!/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方式。
```
#!/bin/bash
while true ;do
echo "hello world"
done
```
## for循环
for循环的语法。
**方式1**
```
for (( ; ; ));do # 推荐
执行内容
done
```
**方式2**
```
for (( ; ;))
do
执行内容
done
```
**方式3**
```
for ((;;));do 执行内容 ;done
```
for循环的案例。
1.打印1-100的数字。
```
for ((i=1; i<=100; i ++)); do
echo $i
done
```
2.打印1-100间的奇数。
```
#!/bin/bash
for((i=1;i<=100;i++));do
tmp=$((i%2))
if [ $tmp -ne 0 ];then
echo $i
fi
done
```
3.死循环。死循环中条件表达式永远为真,如果要退出死循环可以用ctrl+c方式.
```
#!/bin/bash
for((;;));do
echo "hello world"
done
```
## for..in 循环
for..in循环的语法。
**方式一**
```
for 变量 in 条件语句;do # 推荐
执行语句
done
```
**方式二**
```
for 变量 in 条件语句
do
执行语句
done
```
**方式三**
```
for 变量 in 条件语句;do 执行语句 ;done
```
案例
1.打印1-5的数字。
```
for loop in 1 2 3 4 5 ;do
echo $loop
done
```
2.打印1-100间的数字。
```
for loop in `seq 1 100`;do
echo $loop
done
```
3.创建1-100的文件夹。
```
for loop in `seq 1 100`;do
mkdir $loop
done
```
## until循环
until 循环执行一系列命令直至条件为 true 时停止。until 循环与 while 循环在处理方式上刚好相反,一般while循环优于until循环,但在某些时候,也只是极少数情况下,until 循环更加有用。首先来看一下until循环的语法。
**方式一**
```
until command;do # 推荐
执行语句
done
```
**方式二**
```
until command
do
执行语句
done
```
## select循环
select 是个无限循环,因此要记住用break命令退出循环或用exit命令终止脚本,也可以按ctrl+c 退出循环。我们首先看select的语法。
**方式一**
```
select name [in list ];do # 推荐
执行语句
done
```
**方式二**
```
select name [in list ]
do
执行语句
done
```
案例,通常我们使用select用来做列表,案例如下。
```
#!/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,我们先来看一下它的语法。
```
case 匹配内容 in
条件1)
执行内容1
执行内容2
;;
条件2)
执行内容1
执行内容2
;;
条件3)
执行内容1
执行内容2
;;
*)
默认执行
;;
esac
```
案例1,计算器。
```
#!/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,位置参数。
```
#!/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
```
# 本章小结
# 习题