什么是 Bash
简介
Bash(GNU Bourne-Again Shell)是一个为 GNU 计划编写的 Unix shell,它是许多 Linux 平台默认使用的 shell。
shell 是一个命令解释器,是介于操作系统内核与用户之间的一个绝缘层。准确地说,它也是能力很强的计算机语言,被称为解释性语言或脚本语言。它可以通过将系统调用、公共程序、工具和编译过的二进制程序”粘合“在一起来建立应用,这是大多数脚本语言的共同特征,所以有时候脚本语言又叫做“胶水语言”。
事实上,所有的 UNIX 命令和工具再加上公共程序,对于 shell 脚本来说,都是可调用的。Shell 脚本对于管理系统任务和其它的重复工作的例程来说,表现的非常好,根本不需要那些华而不实的成熟紧凑的编译型程序语言。
运行 Bash 脚本的方式:
#!/bin/bash
#这一行是表示使用 /bin/bash 作为脚本的解释器
#这行要放在脚本的行首并且不要省略。
# 使用shell来执行
sh hello.sh
# 使用bash来执行
bash hello.sh
# 使用.来执行
. ./hello.sh
# 使用source来执行
source hello.sh
# 还可以赋予脚本所有者执行权限,允许该用户执行该脚本
chmod u+rx hello.sh
./hello.sh
bash特殊字符(上)
注释(#)
行首以 # 开头(除#!之外)的是注释。#! 是用于指定当前脚本的解释器,我们这里为 bash,且应该指明完整路径,所以为 /bin/bash
分号(;)
使用分号 ; 可以在同一行上写两个或两个以上的命令。
点号(.)
等价于 source 命令
bash 中的 source 命令用于在当前 bash 环境下读取并执行 FileName.sh 中的命令。
source test.sh
. test.sh
引号
双引号(")
“STRING” 将会阻止(解释)STRING 中大部分特殊的字符。后面的实验会详细说明。
单引号(’)
‘STRING’ 将会阻止 STRING 中所有特殊字符的解释,这是一种比使用"更强烈的形式。后面的实验会详细说明。
反引号(`)
命令替换
反引号中的命令会优先执行,如:
cp `mkdir back` test.sh back
ls
先创建了 back 目录,然后复制 test.sh 到 back 目录。
冒号(:)
空命令
等价于“NOP”(no op,一个什么也不干的命令)。也可以被认为与 shell 的内建命令 true 作用相同。“:”命令是一个 bash 的内建命令,它的退出码(exit status)是(0)。
#!/bin/bash
while :
do
echo "endless loop"
done
等价于
#!/bin/bash
while true
do
echo "endless loop"
done
可以在 if/then 中作占位符:
#!/bin/bash
condition=5
if [ $condition -gt 0 ]
#gt表示greater than,也就是大于,同样有-lt(小于),-eq(等于)
then : # 什么都不做,退出分支
else
echo "$condition"
fi
变量扩展/子串替换
在与 > 重定向操作符结合使用时,将会把一个文件清空,但是并不会修改这个文件的权限。如果之前这个文件并不存在,那么就创建这个文件。
: > test.sh # 文件“test.sh”现在被清空了
# 与 cat /dev/null > test.sh 的作用相同
# 然而,这并不会产生一个新的进程, 因为“:”是一个内建命令
在与 >> 重定向操作符结合使用时,将不会对预先存在的目标文件 : >> target_file 产生任何影响。如果这个文件之前并不存在,那么就创建它。
问号(?)
测试操作符
在一个双括号结构中,? 就是 C 语言的三元操作符,如:
vim test.sh
输入如下代码,并保存:
#!/bin/bash
a=10
(( t=a<50?8:9 ))
echo $t
运行测试
bash test.sh
8
美元符号($)
引用变量
bash特殊字符(下)
小括号(( ))
在括号中的变量,由于是在子 shell 中,所以对于脚本剩下的部分是不可用的。父进程,也就是脚本本身,将不能够读取在子进程中创建的变量,也就是在子 shell 中创建的变量。如:
vim test20.sh
输入代码:
#!/bin/bash
a=123
( a=321; )
echo "$a" #a的值为123而不是321,因为括号将判断为局部变量
运行代码:
bash test20.sh
a = 123
在圆括号中 a 变量,更像是一个局部变量。
2.初始化数组
创建数组
vim test21.sh
输入代码:
#!/bin/bash
arr=(1 4 5 7 9 21)
echo ${arr[3]} # get a value of arr
运行代码:
bash test21.sh
7
大括号({ })
文件名扩展
复制 t.txt 的内容到 t.back 中
vim test22.sh
输入代码:
#!/bin/bash
if [ ! -w 't.txt' ];
then
touch t.txt
fi
echo 'test text' >> t.txt
cp t.{txt,back}
运行代码:
bash test22.sh
查看运行结果:
ls
cat t.txt
cat t.back
注意: 在大括号中,不允许有空白,除非这个空白被引用或转义。
代码块
代码块,又被称为内部组,这个结构事实上创建了一个匿名函数(一个没有名字的函数)。然而,与“标准”函数不同的是,在其中声明的变量,对于脚本其他部分的代码来说还是可见的。
vim test23.sh
输入代码:
#!/bin/bash
a=123
{ a=321; }
echo "a = $a"
运行代码:
bash test23.sh
a = 321
变量 a 的值被更改了。
中括号([ ])
条件测试
条件测试表达式放在 [ ] 中。下列练习中的 -lt (less than)表示小于号。
vim test24.sh
输入代码:
#!/bin/bash
a=5
if [ $a -lt 10 ]
then
echo "a: $a"
else
echo 'a>=10'
fi
运行代码:
bash test24.sh
a: 5
数组元素
在一个 array 结构的上下文中,中括号用来引用数组中每个元素的编号。
vim test25.sh
输入代码:
#!/bin/bash
arr=(12 22 32)
arr[0]=10
echo ${arr[0]}
运行代码:
bash test25.sh
10
尖括号(< 和 >)
重定向
test.sh > filename:重定向 test.sh 的输出到文件 filename 中。如果 filename 存在的话,那么将会被覆盖。
test.sh &> filename:重定向 test.sh 的 stdout(标准输出)和 stderr(标准错误)到 filename 中。
test.sh >&2:重定向 test.sh 的 stdout 到 stderr 中。
test.sh >> filename:把 test.sh 的输出追加到文件 filename 中。如果 filename 不存在的话,将会被创建。
竖线(|)
管道
分析前边命令的输出,并将输出作为后边命令的输入。这是一种产生命令链的好方法。
破折号(-)
波浪号(~)
表示 home 目录。
特殊变量
位置参数
从命令行传递到脚本的参数:$0,$1,$2,$3…
$0 就是脚本文件自身的名字,$1 是第一个参数,$2 是第二个参数,$3 是第三个参数,然后是第四个。{10},{12}。
$# : 传递到脚本的参数个数
$* : 以一个单字符串显示所有向脚本传递的参数。与位置变量不同,此选项参数可超过 9 个
$$ : 脚本运行的当前进程 ID 号
$! : 后台运行的最后一个进程的进程 ID 号
$@ : 与 $* 相同,但是使用时加引号,并在引号中返回每个参数
$: 显示 shell 使用的当前选项,与 set 命令功能相同
$? : 显示最后命令的退出状态。 0 表示没有错误,其他任何值表明有错误。
位置参数实例
vim test30.sh
#!/bin/bash
# 作为用例, 调用这个脚本至少需要10个参数, 比如:
# bash test.sh 1 2 3 4 5 6 7 8 9 10
MINPARAMS=10
echo
echo "The name of this script is \"$0\"."
echo "The name of this script is \"`basename $0`\"."
echo
if [ -n "$1" ] # 测试变量被引用.
then
echo "Parameter #1 is $1" # 需要引用才能够转义"#"
fi
if [ -n "$2" ]
then
echo "Parameter #2 is $2"
fi
if [ -n "${10}" ] # 大于$9的参数必须用{}括起来.
then
echo "Parameter #10 is ${10}"
fi
echo "-----------------------------------"
echo "All the command-line parameters are: "$*""
if [ $# -lt "$MINPARAMS" ]
then
echo
echo "This script needs at least $MINPARAMS command-line arguments!"
fi
echo
exit 0
运行代码:
bash test30.sh 1 2 10
The name of this script is "test.sh".
The name of this script is "test.sh".
Parameter #1 is 1
Parameter #2 is 2
-----------------------------------
All the command-line parameters are: 1 2 10
This script needs at least 10 command-line arguments!
基本运算符
算数运算符
vim test.sh
#!/bin/bash
a=10
b=20
val=`expr $a + $b`
echo "a + b : $val"
val=`expr $a - $b`
echo "a - b : $val"
val=`expr $a \* $b`
echo "a * b : $val"
val=`expr $b / $a`
echo "b / a : $val"
val=`expr $b % $a`
echo "b % a : $val"
if [ $a == $b ]
then
echo "a == b"
fi
if [ $a != $b ]
then
echo "a != b"
fi
运行
bash test.sh
a + b : 30
a - b : -10
a * b : 200
b / a : 2
b % a : 0
a != b
原生 bash 不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。
expr 是一款表达式计算工具,使用它能完成表达式的求值操作。
注意使用的反引号(esc 键下边)
表达式和运算符之间要有空格 $a + $b 写成 b 不行
条件表达式要放在方括号之间,并且要有空格 [ $a == a==$b] 不行
乘号(*)前边必须加反斜杠()才能实现乘法运算
关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
#!/bin/bash
a=10
b=20
if [ $a -eq $b ]
then
echo "$a -eq $b : a == b"
else
echo "$a -eq $b: a != b"
fi
逻辑运算符
#!/bin/bash
a=10
b=20
if [[ $a -lt 100 && $b -gt 100 ]]
then
echo "return true"
else
echo "return false"
fi
if [[ $a -lt 100 || $b -gt 100 ]]
then
echo "return true"
else
echo "return false"
fi
字符串运算符
#!/bin/bash
a="abc"
b="efg"
if [ $a = $b ]
then
echo "$a = $b : a == b"
else
echo "$a = $b: a != b"
fi
if [ -n $a ]
then
echo "-n $a : The string length is not 0"
else
echo "-n $a : The string length is 0"
fi
if [ $a ]
then
echo "$a : The string is not empty"
else
echo "$a : The string is empty"
fi
结果
abc = efg: a != b
-n abc : The string length is not 0
abc : The string is not empty
文件测试运算符
实例:
#!/bin/bash
file="/home/shiyanlou/test.sh"
if [ -r $file ]
then
echo "The file is readable"
else
echo "The file is not readable"
fi
if [ -e $file ]
then
echo "File exists"
else
echo "File not exists"
fi
结果
The file is readable
File exists
思考
浮点运算,比如实现求圆的面积和周长。
expr 只能用于整数计算,可以使用 bc 或者 awk 进行浮点数运算。
#!/bin/bash
radius=2.4
pi=3.14159
girth=$(echo "scale=4; 3.14 * 2 * $radius" | bc)
area=$(echo "scale=4; 3.14 * $radius * $radius" | bc)
echo "girth=$girth"
echo "area=$area"
以上代码如果想在环境中运行,需要先安装 bc。
sudo apt-get update
sudo apt-get install bc
流程控制
if else
if
if 语句语法格式:
if condition
then
command1
command2
...
commandN
fi
if else
if else 语法格式:
if condition
then
command1
command2
...
commandN
else
command
fi
if-elif-else 语法格式:
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi
for 循环
for 循环一般格式为:
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
while 语句
while 循环用于不断执行一系列命令,也用于从输入文件中读取数据;命令通常为测试条件。其格式为:
while condition
do
command
done
无限循环
无限循环语法格式:
while :
do
command
done
或者
while true
do
command
done
或者
for (( ; ; ))
until 循环
until 循环执行一系列命令直至条件为真时停止。 until 循环与 while 循环在处理方式上刚好相反。 一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用。 until 语法格式:
until condition
do
command
done
case
Shell case 语句为多选择语句。可以用 case 语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。case 语句格式如下:
case 值 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac
取值后面必须为单词 in,每一模式必须以右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;。
取值将检测匹配的每一个模式。一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。如果无一匹配模式,使用星号 * 捕获该值,再执行后面的命令。
下面的脚本提示输入 1 到 4,与每一种模式进行匹配:
echo 'Enter a number between 1 and 4:'
echo 'The number you entered is:'
read aNum
case $aNum in
1) echo 'You have chosen 1'
;;
2) echo 'You have chosen 2'
;;
3) echo 'You have chosen 3'
;;
4) echo 'You have chosen 4'
;;
*) echo 'You did not enter a number between 1 and 4'
;;
esac
输入不同的内容,会有不同的结果,例如:
Enter a number between 1 and 4:
The number you entered is:
3
You have chosen 3
跳出循环
在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell 使用两个命令来实现该功能:break 和 continue。
break 命令
break 命令允许跳出所有循环(终止执行后面的所有循环)。
下面的例子中,脚本进入死循环直至用户输入数字大于 5。要跳出这个循环,返回到 shell 提示符下,需要使用 break 命令。
#!/bin/bash
while :
do
echo -n "Enter a number between 1 and 5:"
read aNum
case $aNum in
1|2|3|4|5) echo "The number you entered is $aNum!"
;;
*) echo "The number you entered is not between 1 and 5! game over!"
break
;;
esac
done
执行以上代码,输出结果为:
Enter a number between 1 and 5:3
The number you entered is 3!
Enter a number between 1 and 5:7
The number you entered is not between 1 and 5! game over!
continue
continue 命令与 break 命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。 对上面的例子进行修改:
#!/bin/bash
while :
do
echo -n "Enter a number between 1 and 5: "
read aNum
case $aNum in
1|2|3|4|5) echo "The number you entered is $aNum!"
;;
*) echo "The number you entered is not between 1 and 5!"
continue
echo "game over"
;;
esac
done
运行代码发现,当输入大于 5 的数字时,该例中的循环不会结束,语句 echo “Game is over!” 永远不会被执行。