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