什么是 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 中所有特殊字符的解释,这是一种比使用"更强烈的形式。后面的实验会详细说明。

bash脚本 echo bash脚本语言_bash脚本 echo

反引号(`)

命令替换
反引号中的命令会优先执行,如:

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 是第三个参数,然后是第四个。bash脚本 echo bash脚本语言_vim_02{10},bash脚本 echo bash脚本语言_vim_03{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!

基本运算符

算数运算符

bash脚本 echo bash脚本语言_bash脚本 echo_04

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 写成 bash脚本 echo bash脚本语言_bash脚本 echo_05b 不行
条件表达式要放在方括号之间,并且要有空格 [ $a == bash脚本 echo bash脚本语言_bash脚本 echo_06a==$b] 不行
乘号(*)前边必须加反斜杠()才能实现乘法运算

关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

bash脚本 echo bash脚本语言_bc_07

#!/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
逻辑运算符

bash脚本 echo bash脚本语言_bash_08

#!/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
字符串运算符

bash脚本 echo bash脚本语言_bash_09

#!/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
文件测试运算符

bash脚本 echo bash脚本语言_bash_10


实例:

#!/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!” 永远不会被执行。