最近项目需要用到Shell脚本,简单粗暴地学习了一下。想要系统的学习可以看这本书《Shell脚本学习指南》(链接: https://pan.baidu.com/s/1o770yRw 密码: qjyr),此外可以深入了解一下UNIX与LINUX。
一个既简单的Shell脚本
终端上操作就可
e.g.显示系统下登录的用户
$ who
_mbsetupuser console Jun 4 18:59
XXX console Jun 4 18:59
XXX ttys001 Jun 21 12:22
wc字数计算,它可以算出行数(line)、字数(word)、字符数(character)。| (管道符号)可以在两个程序间建立管道,下面例子,who的输出,成了wc的输入。
e.g.计算用户个数
$ who | wc -l
3
cat建立文件
$ cat > test.sh
who | wc -l #程序内容
^D #Ctrl+D表示结束文件(这里的D不可以用shift+d来完成,也可能是电脑系统的问题)
sh test.sh #执行程序
Linux的Shell种类
1.Bourne Shell(/usr/bin/sh或/bin/sh)
sh(全称Bourne Shell),是UNIX最初使用的shell,而且在每种UNIX上都可以使用。Bourne Shell在shell编程方便相当优秀,但在处理与用户的交互方便作得不如其他几种shell。
2.Bourne Again Shell(/bin/bash)
bash(全称Bourne Again Shell),LinuxOS默认的,它是Bourne Shell的扩展。与Bourne Shell完全兼容,并且在Bourne Shell的基础上增加了很多特性。可以提供命令补全,命令编辑和命令历史等功能。它还包含了很多C Shell和Korn Shell中的优点,有灵活和强大的编辑接口,同时又很友好的用户界面。
3.C Shell(/usr/bin/csh)
csh(全称C Shell),是一种比Bourne Shell更适合的变种Shell,它的语法与C语言很相似。Tcsh是Linux提供的C Shell的一个扩展版本。
Tcsh包括命令行编辑,可编程单词补全,拼写校正,历史命令替换,作业控制和类似C语言的语法,他不仅和Bash Shell提示符兼容,而且还提供比Bash Shell更多的提示符参数。
4.K Shell(/usr/bin/ksh)
ksh(全称Korn Shell),集合了C Shell和Bourne Shell的优点并且和Bourne Shell完全兼容。
pdksh,是Linux系统提供的ksh的扩展。pdksh支持人物控制,可以在命令行上挂起,后台执行,唤醒或终止程序。
5.Shell for Root(/sbin/sh)
……
注:在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为 #!/bin/bash。
! 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序。e.g. #!/bin/bash
运行 Shell 脚本有两种方法
1、批处理(Batch):用户事先写一个Shell脚本(Script),作为可执行程序,让Shell一次把这些命令执行完,而不必一条一条地敲命令。
e.g..将文件保存为XX.sh, 并cd到相应的目录
chmod +x ./XX.sh #使脚本具有执行权限
./XX.sh #执行脚本 ,也可以直接用 sh xx.sh
注意,一定要写成 ./XX.sh,而不是 XX.sh,运行其它二进制的程序也一样,直接写 XX.sh,linux 系统会去 PATH 里寻找有没有叫 XX.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 XX.sh 是会找不到命令的,要用 ./XX.sh 告诉系统说,就在当前目录找。
2、交互式(Interactive):解释执行用户的命令,用户输入一条命令,Shell就解释执行一条。
Shell识别三种基本命令
内建命令、Shell函数以及外部命令
1.内建命令就是由Shell本身所执行的命令。
1).有些命令是由于其必要性才内建的,例如cd用来改变目录、read会将来自用户(或文件)的输入数据传给Shell变量;
2).另一种内建命令存在则是为了效率,其中最典型的就是test命令,编写脚本时经常用到它;
3).还有I/O命令,如echo和printf;
2.Shell函数是功能健全的一些程序代码,以Shell语言写成,他们可以像命令那样引用。
3.外部命令就是由Shell的副本(新的进程)所执行的命令,基本过程如下:
1).建立一个新的进程,此进程即为Shell的一个副本;
2).在新的进程里,在PATH变量内所列出的目录中,寻找特定的命令。/bin:/usr/bin:/usr/X11R6/bin:/usr/local/bin为PATH变量典型的默认值。当命令名称含有斜杠(/)符号时,将略过路径查找步骤;
3).在新的进程里,以所找到的新程序取代执行中的Shell程序并执行;
4).程序完成后,最初的Shell会接着从终端读取下一条命令,或执行脚本里的下一条命令
echo
echo 命令用于向窗口输出文本,属于I/O命令。(在不同版本上存在着差异,自行体会) e.g. echo "Hello World !"(双引号可以省略)(这里知道echo是输出文本就可,不理解的继续看就行)
1.-e 开启转义
e.g.
echo -e "OK! \n" # 换行
echo -e "OK! \c" # -e 开启转义 \c 不换行
2.echo "It is a test" > myfile #显示结果定向至文件
3.echo '"$name"' #原样输出字符串,不进行转义或取变量(用单引号)
4.echo `date` #显示命令执行结果(反引号)
printf
printf 命令模仿 C 程序库(library)里的 printf() 程序,属于I/O命令。标准所定义,因此使用printf的脚本比使用echo移植性好,太复杂的输出还是用printf比较好
printf 使用引用文本或空格分隔的参数,外面可以在printf中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。默认printf不会像 echo 自动添加换行符,我们可以手动添加 \n。
e.g. printf "Hello, Shell\n"
printf 命令的语法:
printf format-string [arguments...]
参数说明:
format-string: 为格式控制字符串
arguments: 为参数列表。
来个小例子
test.sh文件
#!/bin/sh
echo -----------------------------
printf "%-10s %-10s %-10s\n" 姓名 性别 年龄
printf "%-10s %-10s %-10.0f\n" 小兰 女 18
printf "%-10s %-10s %-10.0f\n" 小明 男 19
#‘-’表示左对齐,默认右对齐
#%s, %c, %d, %f 格式替代符,%-10s:左对齐10个字符,不够填充空格,多了会全部显示出来
echo -----------------------------
# format-string为双引号或单引号,效果一样
printf "%d %s\n" 100 "abc"
printf '%d %s\n' 100 "abc"
# 没有引号也可以输出
printf %s abc
printf "\n"
# format-string 可以被重用
printf %s abc def
printf "\n"
# 如果没有 arguments,那么 %s 用NULL代替,%d 用 0 代替
printf "%s and %d \n"
#同\c,arguments的\n不起作用
printf "<%s>\n" "a\nb"
echo -----------------------------
输出
-----------------------------
姓名 性别 年龄
小兰 女 18
小明 男 19
-----------------------------
100 abc
100 abc
abc
abcdef
and 0
-----------------------------
Shell echo/printf 的转义序列
序列 说明
\a 警示字符,通常是ASCII的BEL字符
\b 退格(Backspace)
\c 输出中忽略最后的换行符号(Newline). 这个参数之后的任何字符,包括接下来的参数,都会被忽略掉(不打印)
\f 清除屏幕(Formfeed)
\n 换行(Newline)
\r 回车(Carriage return)
\t 水平制表符(Horizontal tab)
\v 垂直制表符(Vertical tab)
\\ 反斜杠字符
\ddd 表示1到3位数八进制值的字符。仅在格式字符串中有效
\0ddd 将字符表示成1-3位的八进制数值
Shell变量
username="xiaohong" #字符串可以不加引号,但若中间存在空格,一定要加引号。等号左右都没有空格
变量名规则:
1).首字符必须为字母(a-z,A-Z)
2).中间不能有空格,可以有下划线(_)
3).不能使用标点符号
4).不能使用bash里的关键字(可用help命令查看保留关键字)。
只读变量:
userSex="female"
readonly userSex #不可再被重新定义
变量类型:
1).局部变量
局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
2).环境变量
所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
3). shell变量
shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行
除了显式地直接赋值,还可以用语句给变量赋值, e.g. for file in `ls /etc` 将 /etc 下目录的文件名循环出来。
1.使用变量:(在变量名前加美元符号($)即可)
echo $username
echo ${username} #花括号是为了帮助解释器识别变量的边界,不加也行。
已定义的变量可以被重新定义,e.g. username="xiaolan"
2.删除变量(变量删除后不能再次使用,unset不可删除只读变量)
unset username
变量替换
tesh.sh
#!/bin/bash
#---argument为空,返回AAAAA,argument依然为空---
echo ${argument:-"AAAAA"}
echo "1 - 变量值为: ${argument}"
#---argument为空,返回AAAAA,argument依然为AAAAA---
echo ${argument:="AAAAA"}
echo "2 - 变量值为: ${argument}"
#删除argument
unset argument
#---argument为空,返回空,argument依然为空---
echo ${argument:+"BBBBB"}
echo "3 - 变量值为: $argument"
argument="CCCCC"
#---argument为CCCCC,返回"BBBBB,argument依然为空CCCCC
echo ${argument:+"BBBBB"}
echo "4 - 变量值为: $argument"
#---argument为CCCCC,返回"CCCCC,argument依然为空CCCCC
echo ${argument:?"666~~~"}
echo "5 - 变量值为: ${argument}"
输出
AAAAA
1 - 变量值为:
AAAAA
2 - 变量值为: AAAAA
3 - 变量值为:
BBBBB
4 - 变量值为: CCCCC
CCCCC
5 - 变量值为: CCCCC
shell字符串
shell字符串可以用单引号,也可以用双引号。
1).单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的,而双引号里可以有变量。
2).单引号字串中不能出现单引号(对单引号使用转义符后也不行),而双引号里可以出现转义字符
e.g. echo "haha${username}"
echo 'haha${username}'
1).字符串拼接
greetings="hello, "$username" !"
greetings1="hello, ${username} !"
greetings2="hello, $username !" #(最好不要这样写)
2).获取字符串长度
echo ${#username}
3).提取子字符串
string="hello"
echo ${string:0:3} #从第一个开始,截取3个 ,即hel
4).查找子字符串 (语句中“`”为反引号,而不是单引号“'”)
echo `expr index "$string" eo` #查找字符 "e或 o" 的位置:,输出1
Shell数组
bash支持一维数组(不支持多维数组),并且没有限定数组的大小,初始化时不需要定义数组大小。数组元素的下标由0开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式
1).数组定义(Shell 数组用括号来表示,元素用"空格"符号分割开)
数组名=(值1 值2 ... 值n)
e.g.
1).arrayName=(value0 value1 value2 value3)
2). arrayName=(
value0
value1
value2
value3
)
3). arrayName[0]=value0
arrayName[1]=value1
arrayName[n]=valuen
注:可以不使用连续的下标,而且下标的范围没有限制。
1).读取数组
${数组名[下标]} e.g. valuen=${arrayName[n]}
使用@或*符号可以获取数组中的所有元素
e.g.
echo ${arrayName[@]}
echo ${arrayName[*]}
2).获取数组的长度(与获取字符串的方法相同)
# 取得数组元素的个数
length=${#arrayName[@]}
# 或者
length=${#arrayName[*]}
# 取得数组单个元素的长度
lengthn=${#arrayName[n]}
Shell 注释
以"#"开头的行就是注释,会被解释器忽略。由于没有多行注释,可以把要注释的代码块写成函数,只要不调用就可以(个人不建议)。
Shell 传参
新建一个test.sh 文件,内容为
#!/bin/sh
echo "执行的文件名:$0" #$0 为执行的文件名
echo "第一个参数为:$1" #$1为第一个参数
echo "第二个参数为:$2" #$2为第二个参数
eecho "参数个数为:$#";
echo "传递的参数作为一个字符串显示:$*";
#read 命令从标准输入中读取一行,并把输入行的每个字段的值指定给 shell 变量
read argument
echo "$argument It is a test"
执行
sh test.sh 1 2 3 #参数间以空格隔开
输出
执行的文件名:test.sh
第一个参数为:1
第二个参数为:2
参数个数为:3
传递的参数作为一个字符串显示:1 2 3
随意输入一个变量:
123456789\/
123456789/ It is a test
另外,还有几个特殊字符用来处理参数:
参数处理 说明
$0 当前脚本的文件名
$n 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2
$# 传递到脚本的参数个数
$* 以一个单字符串显示所有向脚本传递的参数。与位置变量不同,此选项参数可超过9个
$$ 脚本运行的当前进程ID号
$! 后台运行的最后一个进程的进程ID号
$@ 与$*相同,但是使用时加引号,并在引号中返回每个参数
$- 显示shell使用的当前选项,与set命令功能相同
$? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
注:$* 与 $@ 区别:
相同点:都是引用所有参数。
不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " * " 等价于 "1 2 3"(传递了一个参数),而 "@" 等价于 "1" "2" "3"(传递了三个参数)。
Shell 基本运算符
算术运算符
关系运算符
布尔运算符
逻辑运算符
字符串运算符
文件测试运算符
原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。
expr 是一款表达式计算工具,使用它能完成表达式的求值操作。
e.g. echo "两数之和为 : `expr 2 + 2`"
注:
1).表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2
2).完整的表达式要被 ` ` (反引号)包含
以下假定变量a为10,变量b为20
算术运算符
注:
1).乘号(*)前边必须加反斜杠(\)才能实现乘法运算;
2).if...then...fi 是条件语句。
3).在 MAC 中 shell 的 expr 语法是:$((表达式)),此处表达式中的 "*" 不需要转义符号 "\" 。
4).条件表达式要放在方括号之间,并且要有空格,例如 [$a==$b] 是错误的,必须写成 [ $a == $b ]。
关系运算符
注:关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
布尔运算符
逻辑运算符
字符串运算符
文件测试运算符
Shell test 命令
Shell中的 test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。Shell test 命令需要和上面的运算符配合使用。
用一个小demo说明
新建一个test.sh 文件,内容为
#!/bin/sh
#数值测试,代码中的 [] 执行基本的算数运算
num1=100
num2=200
if test $[num1+num1] -eq $[num2]
then
echo '两个数相等!'
else
echo '两个数不相等!'
fi
#字符串测试
str1="abcd"
str2="abcde"
if test $str1 = $str2
then
echo '两个字符串相等!'
else
echo '两个字符串不相等!'
fi
#文件测试
if test -e ./test.sh
then
echo '文件已存在!'
else
echo '文件不存在!'
fi
#另外,Shell还提供了与( -a )、或( -o )、非( ! )三个逻辑操作符用于将测试条件连接起来,其优先级为:"!"最高,"-a"次之,"-o"最低
if test -e ./notFile -o -e ./test.sh
then
echo '有一个文件存在!'
else
echo '两个文件都不存在'
fi
输出
两个数相等!
两个字符串不相等!
文件已存在!
有一个文件存在!
Shell 流程控制
1.if语句
1).if
if 条件表达式
then
语句
fi
2).if else
if 条件表达式
then
语句
else
语句
fi
3).if else-if else
if 条件表达式
then
语句
else if 条件表达式
then
语句
else
语句
fi
2.for循环
for 变量 in 列表
do
语句
done
e.g.
for num in 1 2 3 4 5
do
echo "第$num个"
done
3.while 语句
while 条件表达式
do
语句
done
e.g.
num=1
while(( $num<=5 ))
do
echo $num
let "num++"
done
注:let 为Bash let 命令,它用于执行一个或多个表达式,变量计算中不需要加上 $ 来表示变量
while循环可用于读取键盘信息
e.g.
echo '输入一串字符'
while read str
do
echo "$str"
done
4.until 循环
until循环执行一系列命令直至条件为真时停止。条件可为任意测试条件,测试发生在循环末尾,因此循环至少执行一次—请注意这一点。
until 条件表达式
do
语句
done
5.无限循环
while :
do
语句
done
while true
do
语句
done
for (( ; ; ))
6.case
Shell case语句为多选择语句。可以用case语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令
case 值 in
模式1)
语句1
;;
模式2)
语句2
;;
*) #没有匹配
语句2
;;
esac
e.g.
echo '你输入的数字为:'
read num
case $num in
1) echo '你选择了 1'
;;
2) echo '你选择了 2'
;;
*) echo '你输入的数字不是1或2'
;;
esac
7.break跳出循环,continue跳出当前循环
Shell 函数
[ function ] functionname [()]
{
action;
[return int;]
}
注:
1、可以带function functionname() 定义,也可以直接functionname() 定义,不带任何参数。
2、参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255)
Demo test.sh
#!/bin/sh
argumentFunction () {
echo "第一个参数为 $1 !"
echo "第二个参数为 $2 !"
echo "第五个参数为 $5 !"
echo "第十个参数为 ${10} !"#当n>=10时,需要使用${n}来获取参数。
echo "参数总数有 $# 个!" #$#传递到脚本的参数个数
echo "作为一个字符串输出所有参数 $* !"#$*以一个单字符串显示所有向脚本传递的参数,也可用$@
return 255
}
#用函数名调用函数,后面跟函数参数
argumentFunction 1 2 3 4 5 6 7 8 9 10
#函数返回值在调用该函数后通过 $? 来获得。
echo "输入的两个数字之和为 $? !"
输出
第一个参数为 1 !
第二个参数为 2 !
第五个参数为 5 !
第十个参数为 10 !
参数总数有 10 个!
作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 10 !
输入的两个数字之和为 255 !
执行跟踪
为了调试Shell可打开跟踪(execution tracing)功能,有两种方式:向sh传递参数和shell通过set来设置。
1.将跟踪功能(execution tracing)打开,这会使得shell显示每个被执行的命令,并在前面加上“+ ”,即 一个加号后面跟着一个空格。
$sh -x test.sh #可与通过man sh查看sh的参数帮助
或
2.通过set 方法
#! /bin/sh
set -x #打开跟踪功能
执行内容语句
set +x #关闭跟踪功能
第二种e.g. 新建一个test.sh
#!/bin/sh
set -x #打开跟踪功能
echo "abc"
echo "efg"
set +x #关闭跟踪功能
echo "hij"
输出(同第一种方法,可调试部分功能模块)
+ echo abc
abc
+ echo efg
efg
+ set +x
hij
Shell 输入/输出重定向
一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:
标准输入文件(standard input即stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
标准输出文件(standard output即stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
标准错误文件(standard error即stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。
默认情况下,command > file 将 stdout 重定向到 file,command < file 将stdin 重定向到 file,command 2 > file 将 stderr 重定向到 file
Here Document
Here Document 是 Shell 中的一种特殊的重定向方式,用来将输入重定向到一个交互式 Shell 脚本或程序。
它的基本的形式如下:#它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command。
command << delimiter
document
delimiter
注:
结尾的delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。
开始的delimiter前后的空格会被忽略掉。
/dev/null 文件
/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。
command > /dev/null
e.g.
如果希望屏蔽 stdout 和 stderr,可以这样写:
command > /dev/null 2>&1
/dev/tty 文件
/dev/tty 另一种特殊文件,当程序打开此文件时,系统自动将它重定向到当前终端
e.g.
echo "hello" > /dev/tty 都会直接显示在当前的终端中
还可以通过|建立管道,管道的执行速度比使用临时文件的程序快许多。
e.g.program1 | program2 将program1的标准输出修改为program2的标准输入
Shell 文件包含
和其他语言一样,Shell 也可以包含外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。
Shell 文件包含的语法格式如下:
. filename # 注意点号(.)和文件名中间有一空格
或
source filename
e.g.
test1.sh
#!/bin/bash
username="xiaolan"
test2.sh
#!/bin/bash
. ./test1.sh #使用 . 号来引用test1.sh 文件
# source ./test1.sh # 或者使用以下包含文件代码
echo "hi,$username"
运行test2.sh,输出
hi,xiaolan
参考:
http://c.biancheng.net/cpp/view/6994.html
http://www.jb51.net/article/39506_all.htm