Shell 编程入门

时间:2024-02-19 18:28:18

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编码规范》