天天看点

Shell 编程入门

Shell 编程入门

Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。它是操作系统最外层的接口,

负责直接面向用户交互并提供内核服务。

一、变量

1、 定义

Shell

定义变量时,变量名不加美元符号,如:

content="hello world!"
           

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

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

2、 使用

使用一个定义过的变量,只要在变量名前面加美元符号即可,如:

content="hello world!"
echo $content
echo ${content}
           

变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界。

content="hello world!"
echo "name:${content}!!!"
           

推荐给所有变量加上花括号,这是个好的编程习惯。

已定义的变量,可以被重新定义,如:

content="hello world!"
echo $content
content="hello shell!"
echo $content
           

3、 只读变量

使用

readonly

命令可以将变量定义为只读变量,只读变量的值不能被改变。

content="hello world!"
readonly content
content="hello shell!"
           

运行脚本,结果如下:

/bin/sh: NAME: This variable is read only.
           

4、 局部变量

Shell

中默认定义的变量是全局变量,可以使用

global

进行显式声明,其作用域从被定义的地方开始,一直到脚本结束或者被删除的地方。

local

可以定义局部变量,在函数内部使用。

#!/bin/bash

name="global variable"
function func(){
    local name="local variable"
    echo $name
}

func
echo $name

# local variable
# global variable
           

5、 变量类型

shell

中会同时存在三种变量:

  • 局部变量;
  • 环境变量;
  • shell 变量。

二、字符串

字符串是最常用最有用的数据类型,字符串可以用单引号,也可以用双引号,也可以不用引号。

1、单引号

str='this is a string'
echo '$str'

# $str
           

单引号字符串的限制:

  • 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;
  • 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。

2、 双引号

name="shell"
str="Hello, I know you are \"$name\"! \n"
echo $str

# Hello, I know you are "shell"!
           

双引号的优点:

  • 双引号里可以有变量;
  • 双引号里可以出现转义字符。

3、 字符串长度

