Linux 及 Linux Shell 简介
1.1.5 Linux的理念
小即是美
让程序只做好一件事
可移植性比效率更重要
一切皆文件
使用Shell
脚本来提高效率和可移植性
避免使用可定制性低下的用户界面
所有程序都是数据的过滤器
1.2 什么是Linux Shell
Shell
是一个用户程序,或是一个为用户与系统交互提供的环境。
Shell
是一个执行从标准输入设备读入命令的语言解释程序。
当你登录活或打开控制台时Shell
就会执行。
Shell
不是系统内核的一部分,但是它使用系统内核执行程序,创建文件等。
Shell
进程会提供一个命令行提示符,普通用户用$
作提示符,超级用户(root
)用#
作提示符。
Shell
编辑和回调命令
命令
说明
CTRL + W
删除光标位置钱的单词
CTRL + U
清空行
Tab
自动补全文件名的单词
CTRL + R
搜索先前使用的命令
CTRL + C
中止当前命令
CTRL + D
退出登录
ECS + T
调换光标钱的两个单词
上/下箭头
删除光标位置钱的单词
当用户准备结束登陆对话进程时,可以输入logout
、exit
或CTRL + D
组合键结束登录。
1.3 Shell 的种类
Shell
有多种类型,最常用的有三种,Bourne(sh)
、C Shell
、Korn Shell
。
使用 cat /etc/shells
命令查看系统中所有可用的Shell。
命令
说明
cat /etc/shells
查看系统中所有可用的Shell
grep root /etc/passwd
查看用户使用的哪种Shell
echo $SHELL
查看用户使用的哪种Shell
ps -p $$
查看用户使用的哪种Shell
1.5 Shell脚本是什么
Shell
脚本是Linux/Unix
编程环境的重要组成部分。
Shell
脚本一般有以下及部分组成。
解释
示例
Shell关键字
例如:if…else、for do … done
Shell 命令
例如:export、echo、exit、pwd、return
Linux 命令
例如:date、rm、mkdir
文本处理功能
例如:awk、cut、sed、grep
函数
例如:通常函数吧一些常用的功能放在一起。例如,/etc/init.d 目录中的大部分或全部系统Shell脚本所使用的函数都包含在文件 /etc/init.d/functions中。
控制流语句
例如:例如 if…then…else 或 执行重复操作的Shell循环。
1.6 为什么使用Shell脚本
使用Shell
的简单原因
使用简单
节省时间
可以创建你自己的自动化工具和应用程序
使系统管理任务自动化
因为脚本经过很好的测试,所以使用脚本做类似配置服务或系统管理任务时,发生错误的机会将大大减少。
我们经常使用的脚本实例有
监控你的Linux
系统
备份数据和创建快照
创建邮件告警系统
查找耗尽系统资源的进程
查找是否所有的网络服务都正常运行等等。
1.7 创建你的第一个Shell 脚本
一个Shell
脚本就是一个包含ASCII
文本的文件。
如果你像成功的写一个Shell
脚本,你需要做一下三件事情。
写一个脚本
允许Shell
执行它
把它放在Shell
可以找到的地方
1
2
3
#!/bin/bash (1)
# My First Script (2)
ls -l .* (3)
脚本的第一行是很重要的,他是一个告诉Shell
使用什么程序解释器的特别指示。
上面例子中使用的是/bin/bash
。如果使用其他脚本语言比如Perl
、awk
、python
等也同样使用这个机制。
脚本的第二行是一个注释。每一行中出现在#
符号后面的任何内容都将被bash
忽略。
默认情况下,Linux
是不允许文件执行的(权限不足),使用下面命令赋予权限
权限
注释
777
全部权限
755
读写和执行的权限
700
脚本私有,只有你可以读写和执行
初识Linux Shell 2.1 Bash Shell 2.1.1 Bash 简介
Bash
是一个与Bourne Shell
兼容的、执行从输入设备或文件读取命令的命令语言解释器。
Bash
与原来的Unix sh Shell
向后兼容,并且融合了一些有用的Korn Shell
和 C Shell
的特性。它相对于sh
在编程和交互式使用两方面都做了功能改进。
Bash
具有很好的一致性,它使用构建时发现编译平台特征的配置系统,因此可以构建在几乎任何一种Unix
版本上。
2.1.2 Bash 提供的改进
Bash
语法是Bourne Shell
语法的一个改进版本。大多数情况下Bourne Shel
l脚本可以被Bash
正常地运行。
2.2 Shell 在Linux环境中的角色 2.2.1 与登录Shell相关的文件
用户登录时Bash
将会使用以下初始化文件和启动脚本。
文件目录
解释
/etc/profile
系统级的初始化文件,定义了一些环境变量,由登录Shell
调用执行
/etc/bash.bashrc
、 /etc/bashrc
其文件名根据不同的Linux
发行版本而异,每个交互式Shell的系统级的启动脚本,定义了一些函数和别名
/etc/bash.logout
系统级的登录Shell
清理脚本,当登录Shell
退出时执行,部分Linux
发行版默认没有此文件
$HOME/.bash_profile
、$HOME/.bash_login
、$HOME/.profile
用户个人初始化脚本,由登录Shell调用执行。这三个脚本只有一个会被执行,按照此顺序查找,第一个才能在的将被执行。
$Home/.bashrc
用户个人的每个交互式Shell
的启动脚本
$HOME/.bash_logout
用户个人的登录Shell
清理脚本,当登录Shell
退出时执行
$HOME/.inputrc
用户个人的由readline
使用的启动脚本,定义了处理某些情况下的键盘映射
2.2.2 Bash 启动脚本
在用户登录时自动执行的脚本主要用来设置一些环境变量,例如设置JAVA_HOME
的路径。
目录
解释
/etc/profile
当用户在运行级别3登录系统时首先运行
/etc/profile.d
当/etc/profile
运行时,会调用该目录下的脚本
$HOME/.bash_profile
、$HOME/.bash_login
和$HOME/.profile
在/etc/profile
运行后第一个存在的被运行
$HOME/.bashrc
上述脚本的第一个运行后即调用此脚本
/etc/bashrc
将被$HOME/.bashrc
调用运行
/etc/profile.d
此目录下的脚本将被/etc/bashrc
或/etc/bash.bashrc
调用运行
Bash
启动脚本主要设置的环境有
设置环境变量PATH
和PSI
通过变量EDITOR
设置默认的文本编辑器
设置默认的umask
(文件或目录的权限属性)
覆盖活移除不想要的变量或别名
设置别名
加载函数
2.2.3 定制自己的Bash登录脚本 2.2.4 Bash 退出脚本
当登录Shell
退出时,如果$HOME/.bash_logout
脚本存在的话,Bash会读取并执行脚本的内容,此脚本的主要用途:
使用clear
命令清理你的屏幕终端输出
移除一些临时文件
自动运行一些命令或脚本等
2.2.5 定制自己的Bash 退出脚本 2.2.6 有效的登录Shell
路径
/etc/shells
是一个包含有效的登录Shell
全路径名的文本文件,这个文件会被chsh
命令(变更你的登录Shell)所使用也可被其他程序查询使用。比如ftp
服务,查看etc/shells
的内容。
1
2
3
4
5
6
7
8
$ cat /etc/shells
out:
/bin/sh
/bin/bash
/bin/nologin
/bin/tcsh
/bin/cs
/bin/ksh
你也可以使用which命令显示shell的全路径
1
2
3
$ which bash
out:
/bin/bash
2.3 SHell中的变量 2.3.1 Shell 中变量的类型
Shell中有两种变量的类型:系统变量(环境变量)和用户自定义的变量(本地变量或Shell变量)
系统变量由Linux Bash Shell
创建和维护的变量,你可以通过修改系统变量,如PS1
、PATH
、LANG
、HISTSIZE
、ISPLAY
等,配置Shell
的样式
常用的系统变量(环境变量)
系统变量
含义
BASH_VERSION
保存bash
实例的版本
DISPLAY
设置X display
名字
EDITOR
设置默认的文本编辑器
HISTFILE
保存命令历史的文件名
HISTFILESIZE
命令历史文件所能包含的最大行数
HISTSIZE
记录在命令历史中的命令数
HOME
当前用户的主目录
HOSTNAME
你的计算机的主机名
IFS
定义Shell
的内部字段分隔符,一般是空格符、制表符和换行符
PATH
搜索命令的路径。它是以冒号分隔的目录列表。Linux
下的标准命令之所以能在Shell
命令行下的任何路径直接使用,就是因为这些标准命令所在的目录的路径定义在了PATH
变量中,Shell
会在PATH
环境变量指定的全部路径中搜索任何匹配的可执行文件
PS1
你的提示符设定
PWD
当前工作目录。由cd
命令设置
SHELL
设置登录Shell
的路径
TERM
设置你的登录终端的类型
TMOUT
用于Shell
内建命令read
的默认超时时间。单位为秒。在交互式的Shell
中,此变量的值作为发出命令后等待用户输入的秒数,如果没有输入用户将会自动退出
你可以添加上述变量到你账号的home目录下的初始化文件中,比如~/.bash_profile
文件。这样每次登录系统时,这些变量会自动设置成你需要的值。
使用env
或者printenv
查看当前Shell
的所有系统变量。
1
2
3
4
5
6
7
8
$ env
或者
$ printenv
out:
USER=BENNY
LOGNAME=BENNY
HOME=/home/usr
...
2.3.2 如何自定义变量和给变量赋值
在Shell
中创建和设置变量是很简单的,其语法如下:
使用=
给变量赋值,输入的次序是:变量名
、赋值操作符
、赋予的值
。 赋值操作符=
的周围不要有任何空格 ,比如下面的变量定义将会得到command not found
的错误。
1
2
3
varName= varValue
varName =varValue
varName= varValue
1
2
3
4
$ var=$var+1
$ echo $var
out:
1+1
在Bash
中,要将算数表达式的数值给一个变量,可以使用let
命令
1
2
3
4
$ let var=2+1
$ echo $var
out:
3
1
2
3
4
5
$ a=3
$ b=$a
$ echo $b
out:
3
1
2
3
4
$ var=$(pwd)
$ echo $var
out:
/home/benny
将Bash
的内置命令read
读入的内容复制给变量:
1
2
3
4
5
$ echo -n "Enter var:"; read var
Enter var: 此处需要你自己输入(比如输入520)
$ echo $var
out:
520
2.3.3 变量命名规则
变量名必须以字母 或下划线 字符_
开头,后面跟字母、数字或下划线字符,第一个字符不能为数字。不要使用?
、
*
和其他特殊字符命名你的变量。
变量名是大小写敏感的,比如定义几个变量1
2
3
4
5
6
7
8
$ echo $var
out: 123
$ echo $Var
out: 1
$ echo $vAR
out: 2
$ echo $VAR
out: 3
2.3.4 实例:使用echo和printf 打印变量的值
使用echo命令显示变量值,还可以使用printf命令显示变量值。
1
2
3
4
$ var=123
$ printf "%s\n" $var
out:
123
1
printf <FORMAT> <ARGUMENTS...>
1
printf "FirstName" : %s\nLastName:%s" "$FIRSTNAME" "LASTNAME"
$FIRSTNAME
是格式规范,而后面的两个变量则是作为参数传入。格式用字符串中的%s
是指示打印参数的格式类型的分类符,这些分类符有不同的名字。
分类符表
参考书中page
20
转义字符表
参考书中page
20
与printf
命令不同,echo
命令没有提供格式化选项,因此echo
命令比printf
命令简单易用
echo
命令也提供转义字符的功能,可以是用转义字符与printf
命令中的基本相同,但需使用-e
选项激活转义字符功能。
1
2
3
4
5
6
7
$ var=10
$ echo "the number is $var"
out:
the number is 10
$ echo -e "Username: $USER\tHome directory:$HOME\n"
out:
Username:benny Home directory: /home/beny
1
2
3
4
$ LOGDIR="/var/log/"
$ echo "the log file is $LOGDIRmessage"
out:
the log file is
Bash将尝试找一个LOGDIRmessages的变量,而不是$LOGDIR,为了避免这种歧义,我们需要使用${}语法,如下:
1
2
$ echo "the log file is ${LOGDIR}messages"
the log file is /var/log/messages
2.3.5 变量的引用
引用一个变量的时候,最好使用双引号将变量名括起来。例如:`”$cariable”‘
这样可以防止被引用的变量值中的特殊字符(除:$、'
和\
)被解释为其他错误含义。
使用双引号可以防止变量中的值中由多个单词租车发给你的字符串分离,一个双引号括起来的变量使它自身编程一个单一词组,即使值中包含空格。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@iZ251k7t70aZ var]# for var in $list
> do
> echo "$var"
> done
one
two
three
[root@iZ251k7t70aZ var]# for var in "$list"
> do echo "$var"
> done
one two three
注意:
单引号的操作类似与双引号,但是它不允许引用变量,因为在单引号中字符'$'
的特殊含义将会失效。每个特殊的字符,除了字符'
,都将按字面含义解释。
1
2
3
4
5
[root@iZ251k7t70aZ var]# var=123
[root@iZ251k7t70aZ var]# echo '$var'
$var
[root@iZ251k7t70aZ var]# echo "$var"
123
2.3.6 export
使用export
命令可以将变量被子Shell
引用,可以使用export
命令将变量进行输出
命令:export [-fnp] [变量或函数名称]=[变量设置值]
-f
表示export
的一个函数;-n
表示将export
属性从指定变量多函数上移除 p
表示打印当前Shell所有输出的变量,与单独执行export
命令结果相同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@iZ251k7t70aZ var]# echo $$
17691
[root@iZ251k7t70aZ var]# java_home=/usr/local
[root@iZ251k7t70aZ var]# echo $java_home
/usr/local
[root@iZ251k7t70aZ var]# bash
[root@iZ251k7t70aZ var]# echo $$
17738
[root@iZ251k7t70aZ var]# echo $java_home
[root@iZ251k7t70aZ var]# exit
exit
[root@iZ251k7t70aZ var]# export java_home
[root@iZ251k7t70aZ var]# bash
[root@iZ251k7t70aZ var]# echo $$
17750
[root@iZ251k7t70aZ var]# echo $java_home
/usr/local
[root@iZ251k7t70aZ var]# bash
[root@iZ251k7t70aZ var]# echo $$
17762
[root@iZ251k7t70aZ var]# echo $java_home
/usr/local
[root@iZ251k7t70aZ var]#
系统变量会自动输出到后续命令的执行环境
2.3.7 如何删除变量
bash
下使用unset
命令来删除相应的变量或函数。unsert命令会自动
命令: unset [-fv] [变量或函数名称]
-f
选项表示删除一个已定义的函数;-v
选项表示删除一个变量
1
2
3
4
[root@iZ251k7t70aZ var]# echo $java_home
/usr/local
[root@iZ251k7t70aZ var]# unset java_home
[root@iZ251k7t70aZ var]# echo $java_home
使用unset
命令不能删除一个只读的变量,否则将会出现类似如下的错误:
1
2
3
4
5
[root@iZ251k7t70aZ var]# readonly java_home=/usr/local
[root@iZ251k7t70aZ var]# echo $java_home
/usr/local
[root@iZ251k7t70aZ var]# unset java_home
bash: unset: java_home: cannot unset: readonly variable
2.3.8 如何检查变量是否存在
命令:${ varName? ERROR : The Varibale is not defined}
1
2
3
4
5
6
7
[root@iZ251k7t70aZ var]# JAVA_HOME=/usr/local
[root@iZ251k7t70aZ var]# echo ${JAVA_HOME?ERROR:The variable is not defined}
/usr/local
[root@iZ251k7t70aZ var]# unset JAVA_HOME
[root@iZ251k7t70aZ var]# echo ${JAVA_HOME?ERROR:The variable is not defined}
bash: JAVA_HOME: ERROR:The variable is not defined
[root@iZ251k7t70aZ var]#
2.4 Shell环境进阶 2.4.1 回调历史命令 1
2
3
4
5
6
7
8
9
[root@iZ251k7t70aZ var]# history
44 ls
45 ./startup.sh
46 cd /usr
47 ls
48 cd local/
49 ls
50 c dtom
51 cd tomcat7/
在命令提示符下,可以通过CTRL + R
组合键输入相应的关键字可以搜索命令
在Shell
命令提示符下,可以简单的输入!!,来重复执行上一条执行过的命令
你还可以回调最近一次执行的以指定字符开头的命令
1
2
3
4
5
[root@iZ251k7t70aZ var]# ls
account cache cvs db empty games lib local lock log mail nis opt preserve racoon run spool tmp www yp
[root@iZ251k7t70aZ var]# !l
ls
account cache cvs db empty games lib local lock log mail nis opt preserve racoon run spool tmp www yp
你可以使用由history命令列出的列表的行号来重新调用响应的命令
1
2
3
4
5
6
7
8
9
10
11
[root@iZ251k7t70aZ var]# history
1052 ls -l
1053 clear
1054 ls
1055 ls
1056 clear
1057 history
[root@iZ251k7t70aZ var]# !1055
ls
account cache cvs db empty games lib local lock log mail nis opt preserve racoon run spool tmp www yp
[root@iZ251k7t70aZ var]#
2.4.2 Shell中的拓展
Shell
中的拓展有8 中,分别是
大括号拓展
波浪号拓展
参数和变量拓展
命令替换
算数拓展
进程替换
单词拆分
文件名拓展
大括号拓展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@iZ251k7t70aZ var]# echo a{b,c,d}e
abe ace ade
[root@iZ251k7t70aZ var]# echo {a..z}
a b c d e f g h i j k l m n o p q r s t u v w x y z
[root@iZ251k7t70aZ var]# echo {0..10}
0 1 2 3 4 5 6 7 8 9 10
[root@iZ251k7t70aZ var]# echo {5..-3}
5 4 3 2 1 0 -1 -2 -3
[root@iZ251k7t70aZ var]# echo {g..a}
g f e d c b a
[root@iZ251k7t70aZ var]#
[root@iZ251k7t70aZ var]# echo {a..c}{1..3}
a1 a2 a3 b1 b2 b3 c1 c2 c3
[root@iZ251k7t70aZ var]# echo a{{b,c,d}a,{e,f,g}b,h}i
abai acai adai aebi afbi agbi ahi
[root@iZ251k7t70aZ var]#
1
2
3
4
5
6
7
8
9
10
# 在当前文件夹下创建dir1 dir2 dir3
[root@iZ251k7t70aZ local]# ls
aegis bin etc filedirectroy games include lib lib64 libexec mvnreporsitory mysql pic sbin share src tomcat7
[root@iZ251k7t70aZ local]# mkdir {dir1,dir2,dir3}
[root@iZ251k7t70aZ local]# ls
aegis bin dir1 dir2 dir3 etc filedirectroy games include lib lib64 libexec mvnreporsitory mysql pic sbin share src tomcat7
[root@iZ251k7t70aZ local]#
# 在当前文件夹下创建 dir1 dir2 dir3
[root@iZ251k7t70aZ usr]# mkdir / {dir1,dir2}
在Bash
4.0中还提供给了一些大括号的新功能,比如在序列表达式中指定一个增量<INCR>
语法如下: {....}
1
2
$ echo {1..10..2}
1 3 5 7 9
波浪号扩展可以用来指代你自己的主目录,或其他人的主目录
1
2
3
4
5
6
7
[root@iZ251k7t70aZ benny]# cd ~ #进入自己的主目录
[root@iZ251k7t70aZ ~]# pwd
/root
[root@iZ251k7t70aZ ~]# cd ~benny #进入benny的主目录
[root@iZ251k7t70aZ benny]# pwd
/home/benny
[root@iZ251k7t70aZ benny]#
Bash
支持一下三种方式来实现文件名拓展
*
匹配任何字符串,包括空字符串
?
匹配任意单个字符
[...]
匹配方括号内的任意字符
列出所有以字母a或b开头的配置文件
ls /etc/[ab]*.conf
创建和使用别名
在Linux系统环境下,我们通常需要使用命令行来处理一些任务,并且会很频繁的使用某些命令语句,为了节省时间,我们可以在文件~/.bashrc
中为这些命令语句创建别名。
一旦你修改了~/.bashrc
文件,你必须重新启动Shell
后,新的设置才会生效。
alias name=’command’
+ name 用户自定义的用于别名的任意简短字符
+ command 任意linux命令
alias Vim=’vim -ls -t | head -1’’
alias findbig=’find . -type f -exec ls -s {} \;’ | sort -n -r | head -5’
2.4.4 修改Bash
提示符
此处没懂
2.4.5 设置Shell
选项
语法: set(选项)(参数)
选项
注释
-a:
标示已修改的变量,以供输出至环境变量。
-b:
使被中止的后台程序立刻回报执行状态。
-C:
转向所产生的文件无法覆盖已存在的文件。
-d:
Shell预设会用杂凑表记忆使用过的指令,以加速指令的执行。使用-d参数可取消。
-e:
若指令传回值不等于0,则立即退出shell。
-f:
取消使用通配符。
-h:
自动记录函数的所在位置。
-H
Shell:可利用”!”加<指令编号>的方式来执行history中记录的指令。
-k:
指令所给的参数都会被视为此指令的环境变量。
-l:
记录for循环的变量名称。
-m:
使用监视模式。
-n:
只读取指令,而不实际执行。
-p:
启动优先顺序模式。
-P:
启动-P参数后,执行指令时,会以实际的文件或目录来取代符号连接。
-t:
执行完随后的指令,即退出shell。
-u:
当执行时使用到未定义过的变量,则显示错误信息。
-v:
显示shell所读取的输入值。
-x:
执行指令后,会先显示该指令及所下的参数。
参数
状态
注释
allexport
off
从设置开始标记所有新的和修改过的用于输出的变量
braceexpand
on
允许符号扩展,默认选项
emacs
on
在进行命令编辑的时候,使用内建的emacs编辑器, 默认选项
errexit
off
如果一个命令返回一个非0退出状态值(失败),就退出.
errtrace
off
functrace
off
hashall
on
histexpand
on
在做临时替换的时候允许使用!和!! 默认选项
history
on
允许命令行历史,默认选项
ignoreeof
off
禁止coontrol-D的方式退出shell,必须输入exit。
interactive-comments
on
在交互式模式下, #用来表示注解
keyword
off
命令把关键字参数放在环境中
monitor
on
允许作业控制
noclobber
off
保护文件在使用重新动向的时候不被覆盖
noexec
off
在脚本状态下读取命令但是不执行,主要为了检查语法结构。
nolog
off
noglob
off
禁止路径名扩展,即关闭通配符
notify
off
在后台作业以后通知客户
nounset
off
在扩展一个没有的设置的变量的时候, 显示错误的信息
onecmd
off
在读取并执行一个新的命令后退出
physical
off
如果被设置,则在使用pwd和cd命令时不使用符号连接的路径 而是物理路径
pipefail
off
限制错误。还可以使用trap来截获信号
posix
off
改变shell行为以便符合POSIX要求
privileged
off
一旦被设置,shell不再读取.profile文件和env文件 shell函数也不继承任何环境
verbose
off
为调试打开verbose模式
vi
off
在命令行编辑的时候使用内置的vi编辑器
xtrace
off
打开调试回响模式
set -o ignoreeof
set +o ignoreeof
查看由Bash
内只命令shopt
控制的Bash
选项及其状态
参数
状态
cdable_vars
off
cdspell
off
checkhash
off
checkwinsize
on
cmdhist
on
dotglob
off
execfail
off
expand_aliases
on
extdebug
off
extglob
off
extquote
on
failglob
off
force_fignore
on
gnu_errfmt
off
histappend
off
histreedit
off
histverify
off
hostcomplete
on
huponexit
off
interactive_comments
on
lithist
off
login_shell
on
mailwarn
off
no_empty_cmd_completion
off
nocaseglob
off
nocasematch
off
nullglob
off
progcomp
on
promptvars
on
restricted_shell
off
shift_verbose
off
sourcepath
on
xpg_echo
off
使用shopt
命令开启和关闭Bash
选项的语法如下
shopt -s feature-name # 开启一个Bash选项
shopt -u feature-name # 关闭一个bash选项
shopt
命令,cdspell
选项,用于检测cd
命令中目录名字的拼写错误并纠正。错误检查包括调换的字符,缺少的字符,和重复的字符。
1
2
3
4
5
6
7
8
9
[root@iZ251k7t70aZ ~]# cd /var/lid
-bash: cd: /var/lid: No such file or directory
[root@iZ251k7t70aZ ~]# shopt -s cdspell
[root@iZ251k7t70aZ ~]# cd /var/lid
/var/lib
[root@iZ251k7t70aZ lib]# cd /var/lid
/var/lib
[root@iZ251k7t70aZ lib]# pwd
/var/lib
选项cdspell
只在交互式Shell
中有效
你可以使用shopt和set为你定制一个Bash环境,编辑你的~/.bashrc
文件,可以添加如下命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 纠正目录拼写
shopt -q -s cdspell
# 当终端创建口大小改变时,确保显示得到更新
shopt -q -s extglob
#开启扩展模式匹配特性
shopt -q -s extglob
# 退出时追加而不是重启命令历史
shopt -s histpapperd
# 使Bash尝试保存历史记录中多行命令的所有行
shopt -q -s cmdhist
# 得到后天任务结束的及时通知
set -o notify
实例:
使用declare命令定义一个新的环境变量”mylove”,并且将其值设置为”java”,输入如下命令:
declare mylove=’Visual C++’ #定义新环境变量
再使用set命令将新定义的变量输出为环境变量,输入如下命令:
set -a mylove #设置为环境变量
执行该命令后,将会新添加对应的环境变量。用户可以使用env命令和grep命令分别显示和搜索环境变量”mylove”,输入命令如下:
env | grep mylove #显示环境变量值
你可以定制系统范围的Bash
环境,默认情况下,文件/etc/profile
作为Bash的系统范围用户参数文件,而在CentOS
,Fedora
和Redhat
下推荐的方法是使用目录/etc/profle.d
中的文件。
3 常用Shell(Bash)
命令 4 Shell
命令进阶 5 Shell
编程基础 5.1 Shell
脚本的第一行 "#!" (Shebang)
#!
(Shebang)是一个有#
!
构成的字符序列,出现在脚本文件第一行的前两个字符,用于指示一个解释程序。
语法格式:
#!INTERPRETER [OPTION]...
INTERPRETER必须是一个程序的绝对路径
当一个内容经以#!
开头的脚本作为一个程序运行时,程序加载器会将脚本第一行的#!
之后的内容解析为一个解释程序,然互殴用这个指定的解释程序替代其运行,并将脚本的路径作为第一个参数传递给解释程序。
例如一个脚本的路径名为path/to/script
并且它的内容如下行开头
#!/bin/sh
程序加载器被指示用解释程序/bin/bash
替代其运行,并将路径path/to/script
作为第一个参数传递给解释程序/bin/bash
几乎所有的Bash
脚本的内容都是以/bin/bash
开头,并确保Bash
将作为脚本的解释程序
如果没有指定#!
,则会默认用/bin/sh
作为解释程序,但还是推荐你将Bash
脚本的第一行设为#!/bin/bash
5.2 Shell
中的注释
在Shell
脚本中,#
是注释表示符。
在Shell
脚本中, 还可以使用Bash的HERE DOCUMENT 特性添加多行的注释内容
1
2
3
4
5
6
7
8
9
10
#!/bin/bash
echo "下面是注释"
<<COMMENT
comment line 1
comment line 2
comment line 3
COMMENT
echo "上面是注释"
5.3 设置脚本的权限和执行脚本
在运行一个Shell
脚本之前,确保Shell
脚本文件具有可执行的权限,否则会报错permission denied
给脚本添加执行权限
chmod u+x ./multicomments.sh
chmod +x ./multicomments.sh
1
2
$ /home/benny/scripts/helloworld.sh
hello world!
1
2
3
$ cd /home/benny
$ ./scripts/hellowrold.sh
hello world!
如果想像运行一个命令一样运行一个脚本,即不需要指定绝对路径或相对路径只需要输入脚本名称即可。
要实现这一目的,需要将脚本所在目录的路径添加到你的PATH
环境变量中 ,那么就可以在任何路径下直接运行目录PATH
环境变量中。
例如:将目录路径home/benny/scripts
加入PATH
环境变量中,就可以在任何路径下直接运行目录home/benny/scripts
下的Shell
脚本。
1
2
3
4
$ export PATH=$PATH:/home/benny/scripts
$ cd /tmp
$ hellowrold.sh
out : hello world
通过export
命令添加的PATH
变量会在终端关闭后消失,所以建议通过编辑/etc/profile
来改PATH
环境变量,也可以改根目录下的.bashrc
(即:~/.bashrc
)
5.4 Shell
变量进阶 5.4.1 Bash中的参数拓展 5.4.2 Bash的内部变量
Bath的内部变量会影响Bash脚本的行为。
$BATH
用于引用Bash实例的全路径名
1
2
[root@iZ251k7t70aZ ~]# echo "your home directory is $HOME"
your home directory is /root
$IFS
是内部字段分隔符的缩写。此变量决定当Bath解析字符串时将怎样识别字段,或单词分界线。
变量$IFS
的默认值是空格(空格、制表符和换行),但可以被修改。
1
2
3
4
[root@iZ251k7t70aZ ~]# set x y z #使用set命令,将x,y,z赋予位置参数1,2,3
[root@iZ251k7t70aZ ~]# IFS=":;-" #指定Bash的内部字段分隔符
[root@iZ251k7t70aZ ~]# echo "$*" #拓展特殊参数*
x:y:z
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
echo "开始执行脚本"
set -o nounset
TMOUT=3
echo "Are you sure (y/n)"
read input
if ["$input" == "y"]
then
echo "Continue ...."
else
echo "Exit!"
fi
UID
当前用户账号标识码(ID
号)与/etc/passwd
中记录的相同,此变量记录的是当前账户的真实ID
,即使该账户通过su
命令已经临时获得了另一个账号的权限,$UID
是一个只读变量,不接受从命令行或脚本的修改。
使用$UID
变量来判断当前账号是否为root
5.4.3 Bash 中的位置参数和特殊参数
Bash
中的位置参数事由除0
以外的一个或多个数字表示的参数。
位置参数事由Shell
和Shell
的函数呗引用时有Shel
或Shell
函数的参数赋值,并且可以使用Bash的内部命令set
来重新赋值,位置参数可以被引用为${N}
,或当N只含有一个数字时被引为$N
1
2
3
[root@iZ251k7t70aZ myscript]# set 123 four five
[root@iZ251k7t70aZ myscript]# echo "$1 $2 $3 $4"
123 four five
多于一个数字的位置参数在拓展时必须放在大括号中,比如${10}
位置参数不能用过赋值语句来赋值,只能通过Bash
的命令set
和shift
来设置和取消他们,当shell
函数运行时候,位置参数会被临时替换。
bash 对一些参数的处理比较特殊,这些参数只能被引用,但不能修改他们的值 ,这些特殊参数分别是*
、@
、 #
、 ?
、 -
、$
、!
、0
、_
参数
释义
更多
*
拓展为从1开始的所有位置参数
@
也将拓展为从1开始的所有位置参数
#
拓展为位置参数的个数
?
拓展为最近一个在前台执行的命令的退出状态
-
拓展为当前的选项标志
$
拓展为当前Shell的进程号
!
拓展为最近一次执行的后台命令的进程号
0
拓展为Shell或Shell脚本的名称
_
在Shell启动时,它被设置为开始运行的shell或者Sehll脚本中
参数
释义
更多
$0
这个程式的执行名字
$n
这个程式的第n个参数值,n=1..9
$*
这个程式的所有参数,此选项参数可超过9个。
$#
这个程式的参数个数
$$
这个程式的PID(脚本运行的当前进程ID号)
$!
执行上一个背景指令的PID(后台运行的最后一个进程的进程ID号)
$?
执行上一个指令的返回值 (显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误)
$-
显示shell使用的当前选项,与set命令功能相同
$@
跟$*类似,但是可以当作数组用
5.4.4 使用declare
命令指定变量的类型
declare
命令是Bash的内部命令,用于声明变量和修改变量的属性,与Bash的另一个内部命令typeset的用法和用途完全相同
-r
选项,declare命令将吧指定的变量定义为只读变量,这些变量将不能再赋予新值或被清除
-i 选项,declare命令将吧指定的变量定义为整数型变量,赋予整形变量的任何类型的值都将被换成整数
-x 选项,declare命令将吧指定的变量通过环境输出到后续命令
-p 选项,declare命令将显示指定变量的属性和值
有时一个任务或命令会运行很长时间,如果不能确定这个任务什么时候才能结束,这是最好就是把它放到后台运行,然后一旦退出系统,这个任务将被终止
nohup
命令能让运行的命令或脚本在你退出系统后继续在后台运行。
语法如下:
nohup COMMAND [ARG]… &
COMMAND:Shell脚本或命令的名称
[ARG]:脚本或命令的参数
&:nohup命令不能自动地将任务放在后台运行,你必须明确地在nohup命令的末尾添加操作控制符 &
使用nohup命令运行一个脚本script.sh
1
2
3
$ nohup sh script.sh &
[1] 12496
$ nohup : appending output to 'nohup.out'
其中[1]
是任务编号,12496
是任务的进程号,最后一句表示当前脚本运行输出的内容都将被写入到但钱目录下的文件 nohup.out中。
当你退出系统后在重新登陆,你仍会看到脚本script.sh在后台运行。
1
2
$ ps -ef | grep 12496
out:benny 12496 1 0 18:15? 00:00:00 sh script.sh
5.4.5 Bash 中的数组变量
ARRAYNAME[INDEX]=value
INDEX手机一正数,或是一个值为正数的算数表达式
显式的声明一个数组变量使使用Bash的内部命令declare
$ declare -a ARRAYNAME
带有一个索引编号的声明也是可以接受的,但索引编号将被忽略,数组的属性可以使用Bash的内部命令declare和readonly指定,这些属性将被应用到数组的所有变量。
定一个数组变量
$ declare -a linux={‘java’,’php’,’javascript’}
$ ARRAYNAME={value1,value2,value3…valueN}
若要引用数中某一项的内容,必须使用{}
,如果索引编号是@
或*
,那么数组的所有成员都将被使用 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ echo ${linux[@]}
out: java php javascript
$ arr1=(one two three)
$ echo ${arr1[0]} ${arr1[1]} ${arr1[2]}
out: one two three
$ echo ${arr1[*]}
out: one two three
$ echo ${arr1[@]}
out: one two three
$ arr1[3]=four
$ echo ${arr1[@]}
out:one two three four
$ echo $arr1
out:one
如果引用数组时,不指定索引编号,则引用的将是数组中的第一元素,即使用索引编号为0
使用unset
命令可以删除一个数组或数组中的成员变量
1
2
3
4
5
6
7
8
9
$ unset arr1[2]
$ echo ${arr1[@]}
out: one two four
$ unset arr1
$ echo ${arr1[@]}
out:
5.5 Shell 算术运算 5.5.1 Bash的算数运算符 5.5.2 数字常量 5.5.3 使用算数拓展和let进行算数运算 5.5.4 使用expr
命令 5.6 退出脚本 5.6.1 退出状态码
状态值
释义
0
表示运行成功,程序执行未遇到任何问题
1 ~ 125
表示运行失败,脚本命令、系统命令错误或参数传递错误
126
找到了该命令但无法执行
127
未找到要运行的命令
> 128
命令被系统强行结束
Shell
脚本和它里面的函数也会返回一个退出状态码。
可以通过检查Bash
的特殊变量$?
来查看上一条命令的退出状态码 。
1
2
3
4
5
6
7
8
9
[root@iZ251k7t70aZ usr]# ls
a b bin etc games include java kerberos lib lib64 libexec local sbin share src tmp X11R6
[root@iZ251k7t70aZ usr]# echo $?
0
[root@iZ251k7t70aZ usr]# ls /benny
ls: /benny: No such file or directory
[root@iZ251k7t70aZ usr]# echo $?
2
[root@iZ251k7t70aZ usr]#
5.6.2 使用exit
命令
退出状态码N
可以被其他命令或脚本用来采取他们自己的行为,如果退出状态码N
被省略,则将把最后一条运行的命令的退出状态作为脚本的退出状态码。
1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
cd $SOME_DIR
if [ $? -eq 0 ]; then
rm -rf *
else
echo 'Cannot change directory!'
exit 1
fi
上述例子,检查csd
命令的退出状态,如果其不为0,将打印一个错误消息,并使用exit
命令终结脚本运行,返回退出状态码1。
linux shell
命令中判断对文件和文件夹的判断,判断表达式
[ ]
部分是判断表达式,-d
表示判断是否是目录(directory
)&&
是“逻辑与”操作符(这个与C语法类似啊),只有&&前面的判断成立(返回逻辑真),后面的语句才会得到执行。 总之,含义就是: 若/root/Desktop/为目录,就执行 chmod 777 /root/Desktop/download.desktop 若/root/Desktop/不是目录(不存在该目录),就执行 chmod 777 /root/桌面/download.desktop 其实就是处理 Desktop 是英文和中文两种情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
BAK=/data
TAPE=/dev/st0
echo "Trying to backup ${BAK} direcotry to type device ${TAPE}"
[! -d $BAK] &&{echo "Source backup directory $BAK not found"}
if [$? -ne 0]
then echo "An Error occurred while making a type backup"
exit 3
fi
exit 0 #如果备份成功,则返回0
** [里面的参数 还有待继续学习]
5.7 调试脚本
常用的脚本调试方法是Bash
的-x
选项启动一个子Shell
它将以调试模式运行,使Shell
在执行脚本的过程中把实际执行的每一个命令显示出来,并且在每一个命令行的行首显示一个 +
号
+
号后面显示的是经过了参数拓展之后的命令行的内容,有助于分析是什么命令。
1
2
3
4
5
6
7
8
[root@iZ251k7t70aZ myscript]# ./param.sh
this is /bin/bash
Linux iZ251k7t70aZ 2.6.18-371.11.1.el5 #1 SMP Wed Jul 23 15:12:55 EDT 2014 x86_64 x86_64 x86_64 GNU/Linux
[root@iZ251k7t70aZ myscript]# bash -x param.sh
+ echo 'this is /bin/bash'
this is /bin/bash
+ uname -a
Linux iZ251k7t70aZ 2.6.18-371.11.1.el5 #1 SMP Wed Jul 23 15:12:55 EDT 2014 x86_64 x86_64 x86_64 GNU/Linux
Bash 中还有一个 -v
选项,该选项激活详细输出模式
在此模式,由Bash
读入的脚本的每一个命令行豆浆在执行前被输出。
通常情况下,将-v
选项和-x
选项同时使用
1
2
3
4
5
6
7
8
9
[root@iZ251k7t70aZ myscript]# bash -xv param.sh
#!/bin/bash
echo "this is /bin/bash"
+ echo 'this is /bin/bash'
this is /bin/bash
uname -a
+ uname -a
Linux iZ251k7t70aZ 2.6.18-371.11.1.el5 #1 SMP Wed Jul 23 15:12:55 EDT 2014 x86_64 x86_64 x86_64 GNU/Linux
[root@iZ251k7t70aZ myscript]#
-x
选项虽然使用起来比较方便,但它输出的调试信息仅限于参数拓展后的每一条执行命令以及行首的一个+
号,但却没有代码行的行号这样的重要信息。
可以通过Bash
的一些内部环境变量来增加-x
选项的输出信息
内部环境变量
释义
$LINENO
表示Shell脚本的当前行号
$FUNCNAME
它是一个包含了当前在执行调用栈中的所有Shell函数名称的数组变量。${FUNCNAME[0]}代表正在执行的Shell函数的名称,${FUNCNAME[1]则代表调用函数${FUNCNAME[1]则代表调用函数${FUNCNAME[0]}的函数的名字,一次类推
$PS4
使用Bash
的-x
选项时,每一条执行的命令的行首会显示+
号,而这个+
号其实就是变量$PS4
的默认值
利用变量$PS4
的这一特性,结合上述另两个Bash内部变量,通过重新定义变量$PS4就可以增强-x
选项的输出信息
1
$ export PS4='+{$LINENO:${FUNCNAME[0]}}'
1
2
3
4
5
6
7
8
9
10
[root@iZ251k7t70aZ myscript]# export PS4='+{$LINENO:${FUNCNAME[0]}}'
[root@iZ251k7t70aZ myscript]# bash -xv param.sh
#!/bin/bash
echo "this is /bin/bash"
+{2:}echo 'this is /bin/bash'
this is /bin/bash
uname -a
+{3:}uname -a
Linux iZ251k7t70aZ 2.6.18-371.11.1.el5 #1 SMP Wed Jul 23 15:12:55 EDT 2014 x86_64 x86_64 x86_64 GNU/Linux
[root@iZ251k7t70aZ myscript]#
5.8 Shell脚本编程风格
每个代码行不多于80个字符
保持一直的缩进深度,程序结构的缩进应与逻辑嵌套深度一致
每一个代码块之间留一个空行,可以提高脚本的可读性
每个脚本文件都必须要有一个文件头注释,任何一个不简短而不显而易见的函数都需要注释
文件头提供文件名和他的内容等一些信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash
#
#=====================================
#
# FILE: param.sh
#
# USAGE: ./param.sh
#
# DESCRIPTION: Read the Bash variable $SECONDS
#
# OPTIONS: ----
#
# REQUIREMENTS:----
#
# BUGS:---
#
# NOTES:---
#
# ORGANIZATION:---
#
# CREATETED:---
#
# REVISION:---
6.1 Shell的条件执行–条件测试 6.1.1 使用test命令
Shell脚本可以使用条件逻辑,使脚本可以根据参数,Shell变量或是其他条件的值采取不同的行动。
test命令运行你做各种测试并每当测试成功或失败时设置它的退出状态码为0
(表示真
)或1
(表示假
)
test
命令可以用于:
test命令的语法如下:
test EXPRESSION || [空格
EXPRESSION空格
]
[ EXPRESSION
] 注意: 表达式前后需要有空格
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@iZ251k7t70aZ myscript]# test -d "$HOME"; echo $?
0
[root@iZ251k7t70aZ myscript]# test -d "$HOMEs"; echo $?
1
[root@iZ251k7t70aZ myscript]# [ "abc" != "def"]; echo $?
-bash: [: missing `]'
2
[root@iZ251k7t70aZ myscript]# ["abc" != "def" ]; echo $?
-bash: [abc: command not found
127
[root@iZ251k7t70aZ myscript]# [ "abc" != "def" ]; echo $?
0
[root@iZ251k7t70aZ myscript]# test 8 -gt 3 && echo TRUE || echo False
TRUE
[root@iZ251k7t70aZ myscript]# test -8 -gt 3 && echo TRUE || echo False
False
操作符
描述符
-e
文件存在
-a
文件存在,这个选项的效果与-e相同. 但是它已经被”弃用”了, 并且不鼓励使用.
-f
表示这个文件是一个一般文件(并不是目录或者设备文件)
-s
文件大小不为零
-d
表示这是一个目录
-b
表示这是一个块设备(软盘, 光驱, 等等.)
-c
表示这是一个字符设备(键盘, modem, 声卡, 等等.)
-p
这个文件是一个管道
-h
这是一个符号链接
-L
这是一个符号链接
-S
表示这是一个socket
-t
文件(描述符)被关联到一个终端设备上这个测试选项一般被用来检测脚本中的stdin([ -t 0 ]) 或者stdout([ -t 1 ])是否来自于一个终端.
-r
文件是否具有可读权限(指的是正在运行这个测试命令的用户是否具有读权限)
-w
文件是否具有可写权限(指的是正在运行这个测试命令的用户是否具有写权限)
-x
文件是否具有可执行权限(指的是正在运行这个测试命令的用户是否具有可执行权限)
-g
set-group-id(sgid)标记被设置到文件或目录上,如果目录具有sgid标记的话, 那么在这个目录下所创建的文件将属于拥有这个目录的用户组, 而不必是创建这个文件的用户组. 这个特性对于在一个工作组中共享目录非常有用.
-u
set-user-id (suid)标记被设置到文件上,如果一个root用户所拥有的二进制可执行文件设置了set-user-id标记位的话, 那么普通用户也会以root权限来运行这个文件. [1] 这对于需要访问系统硬件的执行程序(比如pppd和cdrecord)非常有用. 如果没有suid标志的话, 这些二进制执行程序是不能够被非root用户调用的.
-O
判断你是否是文件的拥有者
-G
文件的group-id是否与你的相同
-N
从文件上一次被读取到现在为止, 文件是否被修改过
f1 -nt
f2
文件f1比文件f2新
f1 -ot
f2
文件f1比文件f2旧
f1 -ef
f2
文件f1和文件f2是相同文件的硬链接
!
“非” – 反转上边所有测试的结果(如果没给出条件, 那么返回真).
检查名命令文件/bin/cp
是否存在,如果存在则打印找到此文件
1
2
3
4
5
[root@iZ251k7t70aZ myscript]# test -e /bin/cp && "the command $_ found " || "the command $_ not found"
-bash: the command /bin/cp found : No such file or directory
[root@iZ251k7t70aZ myscript]# [ -d /local ] && echo "真" || echo "假"
真
上述命令语句中的$_
表示前一个执行的命令中的额最后一个参数。
操作符
描述
-z 《String》
如果《String》为空则为真
-n 《String》
如果《String》不为空则为真
《String1》=《String2》
如果《String1》与《String2》相同则为真
《String1》!=《String2》
如果《String1》与《String2》不相同则为真
《String1》<《String2》
如果《String1》的字典顺序排在《String2》之前则为真
《String1》>《String2》
如果《String1》的字典顺序排在《String2》之后则为真
操作符
描述
-eq
等于,如:if [ “$a” -eq “$b” ]
-ne
不等于,如:if [ “$a” -ne “$b” ]
-gt
大于,如:if [ “$a” -gt “$b” ]
-ge
大于等于,如:if [ “$a” -ge “$b” ]
-lt
小于,如:if [ “$a” -lt “$b” ]
-le
小于等于,如:if [ “$a” -le “$b” ]
<
小于(需要双括号),如:((“$a” < “$b”))
<=
小于等于(需要双括号),如:((“$a” <= “$b”))
>
大于(需要双括号),如:((“$a” > “$b”))
>=
大于等于(需要双括号),如:((“$a” >= “$b”))
6.1.2 if结构的语法格式
if TEST-COMMANDS ; then CONSEQUENT-COMMANDS ; fi 或 if TEST-COMMANDS ; then CONSEQUENT-COMMANDS fi 或 if TEST-COMMANDS then CONSEQUENT-COMMANDS fi
6.1.3 if…else…fi
if TEST-COMMANDS then CONSEQUENT-COMMANDS else CONSEQUENT-COMMANDS fi
6.1.5 多级的if…elif…else…fi
if TEST-COMMANDS then CONSEQUENT-COMMANDS elif TEST-COMMANDS then CONSEQUENT-COMMANDS elif TEST-COMMANDS then CONSEQUENT-COMMANDS else CONSEQUENT-COMMANDS fi
6.2 条件执行 6.2.1 逻辑与&&
1
2
3
4
if [ on $var ] && [ -e $var ]
then
echo "..."
fi
在test
命令中我们可以使用-a
选项来表示逻辑与。
将上述命令改写:
1
2
3
4
if [ -n $var -a -e $var ]
then
echo "..."
fi
6.2.2 逻辑或||
1
2
3
4
5
NOW='data +%a'
if [ "$NOW" = "MON" ] || [ "$NOW" = "SAT" ]
then
echo "..."
fi
上述脚本中我们使用"[[ ]]"
代替"[]"
,那么次脚本会简介一些
1
2
3
4
5
NOW='data +%a'
if [[ $NOW = "MON" || $NOW = "SAT" ]]
then
echo "..."
fi
与test
命令的-a
选项类似,我们可以使用test
命令的-o
选项来表示逻辑或
将上述命令改写成:
1
2
3
4
5
6
# 定义变量NOW,并将计算得到的今天是星期几赋值给变量NOW
NOW='data +%a'
if [ "$NOW" = "MON" -o "$NOW" = "$SAT" ]
then
echo "..."
fi
6.2.3 逻辑非 “!” 1
2
3
4
5
6
7
if [! -d /home/benny]
then
mkdir /home/benny
else
echo "the directory is exist"
fi
6.3 case语句实例
case语句是多级的if…then…else…fi语句很好的替代方式。
它可以让一个条件与多个模式相比较,而且case语句的结构的读写比较方便
case语句的语法实例
1
2
3
4
5
6
7
8
9
10
11
12
case EXPRESSION in
PATTERN 1 )
CONSEQUENT-COMMANDS
;;
PATTERN 2 )
CONSEQUENT-COMMANDS
;;
PATTERN N )
CONSEQUENT-COMAANDS
;;
esac
case
语句结构一定要以"esac"
结尾,每一个命令列表都以两个";;"
为终结,只有最后一个命里该列表的";;"
可以被省略。
通过一个Linux
下信号处理的脚本来学习case
语句的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
# ===============================
# FILE: killsignal.sh
# USAGE: ./killsignal.sh
# DESCRIPTION:
# AUTHOR: benny
# CREATED: 10/24/2013
# ===============================
if [ $# -lt 2]
then
echo "..."
exit
fi
case "$1" in
1)
echo "..."
;;
2)
kill -SIGHUP $2
;;
*)
echo "前面的都没有匹配,则匹配此选项"
esac
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/bash
# ===============================
# FILE: killsignal.sh
# USAGE: ./killsignal.sh
# DESCRIPTION:
# AUTHOR: benny
# CREATED: 10/24/2013
# ===============================
NOW='date +%a'
case $NOW in
# 若今天为星期一
Mon)
echo "FULL Backup"
;;
#若今天为星期二、星期三、星期四
Tue | Wed | Thu )
echo "Partial backup"
;;
# 若今天为星期六、星期天
Sat | Sun)
echo "no backup"
;;
*)
echo "notihing"
;;
exac
7.1 for循环
首先,循环条件中使用的变量必须是已经初始化的,然后在循环中开始执行
在每一次循环开始时,进行一次测试
重复的执行一个代码块
7.1.1 for循环语法
1
2
3
4
5
6
7
for VAR in item1 item2 ... itemN
do
command1
command2
...
commandN
done
1
2
3
4
5
6
7
for VAR in $fileName
do
command1
command2
...
commandN
done
1
2
3
4
5
6
7
8
for var in $(Linux-command-name)
# 或者使用 for VAR in 'Linux-command-name'
do
command1
command2
...
commandN
done
在for循环中,每次指定列表中的(item1…itemN) 新值被赋值给变量VAR后,for循环都会执行一次,它将重复的执行do和done之间的所有语句,知道条件不满足时为止。
这些值通常都是
字符串
数字
命令行参数
文件名
Linux命令的输出
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
#=====================================
# FILE: killsignal.sh
# USAGE: ./killsignal.sh
# DESCRIPTION: 简单的使用for循环脚本
# AUTHOR: benny
# CREATED: 10/24/2013
#==========================
for i in 1 2 3 # 从1~3循环
do
echo "the for loop is run $i times"
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
#=====================================
# FILE: killsignal.sh
# USAGE: ./killsignal.sh
# DESCRIPTION: 使用变量内容的for循环脚本实例
# AUTHOR: benny
# CREATED: 10/24/2013
#==========================
filenames="/etc/yp.conf /etc/nsswitch.conf /etc/auto.master"
for file in $filenames
do
[ -f $file ] && echo "the file $file was found " || echo "the file is not found"
done
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
#=====================================
# FILE: killsignal.sh
# USAGE: ./killsignal.sh
# DESCRIPTION: 使用变量内容的for循环脚本实例
# AUTHOR: benny
# CREATED: 10/24/2013
#==========================
echo "Printing file list in /tmp diretroy:"
for file in 'ls /etm/*'
do
echo $file
done
1
2
3
4
5
6
7
for (( EXP1; EXP2; #XP3 ))
do
command1
command2
...
commandN
done
EXP1 初始化表达式
EXP2 循环测试活条件
EXP3 计算表达式
7.2 while 循环 7.2.1 while 循环语法 1
2
3
4
5
6
7
while [ Condition ]
do
Command1
Command2
...
CommandN
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
#=====================================
# FILE: killsignal.sh
# USAGE: ./killsignal.sh
# DESCRIPTION: 简单的使用while循环
# AUTHOR: benny
# CREATED: 10/24/2013
#==========================
var=1
while [ $var -lt 3 ]
do
echo "the for loop is run $var times"
var=$((var+1))
done
7.2.2 无限while循环
定义一个无限while循环可以使用如下3种命令:
true 命令 —— 不做任何事,表示成功,返回退出状态码0
语法格式:
1
2
3
4
5
6
7
8
9
10
while :
do
echo "..."
done
# 或
while true
do
echo "..."
done
# 或
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
while :
do
clear # 清理终端屏幕
echo "============="
echo " MAIN - MENU "
echo "============="
read -p "Enter your choice[ 1 - 4 ]:" choice # 从标准输入中读取用户的输入,并赋值给变量choice
case $choice in
1)
echo "Today is $(date + %Y-%m-%d)"
;;
2)
uname -a # 打印系统信息
;;
3)
w
read -p "Press [Enter] key to continue..." readEnterKey
;;
4)
echo "buy"
exit 0
;;
*)
echo "Error : Invalid option"
read -p "Presdss key to continue...." readEnterKey
;;
exac
7.3 until 循环语句实例
until循环与while循环类似,也同样基于一个条件,但until循环的判断条件正好与while循环的判断条件相反,until循环在条件为假的条件下才会持续的运行 。一旦条件被满足,即为真,就会退出循环。
语法如下:
1
2
3
4
5
6
7
until [ COMMANDS ]
do
command1
command2
...
commandN
done
until循环与while循环相比:
until 循环执行直到返回0状态
while 循环执行直到返回非0状态
until循环总是执行至少一次
7.4 select 循环语句
1
2
3
4
5
6
7
select VAR in LIST
do
command1
command2
...
commandN
done
select循环有以下特点
select语句使用Bash内部变量PS3的值作为它的提示符之一
打印到屏幕上的列表LIST中的每一项会在前面加上一个数字编号
当用户输入的数字与某一个数字编号一致时,列表中的相应的项即被赋予变量VAR
如果用户输入的内容为空,将重新显示列表LIST中的项和提示符信息
可以通过添加一个退出选项,或按CTRL+C 或 CTRL+D组合键退出select循环
脚本实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
PS3="Run command:"
select choice in date w hostname "uname -a " Exit //指定select循环列表
do
case $choice in
date)
echo "=============="
echo "Current System date and time:"
ehco "================"
;;
w)
echo "=============="
echo "who is log on:"
ehco "================"
;;
hostname)
echo "=============="
echo "Hostname :"
ehco "================"
;;
"uname -a")
echo "=============="
echo "System information"
ehco "================"
;;
Exit)
echo "Bye!"
exit 0
;;
esac
done
7.5 循环控制 7.5.1 break 语句
break 语句用于从for、while、until、select循环中退出、停止循环的执行
语法如下:
n 代表嵌套循环的层级,如果指定了n,break将推出n级嵌套循环。
如果没有指定n或n不大于等于1,则退出状态码为0,否则退出状态码为n
此实例需要查看
7.5.2 continue 语句
continue 语句用于跳过循环体中剩余的命令直接跳转到循环体的顶部,而重新开始循环的下一次重复。
continue语句可以应用于for、while或until循环
语法如下:
第8章 Shell函数 8.1 函数的定义
1
2
3
4
5
6
7
8
9
# 函数名
function_name(){
# 函数体 ,在函数中执行的命令行
commands...
# 参数返回,return语句是可选的
#如果没有return语句,则以函数最后一条命令的运行结果作为返回值
#如果使用return语句,则return后跟数值n(数值范围:0~255)
[ return ini; ]
}
或者如果你愿意,可以在函数名字前面加上关键字function
1
2
3
function function_name(){
commands...
}
1
2
3
4
5
function name { commands1; commands2;commandsN; }
或者
name() { commands1; commands2;commandsN; }
可以使用内部命令unset
的’-f’选线过来取消函数的定义
通常情况下,函数体外的大括号与函数体之间必须用空白符(空格、回车或制表符等)换行符分开,因为大括号{}是保留字,但只有{
或}
与其中间的命令列表呗加空格或者其它Shell元字符(比如 , 或; 或|等)分隔时,才能被识别为保留字
8.2 函数的参数,变量与返回值 8.2.1 向函数传递参数
Shell函数有自己的命令行参数,函数使用特殊变量$1,$2,…,$n来范文传递给它的参数
1
2
3
4
5
6
name(){
arg1=$1
arg1=$2
arg1=$3
command on $arg1
}
上面的函数中
name = 函数名
foo = 参数1 传递给函数的第一个参数(位置参数$1)
bar = 参数2 传递给函数的第二个参数(位置参数$2)
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/bin/bash
#=====================================
# FILE: passed.sh
# USAGE: ./passed.sh
# DESCRIPTION:
# AUTHOR: benny
# CREATED: 10/24/2013
#==========================
# 定义函数passed
passwed(){
# 定义变量a,将此传递给函数passed()的第一个参数赋值给此变量
a=$1
# 打印特殊函数0的值,即脚本名称
echo "passed(): \$0 is $0 "
# 打印位置参数1的值,即指定给函数的第一个参数
echo "passed() : \$1 is $1"
echo "passed() : \$a is $a"
# 打印传递给函数passed的参数个数
echo "passed() : total args is $#"
# 打印传递给函数passed()的所有函数
echo "passed() : total args is $@"
# 打印传递给函数passed()的所有函数
echo "passed() : total args is $*"
}
echo "**** calling passed() first time *****"
# 调用函数passwd()并指定一个参数'one'
passed one
echo "**** calling passed() second time *****"
passed one two three
out:
**** calling passed() first time *****
passed(): $0 is ./passed.sh
passed() : $1 is one
passed() : $a is one
passed() : total args is 1
passed() : total args is one
passed() : total args is one
**** calling passed() second time *****
passed(): $0 is ./passed.sh
passed() : $1 is one
passed() : $a is one
passed() : total args is 3
passed() : total args is one two three
passed() : total args is one two three
在Shell函数中:
所有函数参数都可以通过$1,$2,…,$N来访问
$0
指代Shell脚本的名字
$*
$@
保存传递给函数所有的参数
$#
保存村递给函数的位置参数的个数
8.2.2 本地变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/bin/bash
#=====================================
# FILE: fvar.sh
# USAGE: ./fvar.sh
# DESCRIPTION: 本地变量
# AUTHOR: benny
# CREATED: 10/24/2013
#==========================
# 定义函数create_logFile
create_logFile(){
#修改变量d的名字
d=$1
echo "create_logFile : d is set to $d"
}
# 定义变量d
d=/tmp/diskUsage.log
echo "Before calling create_logFile d is set to $d"
# 调用函数create_logFile并指定一个参数
create_logFile "/home /benny/diskUsage.log"
echo "After calling create_logFile d is set to $d"
out:
[root@iZ251k7t70aZ myscript]# ./fvar.sh
Before calling create_logFile d is set to /tmp/diskUsage.log
create_logFile : d is set to /home /benny/diskUsage.log
After calling create_logFile d is set to /home /benny/diskUsage.log
通常情况下,我们可以使用local命令来创建一个本地变量
语法
1
2
3
4
5
6
7
8
9
10
11
12
local var=value
local varName
或者
function name(){
# 定义一个本独变量var
local var=$1
command1 on $var
}
local命令只能在函数内部使用
local命令将变量名的可见范围控制在函数内部
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
# 定义全变量d
d=/tmp/diskUsage.log
#定义函数create_logFile
function create_logFile(){
# 定义本地变量,这个变量制度及此函数可见
local d=$1
echo "create_logFIle(): d is set to $d"
}
echo "Before caling create_logFIle d is set to $d"
# 调用函数create_logFile() 并指定一个参数
create_logFile "/home/benny/diskUsage.log"
echo "After calling create_log_file() d is set to $d"
~
out:
[root@iZ251k7t70aZ myscript]# ./localfvar.sh
Before caling create_logFIle d is set to /tmp/diskUsage.log
create_logFIle(): d is set to /home/benny/diskUsage.log
After calling create_log_file() d is set to /tmp/diskUsage.log
8.2.3 使用return
命令
如果函数理由Shell
内置命令return
,则函数执行到return
语句时结束,并且返回到Shell脚本中调用函数位置的下一个命令。
如果return带有一个数值型参数,则这个参数就是函数的返回值,返回值的最大值是255
;否则函数的返回值就是函数体内最后一个执行的命令的返回状态
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[root@iZ251k7t70aZ myscript]# vi checkpid.sh
#!/bin/bash
checkpid(){
# 定义本地变量
local i
# 使用for循环遍历传递给此函数的所有参数
for i in $*
do
# 如果目录/proc/$i存在,则执行此函数返回
# 在一般的Linux系统中,如果进程正在运行,则在/proc目录下会存在一个以进程号命名的子目录
[ -d "/proc/$i" ] && return 0
done
# 返回1
return 1
}
checkpid $pid1 $pis2 $pid3
if [ $? == 0 ]
then
echo "the one of them is running"
else
echo "These Pids are not running"
fi
out:
[root@iZ251k7t70aZ myscript]# ./checkpid.sh
These Pids are not running
上述中的if
判断语句也可以改成,因为返回值是0
或1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
checkpid $pid1 $pis2 $pid3
if [ $? == 0 ]
then
echo "the one of them is running"
else
echo "These Pids are not running"
fi
改成
if (checkpid $pid1 $pis2 $pid3) ; then
echo "the one of them is running"
else
echo "These Pids are not running"
fi
8.3 函数的调用 8.3.1 在Shell命令行调用函数
在命令行中,可以通过直接输入函数的名字,来调用或引用函数
1
[root@iZ251k7t70aZ myscript]# yDay() { date --date='1 day ago'; }
1
2
[root@iZ251k7t70aZ myscript]# yDay
Wed Jul 13 15:09:44 CST 2016
8.3.2 在脚本中调用函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash
# 定义变量TEST
TEST=" /tmp/fileName"
# 调用delete_file; 失败
delete_file
#定义函数delete_file
delete_file(){
echo "Deleting file...."
out:
[root@iZ251k7t70aZ myscript]# ./dfile.sh
./dfile.sh: line 7: delete_file: command not found
}
出错的原因是因为脚本的执行顺序是从上而下运行
调用了未定义的方法
为了避免出现这样的问题,要在脚本的开头定义和编写函数
改写一下上面的脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# 定义函数delete_file
function delete_file(){
echo "Delteting ...."
}
# 定义变量TEST
TEST=" /tmp/fileName"
# 调用delete_file;
delete_file
8.3.3 从函数文件中调用函数
你可以把所有的函数存储到一个文件中
你可以把所有订单而函数加载到当前脚本或者是命令行
加载函数文件中所有函数的语法如下
1
2
3
4
5
$ . /path/to/your/functions.sh
或者
$ . functions.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/bash
#====================
# FILE: functions.sh
# USAGE:./functions.sh
# DESCRIPTION:
#====================
# 定义变量
declare -r TRUE=0
declare -r FLASE=1
declare -r PASSWD_FILE=/etc/passwd
################################
#用途: 将字符串转换成小写
#参数:
# $1 -> 要转换为小写的字符串
################################
function to_lower(){
# 定义本地变量str
local str="$@"
# 定义本地变量output
local output
# 将变量str的值转换为小写后赋值给变量output
output=$(tr '[A_Z]' '[a-z]'<<<"{str}")
echo $output
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[root@iZ251k7t70aZ myscript]# vi functiondemo.sh
#!/bin/bash
#============================
# FILE: functionDemo.sh
# AUTHOR: benny
# DESCRIPTION: 加载函数文件到脚本
# USAGE: ./funcionDemo.sh
#============================
# 加载函数文件 functions.sh
# 这里的路径需要根据你的手机环境做改动
. /usr/local/myscript/functions.sh
# 定义本地变量
# var1 是没有被function.sh使用个
var1="The manabharata is the longst and , arguably, one of the greatest epicpoems in any language"
# 调用函数is_root 执行成功或失败,会分别打印不同的信息
is_root && echo "you are logged in as root" || echo "you are not logged in as root"
# 调用函数is_user_exists
is_user_exists "benny" && e "Account found" || echo "Account not found"
# 打印变量的值
echo -e "*** orignal quote : \n${var1}"
# 调用函数to_lower()
# 将$var1 作为参数传递给to_lower()
# 将echo内使用命令替换
echo -e "*** Lowercase version : \n${to_lower $(var1)}"
8.3.4 递归函数调用
递归函数是重复调用其自身的函数,并且没有递归调用次数的限制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash
factorial(){
# 定义本地变量
local i=$1
# 定义本地变量f
local f
# 声明变量i为整数
declare -i f
# factorial被调用直到$f的值<=2
# 开始递归
[ $i -le 2 ] && echo $i || {f=$((i-1));f=$(factorial $f);f=$(( f * i ));echo $f; }
}
# 显示函数的用户
[ $# -eq 0 ] && { echo "Usage : $0 number ";exit 1; }
# 调用函数factorial
factorial $1
在Bash下,递归函数执行速度慢,应尽可能避免使用递归函数。
将函数放在后台运行
&
操作符可以将命令放在后台运行并释放你的终端,同样也可以将函数放在后台运行
语法规则
1
2
3
4
5
6
7
8
9
10
11
12
# 定义函数name
name(){
echo "Do something"
sleep 1
}
# 将函数放在后台运行
name &
# 继续执行其他命令
commmands...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/bin/bash
# prigress.sh 当进行备份时显示进度
# 定义函数progress
progress(){
echo -n "$0: please wait ...."
while true
do
echo -n "."
# 休眠5秒
sleep 5
done
}
# 定义函数dobackup
dobackup(){
# 运行备份命令
tar zcvf /dev/st0 /home >/dev/null 2>&1
}
# 将函数放在后台运行
progress &
# 保存函数 progress() 运行的进程号
# 需要使用PID来结束此函数
MYSELF=$!
echo "$MYSELF"
# 开始备份
# 转移控制到函数dobackup
dobackup
# 杀死进程
kill $MYSELF >/dev/null 2>&1
echo -n "...done"
echo
shell中可能经常能看到:>/dev/null 2>&1
命令的结果可以通过%>
的形式来定义输出
/dev/null
代表空设备文件
>
代表重定向到哪里,例如:echo "123" > /home/123.txt
1
表示stdout
标准输出,系统默认值是1
,所以>/dev/null
等同于1>/dev/null
1
表示stderr
标准错误
&
表示等同于的意思,2>&1
,表示2
的输出重定向等同于1
1>/dev/null
首先表示标准输出重定向到空设备文件,也就是不输出任何信息到终端,说白了就是不显示任何信息。
2>&1
接着,标准错误输出重定向等同于标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件。