[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 # 限制指定函数 ``` **设置环境变量** 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变量: * Bash脚本中定义的变量是global的,其作用域从被定义的地方开始,到Bash结束或被显示删除的地方为止。 * Bash函数定义的变量默认是global的,其作用域从“函数被调用时执行变量定义的地方”开始,到Bash结束或被显示删,函数定义的变量可以被显示定义成local的,其作用域局限于函数内。但请注意,函数的参数是local的。 * 如果同名,Bash函数定义的local变量会屏蔽脚本定义的global变量。 局部变量的关键字为"local",以下为局部变量案例。 ``` #!/bin/bash function hello() { local text="Hello World!!!" # 定义局部变量 echo $text } hello echo $text # 可以试着去掉函数中的local,再执行本脚本的效果 ``` ## 环境变量 所有的程序包括Bash启动的程序都能访问环境变量,有些程序需要环境变量来保证其正常运行。在Bash中可以通过以下三个命令来查看环境变量,他们区别在于: * set 用来显示本地变量 * env 用来显示环境变量 * export -p 用来显示和设置环境变量 *注:变量和环境变量的区别是:变量不能被子进程继承,而环境变量会被子进程继承。* 我们还可以通过以下两个文件来设置环境变量: * /etc/profile * ~./.bash\_profile ## 内部变量 内部变量是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 ] # a 大于 b 见表1 then echo "a is greater than b" elif [ $a -lt $b ] # a 小于 b 见表1 then echo "a is less than b" else echo "None of the condition met" fi ``` 文件比较符。 | \[ \]括号 | (())扩容 | 含义 | | :--- | :--- | :--- | | -eq | == | 等于 | | -ne | != | 不等于 | | -gt | &gt; | 大于 | | -ge | &gt;= | 大于等于 | | -lt | &lt; | 小于 | | -le | &lt;= | 小于等于 | **案例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循环 * foreach循环 * 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 ``` # 本章小结 # 习题