string="abcd"
echo ${#string} 

# 4
           

4、 提取子字符串

以下实例从字符串第 2 个字符开始截取 4 个字符:

string="huawei is a great compan"
echo ${string:1:4} 

# uawe
           

5、 查找子字符串

查找字符 i 或 o 的位置(哪个字母先出现就计算哪个):

string="huawei is a great compan"
echo `expr index "$string" io`  

# 6
           

注意: 以上脚本中 ` 是反引号,而不是单引号 ',不要看错了哦。

三、数组

Shell

只支持一维数组(不支持多维数组),并且没有限定数组的大小。类似于

C

语言,数组元素的下标由 0 开始编号。

1、 定义数组

shell

中,用括号来表示数组,数组元素用"空格"符号分割开。

array=("value0" "value1" "value2" "value3")
           

还可以单独定义数组的各个元素:

array[0]="value0"
array[1]="value1"
array[n]="valuen"
           

2、 读取数组

读取数组元素值的一般格式是:

value=${array_name[n]}
           

@

符号可以获取数组中的所有元素,例如:

echo ${array_name[@]}

# value0 value1 value2 value3
           

3、 获取长度

获取数组长度的方法与获取字符串长度的方法相同,例如:

# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}
           

注意:数组不可以进行切割,错误用法

${array[1:2]}

四、流程控制

1、 if 判断

语法示例判断两个变量是否相等。

#!/bin/bash

a=10
b=20
if [ $a == $b ]; then
    echo "a 等于 b"
elif [ $a -gt $b ]; then
    echo "a 大于 b"
elif [ $a -lt $b ]; then
    echo "a 小于 b"
else
    echo "没有符合的条件"
fi

# a 小于 b
           

不能使用

if [ $a > $b ]

,正确的方式是

if (( $a > $b ))

2、 for 循环

for

循环即执行一次所有命令,空格进行元素分割,使用变量名获取列表中的当前取值。

示例,顺序输出当前列表中的数字:

#!/bin/bash

for loop in 1 2 3; do
    echo "The value is: $loop"
done

#The value is: 1
#The value is: 2
#The value is: 3
           

循环字符串内容:

#!/bin/bash

for str in This is a string; do
    echo $str
done

# This
# is
# a
# string
           

循环数组中元素:

#!/bin/bash

array=("value0" "value1" "value2" "value3")
# array[*]与array[@]两者皆可
for loop in ${array[*]}; do    
    echo ${loop}
done

# value0
# value1
# value2
# value3
           

3、 while 循环

while

循环用于不断执行一系列命令,也用于从输入文件中读取数据。

以下是一个基本的

while

循环,测试条件是:如果 int 小于等于 5,那么条件返回真。int 从 1 开始,每次循环处理时,int 加 1。运行上述脚本,返回数字 1 到 5,然后终止。

int=1
while [ $int -le 5 ]; do
    echo $int
    let "int++"
done
           

无限循环

# 方式一
while :
do
    command
done

# 方式二
while true
do
    command
done
           

4、 break 终止

在循环语句中,可以使用

break

命令,允许跳出所有循环(终止执行后面的所有循环)。

#!/bin/bash

while :; do
    echo -n "输入 1 到 5 之间的数字:"
    read aNum
    case $aNum in
    1 | 2 | 3 | 4 | 5)
        echo "你输入的数字为 $aNum!"
        ;;
    *)
        echo "你输入的数字不是 1 到 5 之间的! 游戏结束"
        break
        ;;
    esac
done
           

5、 continue 继续

continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。

#!/bin/bash

while :; do
    echo -n "输入 1 到 5 之间的数字: "
    read aNum
    case $aNum in
    1 | 2 | 3 | 4 | 5)
        echo "你输入的数字为 $aNum!"
        ;;
    *)
        echo "你输入的数字不是 1 到 5 之间的!"
        continue
        echo "游戏结束"
        ;;
    esac
done
           

运行代码发现,当输入大于5的数字时,该例中的循环不会结束,语句 echo "游戏结束" 永远不会被执行。

五、函数

1、 函数定义

Shell

中可以用户定义函数,然后在

shell

脚本中可以随便调用。

下面的例子定义了一个函数并进行调用:

#!/bin/bash

function demo(){
     echo "这是我的第一个 shell 函数!"
}
echo "-----函数开始执行-----"
demo
echo "-----函数执行完毕-----"
           

可以带

function fun()

定义,也可以直接

fun()

定义,不带任何参数。

参数返回,可以显示加:

return

返回,如果不加,将以最后一条命令运行结果,作为返回值。

return

后跟数值n(0-255)。

函数脚本执行结果:

-----函数开始执行-----
这是我的第一个 shell 函数!
-----函数执行完毕-----
           

2、 函数参数

shell

中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1 表示第一个参数,$2 表示第二个参数...

带参数的函数示例:

#!/bin/bash

function funWithParam(){
     echo "第一个参数为 $1 !"
     echo "第十个参数为 $10 !"
     echo "第十个参数为 ${10} !"
     echo "第十一个参数为 ${11} !"
     echo "参数总数有 $# 个!"
     echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 11 22 3 4 5 6 7 8 9 34 73
           

输出结果:

第一个参数为 11 !
第十个参数为 110 !
第十个参数为 34 !
第十一个参数为 73 !
参数总数有 11 个!
作为一个字符串输出所有参数 11 22 3 4 5 6 7 8 9 34 73 !
           

参数获取时

$n

${n}

还是有区别的,特别是第二行的打印。

$10

不能获取第十个参数,获取第十个参数需要

${10}

。当n>=10时,需要使用

${n}

来获取参数。

另外,还有几个特殊字符用来处理参数:

$#	传递到脚本或函数的参数个数
$*	以一个单字符串显示所有向脚本传递的参数
$$	脚本运行的当前进程ID号
$!	后台运行的最后一个进程的ID号
$@	与$*相同,但是使用时加引号,并在引号中返回每个参数。
$-	显示Shell使用的当前选项,与set命令功能相同。
$?	显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
           

6、运算符

1、算术运算符

下表列出了常用的算术运算符。

+	加法	
-	减法	
*	乘法	
/	除法
%	取余	
=	赋值	
==	相等
!=	不相等
           

注意:条件表达式要放在方括号之间,并且要有空格,例如:

[$a==$b]

是错误的,必须写成

[ $a == $b ]

使用示例如下:

#!/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
           

还可以使用下面的运算符替换,结果都一致:

#!/bin/bash

a=10
b=20

val=$(expr $a + $b)
echo "a + b : $val"

var=$(($a + $b))
echo "a + b : $val"

var=$[$a + $b]
echo "a + b : $val"
           

注意:

  • 乘号(*)前边必须加反斜杠()才能实现乘法运算;
  • $((表达式)) 此处表达式中的 "*" 不需要转义符号 ""。

2、关系运算符

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

下表列出了常用的关系运算符。

-eq	检测两个数是否相等,相等返回 true。
-ne	检测两个数是否不相等,不相等返回 true。	
-gt	检测左边的数是否大于右边的,如果是,则返回 true。	
-lt	检测左边的数是否小于右边的,如果是,则返回 true。	
-ge	检测左边的数是否大于等于右边的,如果是,则返回 true。	
-le	检测左边的数是否小于等于右边的,如果是,则返回 true。
           
#!/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
if [ $a -gt $b ]; then
    echo "$a -gt $b: a 大于 b"
else
    echo "$a -gt $b: a 不大于 b"
fi
           

运算符可以使用"=="、"! ="、">"替换:

#!/bin/bash

a=10
b=20

if [ $a == $b ]; then
    echo "$a -eq $b : a 等于 b"
else
    echo "$a -eq $b: a 不等于 b"
fi
if (($a > $b)); then
    echo "$a -gt $b: a 大于 b"
else
    echo "$a -gt $b: a 不大于 b"
fi
           

注意:">"、"> =" 、"<" 、"< =" 不能使用"[]"。

3、逻辑运算符

常用的逻辑运算符。

&&	逻辑的 AND	[[ $a -lt 100 && $b -gt 100 ]] 返回 false
||	逻辑的 OR	[[ $a -lt 100 || $b -gt 100 ]] 返回 true
           
#!/bin/bash

a=10
b=20

if [[ $a -lt 100 && $b -gt 100 ]]; then
    echo "返回 true"
else
    echo "返回 false"
fi

if [[ $a -lt 100 || $b -gt 100 ]]; then
    echo "返回 true"
else
    echo "返回 false"
fi
           

执行脚本,输出结果如下所示:

返回 false
返回 true
           

4、字符串运算符

下表列出了常用的字符串运算符。

=	检测两个字符串是否相等,相等返回 true。	
!=	检测两个字符串是否不相等,不相等返回 true。	
-z	检测字符串长度是否为0,为0返回 true。	
-n	检测字符串长度是否不为 0,不为 0 返回 true。
$	检测字符串是否为空,不为空返回 true。	
           

字符串运算符实例如下:

#!/bin/bash

if [ -z $a ]
then
   echo "-z $a : 字符串长度为 0"
else
   echo "-z $a : 字符串长度不为 0"
fi
if [ -n "$a" ]
then
   echo "-n $a : 字符串长度不为 0"
else
   echo "-n $a : 字符串长度为 0"
fi
if [ $a ]
then
   echo "$a : 字符串不为空"
else
   echo "$a : 字符串为空"
fi
           

5、文件测试运算符

文件测试运算符用于检测 Unix 文件的各种属性。

-b file	检测文件是否是块设备文件。	
-c file	检测文件是否是字符设备文件。	
-d file	检测文件是否是目录。
-f file	检测文件是否是普通文件。
-g file	检测文件是否设置了 SGID 位。
-k file	检测文件是否设置了粘着位(Sticky Bit)。
-p file	检测文件是否是有名管道。
-u file	检测文件是否设置了 SUID 位。
-r file	检测文件是否可读。
-w file	检测文件是否可写。
-x file	检测文件是否可执行。
-s file	检测文件是否为空。
-e file	检测文件。
           

七、输入/输出重定向

1、 输出重定向

将命令的完整的输出重定向在用户文件中。

# 覆盖
$ echo "hello world" >./test.file

# 追加
$ echo "hello world" >>./test.file
           

2、 输入重定向

从用户文件中的内容输出到命令行。

$ wc -l  < ./test.file
1
           

可以与 while 语句结合,遍历文件内容,按行打印:

while read line; do
    echo $line
done < ./test.file
           

3、 标准输入输出

一般情况下,每个

Unix/Linux

命令运行时都会打开三个文件:

  • 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
  • 标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
  • 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。

默认情况下,command > file 将 stdout 重定向到 file,command < file 将stdin 重定向到 file。

如果希望 stderr 重定向到 file,可以这样写:

$ command 2>file
           

如果希望 stderr 追加到 file 文件末尾,可以这样写:

$ command 2>>file
           

2 表示标准错误文件(stderr)。

如果希望将 stdout 和 stderr 合并后重定向到 file,可以这样写:

$ command > file 2>&1

或者

$ command >> file 2>&1
           

如果希望对 stdin 和 stdout 都重定向,可以这样写:

$ command < file1 >file2
           

command 命令将 stdin 重定向到 file1,将 stdout 重定向到 file2。

八、eval 函数

当我们在命令行前加上

eval

时,

shell

就会在执行命令之前扫描它两次。

eval

命令将首先会先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量。该命令对变量进行两次扫描。

常见的使用场景如下:

1、普通情况

$ var=100
$ echo $var
100
$ eval echo $var
           

这样和普通的没有加

eval

关键字的命令的作用一样。

2、字符串转换命令

$ cat file
helle shell
it is a test of eval

$ tfile="cat file"
$ eval $tfile
helle shell
it is a test of eval
           

从上面可以看出 eval 经历了两次扫描,第一次扫描替换了变量为字符串,第二次扫描执行了字符串内容。

3、获取参数

$ cat t.sh
#!/bin/bash

eval echo \$$#

$ ./t.sh a b c
c
$ ./t.sh 1 2 3
3
           

通过转义符 “|” 与 $# 结合,可以动态的获取最后一个参数。

4、 修改指针

$ var=100
$ ptr=var
$ eval echo \$$ptr
100
$ eval $ptr=50
$ echo $val
50
           

推荐阅读:

《Linux命令行与shell脚本编程大全》

《谷歌shell编码规范》