本文章内容来自与“的Linux的命令行与外壳脚本编程大全。第3版”
目录
Linux命令. 1
进程相关. 5
Ps命令. 5
TOP命令. 6
Kill命令. 6
Type(查看命令的命令,which). 7
History历史命令. 7
监测磁盘. 7
Mount挂载. 7
Umount卸载. 8
Df命令. 8
Du命令. 8
文件内容处理命令. 8
Sort排序. 8
grep搜索. 9
Tar归档(压缩和解压). 9
wc命令. 9
系统命令. 10
Alias命名其他命令别名. 10
环境变量相关命令. 10
Export导出环境变量. 10
Unset删除环境变量. 10
系统默认变量(例UID). 10
系统用户相关. 12
useradd添加用户. 12
userdel删除用户. 13
chpasswd修改用户密码. 13
文件权限. 13
Linux文件权限码. 13
默认文件权限. 13
改变权限chmod 14
Readlink命令. 14
软件包管理. 15
基于Red Hat 的系统. 15
Yum安装软件包. 15
yum更新软件包. 15
yum卸载软件包. 16
处理损坏的包依赖关系. 16
Yum仓库. 16
vim编辑器命令. 16
编辑命令. 16
查找和替换. 17
Shell脚本常用命令. 17
变量赋值. 17
变量子串的常用操作(提取。。。). 18
变量替换表(空值给默认值). 18
Echo命令. 19
数学运算命令. 19
Expr命令. 19
方括号计算. 20
bc浮点数计算. 20
Linux退出状态码. 21
If语句. 22
数值比较. 22
字符串比较. 23
文件比较. 23
if语句的其他使用. 23
双括号. 23
双方括号. 24
Case语句. 24
for循环语句. 25
更改字段分隔符. 25
C 语言风格的for 命令. 27
while循环语句. 28
使用多个测试命令. 28
until 命令. 29
循环处理文件数据. 30
控制循环. 31
break 命令. 31
continue 命令. 31
处理循环的输出. 31
处理用户输入($0,$1-$9). 32
读取脚本名($0)basename用法. 32
参数统计($#). 33
抓取所有的数据($*和$@) 34
移动变量(shift). 34
处理选项. 36
选项标准化. 41
获得用户输入read 42
read超时. 42
限制read输入字符. 43
read从文件中读取. 44
呈现数据. 44
理解输入和输出. 44
重定向. 45
文件描述符. 45
使用和恢复文件描述符. 45
关闭文件描述符. 46
列出打开的文件描述符. 47
创建临时文件和文件夹. 47
信号. 49
捕获信号. 49
捕获脚本退出. 50
修改或移除捕获. 51
删除捕获. 52
nohup命令(脱离会话终端) 53
查看作业号(jobs) 53
重启被暂停的作业. 53
调整谦让度. 54
nice 命令. 54
renice命令. 54
定时任务. 55
构建cron时间表. 55
函数. 56
return 56
使用函数输出. 56
在函数中处理变量. 57
全局变量. 57
局部变量. 58
向函数传数组参数. 58
创建函数库和调用. 59
在命令行上创建函数. 61
.bashrc文件. 61
函数库实例. 61
菜单. 62
创建菜单布局. 62
sed和gawk 63
sed基本用法. 63
sed从文件中读取编辑器命令. 63
gawk基本用法. 64
取一行中的某个字段. 64
指定字段分隔符. 65
echo和gawk合用. 65
gawk从文件中读取编辑器命令. 65
在处理数据前执行gawk脚本(比如为报告创建标题). 66
实例. 66
一, 创建多个用户(使用.csv文件). 66
二, 查找可执行文件. 67
参数 |
描述 |
-e |
显示所有进程 |
-U userlist |
显示属主的用户ID在userlist列表中的进程 |
-F |
显示更多额外输出(相对-f参数而言) |
-l |
显示长列表 |
基本输出显示列
加-l增加列
第一行显示了当前时间、系统的运行时间、登录的用
户数以及系统的平均负载。
平均负载有3个值:最近1分钟的、最近5分钟的和最近15分钟的平均负载。值越大说明系统
的负载越高。由于进程短期的突发性活动,出现最近1分钟的高负载值也很常见,但如果近15分
钟内的平均负载都很高,就说明系统可能有问题。通常,如果系统的负载值超过了2,就说明系统比较繁忙了。
第二行显示了进程概要信息——top命令的输出中将进程叫作任务(task):有多少进程处在
运行、休眠、停止或是僵化状态(僵化状态是指进程完成了,但父进程没有响应)。
下一行显示了CPU的概要信息。top根据进程的属主(用户还是系统)和进程的状态(运行、
空闲还是等待)将CPU利用率分成几类输出。
最后一部分显示了当前运行中的进程的详细列表,有些列跟ps命令的输出类似。
键入f允许你选择对输出进行排序的字段,键入d允许你修改轮询间隔。键入q可以退出top。
Kill命令可以终结进程
使用方法:
Kill PID
还可以加-s 参数指定其他信号。
Killall它支持通过进程名,也支持通配符
使用方法:
Killall http*
会终结所有以http开头的进程
# type cd
cd is a shell builtin
#
# type exit
exit is a shell builtin
要注意,有些命令有多种实现。例如echo和pwd既有内建命令也有外部命令。两种实现略有
不同。要查看命令的不同实现,使用type命令的-a选项。
$ type -a echo
echo is a shell builtin
echo is /bin/echo
$
$ which echo
/bin/echo
$
$ type -a pwd
pwd is a shell builtin
pwd is /bin/pwd
$
$ which pwd
/bin/pwd
$
命令type -a显示出了每个命令的两种实现。注意,which命令只显示出了外部命令文件。
想强制重新读取.bash_history文件,更新终端会话的历史记录,可以使用history -n命令
默认情况下,mount命令会输出当前系统上挂载的设备列表。
mount命令提供如下四部分信息:
手动挂载媒体设备的基本命令:
mount -t type device directory
type参数指定了磁盘被格式化的文件系统类型。
参数 |
描述 |
-f |
使mount命令模拟挂载设备,但并不真的挂载 |
umount命令支持通过设备文件或者是挂载点来指定要卸载的设备。如果有任何程序正在使
用设备上的文件,系统就不会允许你卸载它
df命令会显示每个有数据的已挂载文件系统。如你在前例中看到的,有些已挂载设备仅限系
统内部使用。命令输出如下:
参数 |
描述 |
-h |
会把输出中的磁盘空间按照用户易读的形式显示,通常用M来替代兆字节,用G替代吉字节 |
du命令可以显示某个特定目录(默认情况下是当前目录)的磁盘使用情况
参数 |
描述 |
-h |
会把输出中的磁盘空间按照用户易读的形式显示,通常用M来替代兆字节,用G替代吉字节 |
-c |
显示所有已列出文件总的大小。 |
Sort不加参数排序会把所有当作字符串排序。
参数 |
描述 |
-n |
按字符串数值来排序(并不转换为浮点数) |
-M |
按月排序, sort命令就能识别三字符的月份名,并相应地排序。 |
-b |
排序时忽略起始的空白 |
-r |
反序排序(升序变成降序) |
-z |
用NULL字符作为行尾,而不是用换行符 |
-d |
仅考虑空白和字母,不考虑特殊字符 |
-t |
指定一个用来区分键位置的字符 |
-k POS1[,POS2] |
排序从POS1位置开始;如果指定了POS2的话,到POS2位置结 束 |
-k和-t参数在对按字段分隔的数据进行排序时非常有用,
sort -t ':' -k 3 -n /etc/passwd
du -sh * | sort –nr
参数 |
描述 |
-v |
反向搜索,(输出不匹配该模式的行) |
-n |
显示匹配模式的行所在的行号 |
-c |
要知道有多少行含有匹配的模式 |
-e |
可以使用正则表达式进行匹配搜索 |
参数 |
描述 |
-c |
创建一个新的归档文件 |
-A |
将一个已有tar归档文件追加到另一个已有tar归档文件,嵌套tar文件 |
-r |
追加文件到已有tar归档文件末尾 |
-x |
从已有tar归档文件中提取文件 |
-f |
输出结果到文件或设备file, |
-v |
在处理文件时显示文件 |
wc命令可以对对数据中的文本进行计数。默认情况下,它会输出3个值:
Wc –l 只取行数
方法1、paste -d "\t" eng.txt chi.txt
semicolon 分号
comma 逗号
delimiter 定界符
spacebar 空格键
hyphen 连字符号
single quote 单引号
double quote 双引号
方法2、或者使用awk来处理
awk 'NR==FNR{a[i]=$0;i++}NR>FNR{print a[j]" "$0;j++}' eng.txt chi.txt
semicolon 分号
comma 逗号
delimiter 定界符
spacebar 空格键
hyphen 连字符号
single quote 单引号
double quote 双引号
hash 井号
命令别名允许你为常用的命令(及其参数)创建另一个名称
用法:alias li='ls -li'
通过alisa –p查看系统中所有的别名
设置全局环境变量先创建一个局部环境变量,然后再把它导出到全局环境中
这个过程通过export命令来完成,变量名前面不需要加$
例如export my_variable
注意:修改子shell中全局环境变量并不会影响到父shell中该变量的值
在unset命令中引用环境变量时,记住不要使用$。
例如 Unset my_variable
变 量 描 述
CDPATH 冒号分隔的目录列表,作为cd命令的搜索路径
HOME 当前用户的主目录
IFS shell用来将文本字符串分割成字段的一系列字符
MAIL 当前用户收件箱的文件名(bash shell会检查这个文件,看看有没有新邮件)
MAILPATH 冒号分隔的当前用户收件箱的文件名列表(bash shell会检查列表中的每个文件,看看有没有新邮件)
OPTARG getopts命令处理的最后一个选项参数值
OPTIND getopts命令处理的最后一个选项参数的索引号
PATH shell查找命令的目录列表,由冒号分隔
PS1 shell命令行界面的主提示符
PS2 shell命令行界面的次提示符
BASH 当前shell实例的全路径名
BASH_ALIASES 含有当前已设置别名的关联数组
BASH_ARGC 含有传入子函数或shell脚本的参数总数的数组变量
BASH_ARCV 含有传入子函数或shell脚本的参数的数组变量
BASH_CMDS 关联数组,包含shell执行过的命令的所在位置
BASH_COMMAND shell正在执行的命令或马上就执行的命令
BASH_ENV 设置了的话,每个bash脚本会在运行前先尝试运行该变量定义的启动文件
BASH_EXECUTION_STRING 使用bash -c选项传递过来的命令
BASH_LINENO 含有当前执行的shell函数的源代码行号的数组变量
BASH_REMATCH 只读数组,在使用正则表达式的比较运算符=~进行肯定匹配(positive match)时,包含了匹配到的模式和子模式
BASH_SOURCE 含有当前正在执行的shell函数所在源文件名的数组变量
BASH_SUBSHELL 当前子shell环境的嵌套级别(初始值是0)
BASH_VERSINFO 含有当前运行的bash shell的主版本号和次版本号的数组变量
BASH_VERSION 当前运行的bash shell的版本号
BASH_XTRACEFD 若设置成了有效的文件描述符(0、1、2),则'set -x'调试选项生成的跟踪输出可被重定向。通常用来将跟踪输出到一个文件中
BASHOPTS 当前启用的bash shell选项的列表
BASHPID 当前bash进程的PID
COLUMNS 当前bash shell实例所用终端的宽度
COMP_CWORD COMP_WORDS变量的索引值,后者含有当前光标的位置
COMP_LINE 当前命令行
COMP_POINT 当前光标位置相对于当前命令起始的索引
COMP_KEY 用来调用shell函数补全功能的最后一个键
COMP_TYPE 一个整数值,表示所尝试的补全类型,用以完成shell函数补全
COMP_WORDBREAKS Readline库中用于单词补全的词分隔字符
COMP_WORDS 含有当前命令行所有单词的数组变量
COMPREPLY 含有由shell函数生成的可能填充代码的数组变量
COPROC 占用未命名的协进程的I/O文件描述符的数组变量
DIRSTACK 含有目录栈当前内容的数组变量
EMACS 设置为't'时,表明emacs shell缓冲区正在工作,而行编辑功能被禁止
ENV 如果设置了该环境变量,在bash shell脚本运行之前会先执行已定义的启动文件(仅
用于当bash shell以POSIX模式被调用时)
EUID 当前用户的有效用户ID(数字形式)
FCEDIT 供fc命令使用的默认编辑器
FIGNORE 在进行文件名补全时可以忽略后缀名列表,由冒号分隔
FUNCNAME 当前执行的shell函数的名称
FUNCNEST 当设置成非零值时,表示所允许的最大函数嵌套级数(一旦超出,当前命令即被终止)
GLOBIGNORE 冒号分隔的模式列表,定义了在进行文件名扩展时可以忽略的一组文件名
GROUPS 含有当前用户属组列表的数组变量
histchars 控制历史记录扩展,最多可有3个字符
HISTCMD 当前命令在历史记录中的编号
HISTCONTROL 控制哪些命令留在历史记录列表中
HISTFILE 保存shell历史记录列表的文件名(默认是.bash_history)
HISTFILESIZE 最多在历史文件中存多少行
HISTTIMEFORMAT 如果设置了且非空,就用作格式化字符串,以显示bash历史中每条命令的时间戳
HISTIGNORE 由冒号分隔的模式列表,用来决定历史文件中哪些命令会被忽略
HISTSIZE 最多在历史文件中存多少条命令
HOSTFILE shell在补全主机名时读取的文件名称
HOSTNAME 当前主机的名称
HOSTTYPE 当前运行bash shell的机器
IGNOREEOF shell在退出前必须收到连续的EOF字符的数量(如果这个值不存在,默认是1)
INPUTRC Readline初始化文件名(默认是.inputrc)
LANG shell的语言环境类别
LC_ALL 定义了一个语言环境类别,能够覆盖LANG变量
LC_COLLATE 设置对字符串排序时用的排序规则
LC_CTYPE 决定如何解释出现在文件名扩展和模式匹配中的字符
LC_MESSAGES 在解释前面带有$的双引号字符串时,该环境变量决定了所采用的语言环境设置
LC_NUMERIC 决定着格式化数字时采用的语言环境设置
LINENO 当前执行的脚本的行号
LINES 定义了终端上可见的行数
MACHTYPE 用“CPU公司系统”(CPU-company-system)格式定义的系统类型
MAPFILE 一个数组变量,当mapfile命令未指定数组变量作为参数时,它存储了mapfile所读入的文本
MAILCHECK shell查看新邮件的频率(以秒为单位,默认值是60)
OLDPWD shell之前的工作目录
OPTERR 设置为1时,bash shell会显示getopts命令产生的错误
OSTYPE 定义了shell所在的操作系统
PIPESTATUS 含有前台进程的退出状态列表的数组变量
POSIXLY_CORRECT 设置了的话,bash会以POSIX模式启动
PPID bash shell父进程的PID
PROMPT_COMMAND 设置了的话,在命令行主提示符显示之前会执行这条命令
PROMPT_DIRTRIM 用来定义当启用了\w或\W提示符字符串转义时显示的尾部目录名的数量。被删除的目录名会用一组英文句点替换
PS3 select命令的提示符
参数 |
描述 |
-c |
给用户添加新备注 |
-d |
为主目录指定一个名字(如果不想用登录名作为主目录名的话) |
-e |
用YYYY-MM-DD格式指定一个账户过期的日期 |
-f |
指定这个账户密码过期后多少天这个账户被禁用;0表示密码一过期就立即禁用,1表示禁用这个功能 |
-r |
创建系统账户 |
-p |
为用户账户指定默认密码 |
-m |
创建用户的HOME目录 |
例如useradd oldboy 添加一个oldboy用户
useradd -D -e 2018-09-10 -f 0 oldboy
默认情况下,userdel命令会只删除/etc/passwd文件中的用户信息,而不会删除系统中属于该账户的任何文件。
如果加上-r参数,userdel会删除用户的HOME目录以及邮件目录。然而,系统上仍可能存
有已删除用户的其他文件。这在有些环境中会造成问题。
userdel -r oldboy
chpasswd命令能从标准输入自动读取登录名和密码对(由冒号分割)列表,给密码加密,然后为用户账户设置。你也可以用重定向命令来将含有userid:passwd对的文件重定向给该命令。
# chpasswd < users.txt
passwd命令用法:
passwd 用户名
权限 |
二进制值 |
八进制值 |
描述 |
--- |
000 |
0 |
没有任何权限 |
--x |
001 |
1 |
只有执行权限 |
-w- |
010 |
2 |
只有写入权限 |
-wx |
011 |
3 |
有写入和执行权限 |
r-- |
100 |
4 |
只有读取权限 |
r-x |
101 |
5 |
有读取和执行权限 |
rw- |
110 |
6 |
有读取和写入权限 |
rwx |
111 |
7 |
有全部权限 |
umask命令用来设置所创建文件和目录的默认权限。
umask命令可以显示和设置这个默认权限。
$ umask
0022
$
要把umask值从对象的全权限值中减掉。对文件来说,全权限的值是666(所有用户都有读和写的权限);而对目录来说,则是777(所有用户都有读、写、执行权限)。
可以用umask命令为默认umask设置指定一个新值。
$ umask 026
$ touch newfile2
$ ls -l newfile2
-rw-r----- 1 rich rich 0 Sep 20 19:46 newfile2
$
命令的格式如下:
chmod options mode file
chmod [ugoa…][[+-=][rwxXstugo…]
后面跟着的符号表示你是想在现有权限基础上增加权限(+),还是在现有权限基础上移除权限(),或是将权限设置成后面的值(=)。
例如:
$ chmod o+r newfile
$ ls -lF newfile
-rwxrw-r-- 1 rich rich 0 Sep 20 19:16 newfile*
$
不管其他用户在这一安全级别之前都有什么权限,o+r都给这一级别添加读取权限。
$ chmod u-x newfile
$ ls -lF newfile
-rw-rw-r-- 1 rich rich 0 Sep 20 19:16 newfile
$
u-x移除了属主已有的执行权限。注意ls命令的-F选项,它能够在具有执行权限的文件名后加一个星号。
使用readlink –f命令能够立刻找出链接文件的最后一环。
要找出系统上已安装的包,可在shell提示符下输入如下命令:
yum list installed
如何用zypper和urpm列出已安装软件
版 本 前端工具 命 令
Mandriva urpm rpm -qa > installed_software
openSUSE zypper zypper search -I > installed_software
yum擅长找出某个特定软件包的详细信息。它能给出关于包的非常详尽的描述,另外你还可
以通过一条简单的命令查看包是否已安装。
# yum list xterm
Loaded plugins: langpacks, presto, refresh-packagekit
Adding en_US to language list
Available Packages
xterm.i686 253-1.el6
#
# yum list installed xterm
Loaded plugins: refresh-packagekit
Error: No matching Packages to list
#
如何用zypper和urpm查看各种包详细信息
信息类型 前端工具 命 令
包信息 urpm urpmq -i package_name
是否安装 urpm rpm -q package_name
包信息 zypper zypper search -s package_name
是否安装 zypper 同样的命令,注意在Status列查找i
Yum安装软件包基本命令
yum install package_name
手动下载rpm安装文件并用yum安装,这叫作本地安装。基本的命令是:
yum localinstall package_name.rpm
要列出所有已安装包的可用更新,输入如下命令:
yum list updates
如果这个命令没有输出就太好了,因为它说明你没有任何需要更新的!但如果发现某个特定
软件包需要更新,输入如下命令:
yum update package_name
如果想对更新列表中的所有包进行更新,只要输入如下命令:
yum update
只删除软件包而保留配置文件和数据文件,就用如下命令:
yum remove package_name
要删除软件和它所有的文件,就用erase选项:
yum erase package_name
有时在安装多个软件包时,某个包的软件依赖关系可能会被另一个包的安装覆盖掉。这叫作损坏的包依赖关系(broken dependency)。
如果系统出现了这个问题,先试试下面的命令:
yum clean all
然后试着用yum命令的update选项。有时,只要清理了放错位置的文件就可以了。
如果这还解决不了问题,试试下面的命令:
yum deplist package_name
如果这样仍未解决问题,还有最后一招:
yum update --skip-broken
--skip-broken选项允许你忽略依赖关系损坏的那个包,继续去更新其他软件包。
要想知道你现在正从哪些仓库中获取软件,输入如下命令:
yum repolist
如果仓库中没有需要的软件,你可以编辑一下配置文件。yum的仓库定义文件位于
/etc/yum.repos.d。你需要添加正确的URL,并获得必要的加密密钥。
用make命令来构建各种二进制文件。make命令会编译源码
在大的文本文件中一行一行地来回移动会特别麻烦,幸而vim提供了一些能够提高移动速度
的命令。
普通模式下vim编辑器命令
命 令 描 述
x 删除当前光标所在位置的字符
dd 删除当前光标所在行
dw 删除当前光标所在位置的单词
d$ 删除当前光标所在位置至行尾的内容
J 删除当前光标所在行行尾的换行符(拼接行)
u 撤销前一编辑命令
a 在当前光标后追加数据
A 在当前光标所在行行尾追加数据
r char 用char替换当前光标所在位置的单个字符
R text 用text覆盖当前光标所在位置的数据,直到按下ESC键
替换命令的格式是:
:s/old/new/
vim编辑器会跳到old第一次出现的地方,并用new来替换。可以对替换命令作一些修改来替
换多处文本。
在命令行中只能给变量赋一个值。
[root@localhost testing]# a=1
如果给多个会提示错误
[root@localhost testing]# age=1 2 3 3
-bash: 2: command not found
如果想给多个,可以通过命令结果赋值给变量的方式
[root@localhost testing]# f=(1 2 3 4 5)
[root@localhost testing]# arg=$(echo ${f[@]})
[root@localhost testing]# echo $arg
1 2 3 4 5
表达式 |
说明 |
${#string} |
返回$string的长度 |
${string:position} |
在$string中,从位置$position之后开始提取子串,不包含$psotion位置的字符 |
${string:position:length} |
在$string中,从位置$position之后开始提取长度为$length的子串 |
子串删除 |
|
${string#substring} |
从变量$string开头开始删除最短匹配$substring子串 |
${string##substring} |
从变量$string开头开始删除最长匹配$substring子串 |
${string%substring} |
从变量$string结尾开始删除最短匹配$substring子串 |
${string%%substring} |
从变量$string结尾开始删除最长匹配$substring子串 |
子串替换 |
|
${string/substring/replace} |
使用$replace,来替换第一个匹配的$substring |
${string/#substring/replace} |
如果$string前缀匹配$substring,就用$replace来代替匹配$substring |
${string/%substring/replace} |
如果$stringho后缀匹配$substring,就用$replace来代替匹配$substring |
如何使用子串替换批量修改文件后缀名。
方法:
for file in `ls *.jpg`;do mv ${file/%jpg/JPG};done
如何使用子串替换批量修改文件名。
for file in `ls *.jpg`:do mv $file `echo ${file%.jpg}|tr “[a-z]” “[A-Z]”`:done
ls *.jpg|awk -F “finished” ‘{print “mv” $0,$1””$2}’|bash(使用awk语言)
rename “finnshed” “” *.Jpg
rename语法:
rename “from” “to” “file”
rename “要改的” “改成的” 对哪些文件操作
例如:rename “.HTML” “.html” *.HTML
预算符号 |
替换 |
${value:-word} |
如果变量名存在且非null,则返回变量的值。否则,返回word字符串。 用途:如果变量未定义,则返回默认值 例:${value:-word},如果value没有定义,则表达式的值为word, 即echo $value word |
${value:=word} |
如果变量名存在且非null,则返回变量的值。否则,设置这个变量值为word,并返回其值。 用途:如果变量未定义,则设置变量为默认值,并返回默认值。 例:${value:-word},如果value没有定义,则设置value值为word,返回表达式的值也为word。 即echo $value word |
${value:?”not define”} |
如果变量名存在且非null,则返回变量的值。否则,显示变量名:message,并退出当前的命令或者脚本。 用途:用于捕捉由于变量未定义而导致的错误,并退出程序。 例:${value:?”not define”}如果value未定义,则显示-bash:value:not define并退出。 |
${value:+word} |
如果变量名存在且非null,则返回word。否则返回null。 用途:测试变量是否存在。 例:${value:+word}如果value已经定义,返回word(也就是真) |
如果想把文本字符串和命令输出显示在同一行中,可以用echo语句的-n参数。命令输出将会在紧接着字符串结束的地方出现。
#vim test1
#!/bin/bash
echo -n "The time and date are: "
date
# ./test1
The time and date are: Mon Feb 21 15:42:23 EST 2014
使用方法:
$ expr 1 + 5
6
$ expr 5 \* 2 #注意接转义符
10
expr命令操作符
操 作 符 描 述
ARG1 | ARG2 如果ARG1既不是null也不是零值,返回ARG1;否则返回ARG2
ARG1 & ARG2 如果没有参数是null或零值,返回ARG1;否则返回0
ARG1 < ARG2 如果ARG1小于ARG2,返回1;否则返回0
ARG1 <= ARG2 如果ARG1小于或等于ARG2,返回1;否则返回0
ARG1 = ARG2 如果ARG1等于ARG2,返回1;否则返回0
ARG1 != ARG2 如果ARG1不等于ARG2,返回1;否则返回0
ARG1 >= ARG2 如果ARG1大于或等于ARG2,返回1;否则返回0
ARG1 > ARG2 如果ARG1大于ARG2,返回1;否则返回0
ARG1 + ARG2 返回ARG1和ARG2的算术运算和
ARG1 - ARG2 返回ARG1和ARG2的算术运算差
ARG1 * ARG2 返回ARG1和ARG2的算术乘积
ARG1 / ARG2 返回ARG1被ARG2除的算术商
ARG1 % ARG2 返回ARG1被ARG2除的算术余数
STRING : REGEXP 如果REGEXP匹配到了STRING中的某个模式,返回该模式匹配
match STRING REGEXP 如果REGEXP匹配到了STRING中的某个模式,返回该模式匹配
substr STRING POS LENGTH 返回起始位置为POS(从1开始计数)、长度为LENGTH个字符的子字符串
index STRING CHARS 返回在STRING中找到CHARS字符串的位置;否则,返回0
length STRING 返回字符串STRING的数值长度
+ TOKEN 将TOKEN解释成字符串,即使是个关键字
(EXPRESSION) 返回EXPRESSION的值
bash shell数学运算符只支持整数运算。
使用方法:$[ operation ]
$ var1=$[1 + 5]
$ echo $var1
6
$ var2=$[$var1 * 2]
$ echo $var2
12
$
在使用方括号来计算公式时,不用担心shell会误解乘号或其他符号。shell知道它不是通配符,因为它在方括号内。
bash shell数学运算符只支持整数运算。
浮点运算是由内建变量scale控制的。必须将这个值设置为你希望在计算结果中保留的小数
位数,否则无法得到期望的结果。
$ bc -q
3.44 / 5
0
scale=4
3.44 / 5
.6880
quit
$
scale变量的默认值是0。在scale值被设置前,bash计算器的计算结果不包含小数位。在将
其值设置成4后,bash计算器显示的结果包含四位小数。-q命令行选项可以不显示bash计算器冗
长的欢迎信息。
在脚本中使用bc基本格式如下:
variable=$(echo "options; expression" | bc)
$ cat test9
#!/bin/bash
var1=$(echo "scale=4; 3.44 / 5" | bc)
echo The answer is $var1
$
脚本中也可以使用变量进行bc计算,
$ cat test11
#!/bin/bash
var1=20
var2=3.14159
var3=$(echo "scale=4; $var1 * $var1" | bc)
var4=$(echo "scale=4; $var3 * $var2" | bc)
echo The final result is $var4
$
如果设计大量的数字计算,bc命令能识别输入重定向,使用内联输入重定向,它允许你直接在命令行中重定向数据。在shell脚本中,
你可以将输出赋给一个变量。
variable=$(bc << EOF
options
statements
expressions
EOF
)
EOF文本字符串标识了内联重定向数据的起止。记住,仍然需要命令替换符号将bc命令的输出赋给变量。
Linux退出状态码
状 态 码 描 述
0 命令成功结束
1 一般性未知错误
2 不适合的shell命令
126 命令不可执行
127 没找到命令
128 无效的退出参数
128+x 与Linux信号x相关的严重错误
130 通过Ctrl+C终止的命令
255 正常范围之外的退出状态码
记住,在elif语句中,紧跟其后的else语句属于elif代码块。它们并不属于之前的
if-then代码块。但是如果之前的条件都不满足,还是会执行最后的else代码块中的命令。
If-then语句根据command1返回的退出状态码是否执行if-then中的代码块。
用法一:(不常用)
if command1
then
command set 1
elif command2
then
command set 2
else
command
fi
test命令可以判断三类条件:
Test命令(就是方括号测试条件)的数值比较功能
比 较 描 述
n1 -eq n2 检查n1是否与n2相等
n1 -ge n2 检查n1是否大于或等于n2
n1 -gt n2 检查n1是否大于n2
n1 -le n2 检查n1是否小于或等于n2
n1 -lt n2 检查n1是否小于n2
n1 -ne n2 检查n1是否不等于n2
方法二常用推荐(包含测试条件)
if [ conidition ]
then
commands1
elif [ conidition ]
then
commands2
else
commands3
fi
test命令的字符串比较功能
比 较 描 述
str1 = str2 检查str1是否和str2相同
str1 != str2 检查str1是否和str2不同
str1 < str2 检查str1是否比str2小
str1 > str2 检查str1是否比str2大
-n str1 检查str1的长度是否非0
-z str1 检查str1的长度是否为0
标准的数学比较符号来表示字符串比较,而用文本代码来表示数值比较。
test命令的文件比较功能
比 较 描 述
-d file 检查file是否存在并是一个目录
-e file 检查file是否存在
-f file 检查file是否存在并是一个文件
-r file 检查file是否存在并可读
-s file 检查file是否存在并非空
-w file 检查file是否存在并可写
-x file 检查file是否存在并可执行
-O file 检查file是否存在并属当前用户所有
-G file 检查file是否存在并且默认组与当前用户相同
file1 -nt file2 检查file1是否比file2新
file1 -ot file2 检查file1是否比file2旧
双括号命令的格式如下:
(( expression ))
双括号命令符号
符 号 |
描 述 |
val++ |
后增 |
val-- |
后减 |
++val |
先增 |
--val |
先减 |
! |
逻辑求反 |
~ |
位求反 |
** |
幂运算 |
<< |
左位移 |
>> |
右位移 |
& |
位布尔和 |
| |
位布尔或 |
&& |
逻辑和 |
|| |
逻辑或 |
双方括号命令的格式如下:
[[ expression ]]
命令格式如下:
case variable in
pattern1 | pattern2) commands1;;
pattern3) commands2;;
*) default commands;;
esac
例如:
$ cat test26.sh
#!/bin/bash
# using the case command
#
case $USER in
rich | barbara)
echo "Welcome, $USER"
echo "Please enjoy your visit";;
testing)
echo "Special testing account";;
jessica)
echo "Do not forget to log off when you're done";;
*)
echo "Sorry, you are not allowed here";;
esac
$
$ ./test26.sh
Welcome, rich
Please enjoy your visit
$
命令的基本格式。
for var in list
do
commands
done
for无限循环写法
for ((;;))
do
done
分隔符,默认list使用空格为分割符,确定每一个值,如果值中有单引号可以使用转义符号\,使单引号脱义。也可以使用双引号来标识整个值的范围,如果值中有空格,也可以使用双引号标识。
例如:
$ cat test2
#!/bin/bash
# another example of how not to use the for command
for test in I don\'t know if "this'll" "wo rk"
do
echo "word:$test"
done
$ ./test2
word:I
word:don't
word:know
word:if
word:this'll
word:wo rk
$
默认情况下,bash shell会将下列字符当作字段分隔符:
如果bash shell在数据中看到了这些字符中的任意一个,它就会假定这表明了列表中一个新数据字段的开始。
如果你想修改IFS的值,使其只能识别换行符,那就必须这么做:IFS=$'\n' 将这个语句加入到脚本中,告诉bash shell在数据值中忽略空格和制表符。
$ cat test5b
#!/bin/bash
# reading values from a file
file="states"
IFS=$'\n'
for state in $(cat $file)
do
echo "Visit beautiful $state"
done
$ ./test5b
Visit beautiful Alabama
Visit beautiful Alaska
Visit beautiful Arizona
Visit beautiful Arkansas
Visit beautiful Colorado
Visit beautiful Connecticut
$
警告 在处理代码量较大的脚本时,可能在一个地方需要修改IFS的值,然后忽略这次修改,在脚本的其他地方继续沿用IFS的默认值。一个可参考的安全实践是在改变IFS之前保存原来的IFS值,之后再恢复它。
这种技术可以这样实现:
IFS.OLD=$IFS
IFS=$'\n'
<在代码中使用新的IFS值>
IFS=$IFS.OLD
这就保证了在脚本的后续操作中使用的是IFS的默认值。
还可以指定IFS的值,例如需要使用:作为分割单位,可以像下面这样设定
IFS=:
如果你需要多个符号来分割,那么IFS也支持指定多个分割符,就像下面这样
IFS=$'\n':;"
这个赋值会将换行符、冒号、分号和双引号作为字段分隔符。如何使用IFS字符解析数据没有任何限制。
注意:
if [ -d "$file" ]
在Linux中,目录名和文件名中包含空格当然是合法的。要适应这种情况,应该将$file变
量用双引号圈起来。如果不这么做,遇到含有空格的目录名或文件名时就会有错误产生。
还可以在for数据列表中使用任意多个目录通配符,例如下面这样
for file in /home/rich/.b* /home/rich/badtest
do #会先匹配/home/rich/.b*,然后匹配/home/rich/badtest
if [ -d "$file" ]
then
echo "$file is a directory"
elif [ -f "$file" ]
then
echo "$file is a file"
else
echo "$file doesn't exist"
fi
done
以下是bash中C语言风格的for循环的基本格式。
for (( variable assignment ; condition ; iteration process ))
$ cat test8
#!/bin/bash
# testing the C-style for loop
for (( i=1; i <= 10; i++ ))
do
echo "The next number is $i"
done
for循环中的do done也可以使用{}来代替,上面的例子也可以写成
$ cat test8
#!/bin/bash
# testing the C-style for loop
for (( i=1; i <= 10; i++ ))
{
echo "The next number is $i"
}
使用多个变量
尽管可以使用多个变量,但你只能在for循环中定义一种条件。
$ cat test9
#!/bin/bash
# multiple variables
for (( a=1, b=10; a <= 10; a++, b-- ))
do
echo "$a - $b"
done
$ ./test9
1 - 10
2 - 9
3 - 8
4 - 7
5 - 6
6 - 5
7 - 4
8 - 3
9 - 2
10 - 1
$
while命令允许定义一个要测试的命令,然后循环执行一组命令,只要定义的测试命令返回的是退出状态码0。它会在每次迭代的一开始测试test命令。在test命令返回非零退出状态码时,while命令会停止执行那组命令。
while命令的格式是:
while test command
do
other commands
done
while无限循环有多种写法,这里举例2个
写法一
while true
do
done
写法二
while [ 1 ]
do
done
只有最后一个测试命令的退出状态码会被用来决定什么时候结束循环。
cat test11
#!/bin/bash
# testing a multicommand while loop
var1=10
while echo $var1
[ $var1 -ge 0 ]
do
echo "This is inside the loop"
var1=$[ $var1 - 1 ]
done
$ ./test11
10
This is inside the loop
9
This is inside the loop
8
This is inside the loop
7
This is inside the loop
6
This is inside the loop
5
This is inside the loop
4
This is inside the loop
3
This is inside the loop
2
This is inside the loop
1
This is inside the loop
0
This is inside the loop
-1
$
请仔细观察本例中做了什么。while语句中定义了两个测试命令。
while echo $var1
[ $var1 -ge 0 ]
第一个测试简单地显示了var1变量的当前值。第二个测试用方括号来判断var1变量的值。
在循环内部,echo语句会显示一条简单的消息,说明循环被执行了。注意当你运行本例时输出是如何结束的。
This is inside the loop
-1
$
while循环会在var1变量等于0时执行echo语句,然后将var1变量的值减一。接下来再次执行测试命令,用于下一次迭代。echo测试命令被执行并显示了var变量的值(现在小于0了)。直到shell执行test测试命令,whle循环才会停止。
这说明在含有多个命令的while语句中,在每次迭代中所有的测试命令都会被执行,包括测
试命令失败的最后一次迭代。要留心这种用法。另一处要留意的是该如何指定多个测试命令。注意,每个测试命令都出现在单独的一行上。
until命令和while命令工作的方式完全相反。until命令要求你指定一个通常返回非零退
出状态码的测试命令。只有测试命令的退出状态码不为0,bash shell才会执行循环中列出的命令。
一旦测试命令返回了退出状态码0,循环就结束了。
until命令的格式如下。
until test commands
do
other commands
done
通常必须遍历存储在文件中的数据。这要求结合已经讲过的两种技术:
通过修改IFS环境变量,就能强制for命令将文件中的每行都当成单独的一个条目来处理,
即便数据中有空格也是如此。一旦从文件中提取出了单独的行,可能需要再次利用循环来提取行中的数据。
典型的例子是处理/etc/passwd文件中的数据。这要求你逐行遍历/etc/passwd文件,并将IFS变量的值改成冒号,这样就能分隔开每行中的各个数据段了。
#!/bin/bash
# changing the IFS value
IFS.OLD=$IFS
IFS=$'\n'
for entry in $(cat /etc/passwd)
do
echo "Values in $entry –"
IFS=:
for value in $entry
do
echo " $value"
done
done
$
这个脚本使用了两个不同的IFS值来解析数据。第一个IFS值解析出/etc/passwd文件中的单独的行。内部for循环接着将IFS的值修改为冒号,允许你从/etc/passwd的行中解析出单独的值。
在运行这个脚本时,你会得到如下输出。
Values in rich:x:501:501:Rich Blum:/home/rich:/bin/bash -
rich
x
501
501
Rich Blum
/home/rich
/bin/bash
Values in katie:x:502:502:Katie Blum:/home/katie:/bin/bash -
katie
x
506
509
Katie Blum
/home/katie
/bin/bash
内部循环会解析出/etc/passwd每行中的各个值。这种方法在处理外部导入电子表格所采用的
逗号分隔的数据时也很方便。
有两个命令能帮我们控制循环内部的情况:
break命令
continue命令
跳出循环,默认跳出一层循环,可以在break后加参数,表示需要跳出几层循环。
例如
break 2
表示跳出2层循环。(if-then是判断,for,while,until才是循环)
continue命令可以提前中止某次循环中的命令,但并不会完全终止整个循环。
相当于给出条件,把符合这个条件东西过滤出去,不提供显示或者输出
也可以在while和until循环中使用continue命令,但要特别小心。记住,当shell执行
continue命令时,它会跳过剩余的命令。
你可以对循环的输出使用管道或进行重定向。这可以通过在done命令之后添加一个处理命令来实现。
for file in /home/rich/*
do
if [ -d "$file" ]
then
echo "$file is a directory"
elif
echo "$file is a file"
fi
done > output.txt
位置参数变量是标准的数字:$0是程序名,$1是第一个参数,$2是第二个参数,依次类推,直到第九个参数$9。在第9个变量之后,你必须在变量数字周围加上花括号,比如${10}。
$ cat test4.sh
#!/bin/bash
# handling lots of parameters
#
total=$[ ${10} * ${11} ]
echo The tenth parameter is ${10}
echo The eleventh parameter is ${11}
echo The total is $total
$
$ ./test4.sh 1 2 3 4 5 6 7 8 9 10 11 12
The tenth parameter is 10
The eleventh parameter is 11
The total is 110
$
传递命令行参数时,如果参数中间有空格,需要使用引号(单引号,双引号均可)。
$ cat test3.sh
#!/bin/bash
# testing string parameters
#
echo Hello $1, glad to meet you.
$
$ ./test3.sh 'Rich Blum'
Hello Rich Blum, glad to meet you.
$
$ ./test3.sh "Rich Blum"
Hello Rich Blum, glad to meet you.
$
可以用$0参数获取shell在命令行启动的脚本名。使用什么方式很重要,如果使用另一个命令来运行shell脚本,命令会和脚本名混在一起,出现在$0参数中。
$ ./test5.sh
The zero parameter is set to: ./test5.sh
$
当传给$0变量的实际字符串不仅仅是脚本名,而是完整的脚本路径时,变量$0就会使用整个路径。
$ bash /home/Christine/test5.sh
The zero parameter is set to: /home/Christine/test5.sh
$
basename命令会返回不包含路径的脚本名。注意basename的使用方法
$ cat test5b.sh
#!/bin/bash
# Using basename with the $0 parameter
#
name=$(basename $0)
echo
echo The script name is: $name
#
$ bash /home/Christine/test5b.sh
The script name is: test5b.sh
$
$ ./test5b.sh
The script name is: test5b.sh
$
特殊变量$#含有脚本运行时携带的命令行参数的个数。如果需要使用多个命令行参数时,可以在脚本运行时直接检查参数够不够,更加简单,不需要一个一个作对比。
这个变量还提供了一个简便方法来获取命令行中最后一个参数,完全不需要知道实际上到底
用了多少个参数。使用${!#}就可以知道命令行参数的最后一个参数。
$ cat test10.sh
#!/bin/bash
# Grabbing the last parameter
#
params=$#
echo
echo The last parameter is $params
echo The last parameter is ${!#}
echo
#
$
$ bash test10.sh 1 2 3 4 5
The last parameter is 5
The last parameter is 5
$
$ bash test10.sh
The last parameter is 0
The last parameter is test10.sh
$
$*和$@变量可以用来轻松访问所有的参数。这两个变量都能够在单个变量中存储所有的命
令行参数。
$*变量会将命令行上提供的所有参数当作一个单词保存。这个单词包含了命令行中出现的每
一个参数值。基本上$*变量会将这些参数视为一个整体,而不是多个个体。
另一方面,$@变量会将命令行上提供的所有参数当作同一字符串中的多个独立的单词。这样
你就能够遍历所有的参数值,得到每个参数。
$*变量会将所有参数当成单个参数,而$@变量会单独处理每个参数。
下面的例子给出了差异
$ cat test12.sh
#!/bin/bash
# testing $* and $@
#
echo
count=1
#
for param in "$*"
do
echo "\$* Parameter #$count = $param"
count=$[ $count + 1 ]
done
#
echo
count=1
#
for param in "$@"
do
echo "\$@ Parameter #$count = $param"
count=$[ $count + 1 ]
done
$
$ ./test12.sh rich barbara katie jessica
$* Parameter #1 = rich barbara katie jessica
$@ Parameter #1 = rich
$@ Parameter #2 = barbara
$@ Parameter #3 = katie
$@ Parameter #4 = jessica
$
bash shell的shift命令能够用来操作命令行参数。跟字面上的意思一样,shift命令会根据它们的相对位置来移动命令行参数。
在使用shift命令时,默认情况下它会将每个参数变量向左移动一个位置。所以,变量$3
的值会移到$2中,变量$2的值会移到$1中,而变量$1的值则会被删除(注意,变量$0的值,也就是程序名,不会改变)。
这里有个例子来解释它是如何工作的。
$ cat test13.sh
#!/bin/bash
# demonstrating the shift command
echo
count=1
while [ -n "$1" ]
do
echo "Parameter #$count = $1"
count=$[ $count + 1 ]
shift
done
$
$ ./test13.sh rich barbara katie jessica
Parameter #1 = rich
Parameter #2 = barbara
Parameter #3 = katie
Parameter #4 = jessica
$
注意:使用shift命令的时候要小心。如果某个参数被移出,它的值就被丢弃了,无法再恢复。
你也可以一次性移动多个位置,只需要给shift命令提供一个参数,指明要移动的位置数就行了。
$ cat test14.sh
#!/bin/bash
# demonstrating a multi-position shift
#
echo
echo "The original parameters: $*"
shift 2
echo "Here's the new first parameter: $1"
$
$ ./test14.sh 1 2 3 4 5
The original parameters: 1 2 3 4 5
Here's the new first parameter: 3
$
通过使用shift命令的参数,就可以轻松地跳过不需要的参数。
处理简单选项
在前面的test13.sh脚本中,你看到了如何使用shift命令来依次处理脚本程序携带的命令
行参数。你也可以用同样的方法来处理命令行选项。
在提取每个单独参数时,用case语句(参见第12章)来判断某个参数是否为选项。
$ cat test15.sh
#!/bin/bash
# extracting command line options as parameters
#
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) echo "Found the -b option" ;;
-c) echo "Found the -c option" ;;
*) echo "$1 is not an option" ;;
esac
shift
done
$
$ ./test15.sh -a -b -c -d
Found the -a option
Found the -b option
Found the -c option
-d is not an option
$
case语句会检查每个参数是不是有效选项。如果是的话,就运行对应case语句中的命令。
不管选项按什么顺序出现在命令行上,这种方法都适用。
$ ./test15.sh -d -c -a
-d is not an option
Found the -c option
Found the -a option
$
case语句在命令行参数中找到一个选项,就处理一个选项。如果命令行上还提供了其他参
数,你可以在case语句的通用情况处理部分中处理。
分离参数和选项(--)
你会经常遇到想在shell脚本中同时使用选项和参数的情况。Linux中处理这个问题的标准方
式是用特殊字符来将二者分开,该字符会告诉脚本何时选项结束以及普通参数何时开始。
对Linux来说,这个特殊字符是双破折线(--)。shell会用双破折线来表明选项列表结束。在
双破折线之后,脚本就可以放心地将剩下的命令行参数当作参数,而不是选项来处理了。
要检查双破折线,只要在case语句中加一项就行了。
$ cat test16.sh
#!/bin/bash
# extracting options and parameters
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) echo "Found the -b option";;
-c) echo "Found the -c option" ;;
--) shift
break ;;
*) echo "$1 is not an option";;
esac
shift
done
#
count=1
for param in $@
do
echo "Parameter #$count: $param"
count=$[ $count + 1 ]
done
$
在遇到双破折线时,脚本用break命令来跳出while循环。由于过早地跳出了循环,我们需
要再加一条shift命令来将双破折线移出参数变量。(这个shift是break上边的shift)
对于第一个测试,试试用一组普通的选项和参数来运行这个脚本。
$ ./test16.sh -c -a -b test1 test2 test3
Found the -c option
Found the -a option
Found the -b option
test1 is not an option
test2 is not an option
test3 is not an option
$
处理带值的选项
有些选项会带上一个额外的参数值。在这种情况下,命令行看起来像下面这样。
$ ./testing.sh -a test1 -b -c -d test2
当命令行选项要求额外的参数时,脚本必须能检测到并正确处理。下面是如何处理的
例子。
$ cat test17.sh
#!/bin/bash
# extracting command line options and values
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option";;
-b) param="$2"
echo "Found the -b option, with parameter value $param"
shift ;;
-c) echo "Found the -c option";;
--) shift
break ;;
*) echo "$1 is not an option";;
esac
shift
done
#
count=1
for param in "$@"
do
echo "Parameter #$count: $param"
count=$[ $count + 1 ]
done
$
$ ./test17.sh -a -b test1 -d
Found the -a option
Found the -b option, with parameter value test1
-d is not an option
$
在这个例子中,case语句定义了三个它要处理的选项。-b选项还需要一个额外的参数值。
由于要处理的参数是$1,额外的参数值就应该位于$2(因为所有的参数在处理完之后都会被移
出)。只要将参数值从$2变量中提取出来就可以了。当然,因为这个选项占用了两个参数位,所
以你还需要使用shift命令多移动一个位置。
只用这些基本的特性,整个过程就能正常工作,不管按什么顺序放置选项(但要记住包含每个选项相应的选项参数)。
$ ./test17.sh -b test1 -a -d
Found the -b option, with parameter value test1
Found the -a option
-d is not an option
$
现在shell脚本中已经有了处理命令行选项的基本能力,但还有一些限制。比如,如果你想将
多个选项放进一个参数中时,它就不能工作了。
$ ./test17.sh -ac
-ac is not an option
$
在Linux中,合并选项是一个很常见的用法,而且如果脚本想要对用户更友好一些,也要给
用户提供这种特性。幸好,有另外一种处理选项的方法能够帮忙。
使用getopts 命令
每次调用它时,它一次只处理命令行上检测到的一个参数。处理完所有的参数后,它会退出
并返回一个大于0的退出状态码。这让它非常适合用解析命令行所有参数的循环中。
getopts命令的格式如下:
getopts optstring variable
optstring值类似于getopt命令中的那个。有效的选项字母都会列在optstring中,如果
选项字母要求有个参数值,就在该字母后加一个冒号。要去掉错误消息的话,可以在optstring之前加一个冒号。getopts命令将当前参数保存在命令行中定义的variable中。
getopts命令会用到两个环境变量。如果选项需要跟一个参数值,OPTARG环境变量就会保
存这个值。OPTIND环境变量保存了参数列表中getopts正在处理的参数位置。这样你就能在处
理完选项之后继续处理其他命令行参数了。
让我们看个使用getopts命令的简单例子。
$ cat test19.sh
#!/bin/bash
# simple demonstration of the getopts command
#
echo
while getopts :ab:c opt
do
case "$opt" in
a) echo "Found the -a option" ;;
b) echo "Found the -b option, with value $OPTARG";;
c) echo "Found the -c option" ;;
*) echo "Unknown option: $opt";;
esac
done
$
$ ./test19.sh -ab test1 -c
Found the -a option
Found the -b option, with value test1
Found the -c option
$
getopts命令有几个好用的功能。对新手来说,可以在参数值中包含空格。
$ ./test19.sh -b "test1 test2" -a
Found the -b option, with value test1 test2
Found the -a option
$
另一个好用的功能是将选项字母和参数值放在一起使用,而不用加空格。
$ ./test19.sh -abtest1
Found the -a option
Found the -b option, with value test1
$
getopts命令能够从-b选项中正确解析出test1值。除此之外,getopts还能够将命令行上
找到的所有未定义的选项统一输出成问号。
$ ./test19.sh -acde
Found the -a option
Found the -c option
Unknown option: ?
Unknown option: ?
$
getopts命令知道何时停止处理选项,并将参数留给你处理。在getopts处理每个选项时,它会将OPTIND环境变量值增一。在getopts完成处理时,你可以使用shift命令和OPTIND值来移动参数。
$ cat test20.sh
#!/bin/bash
# Processing options & parameters with getopts
#
echo
while getopts :ab:cd opt
do
case "$opt" in
a) echo "Found the -a option" ;;
b) echo "Found the -b option, with value $OPTARG" ;;
c) echo "Found the -c option" ;;
d) echo "Found the -d option" ;;
*) echo "Unknown option: $opt" ;;
esac
done
#
shift $[ $OPTIND - 1 ]
#
echo
count=1
for param in "$@"
do
echo "Parameter $count: $param"
count=$[ $count + 1 ]
done
#
$
$ ./test20.sh -a -b test1 -d test2 test3 test4
Found the -a option
Found the -b option, with value test1
Found the -d option
Parameter 1: test2
Parameter 2: test3
Parameter 3: test4
$
这些字母选项在Linux世界里已经拥有了某种程度的标准含义。能支持对用户会很友好
常用的Linux命令选项
选 项 描 述
-a 显示所有对象
-c 生成一个计数
-d 指定一个目录
-e 扩展一个对象
-f 指定读入数据的文件
-h 显示命令的帮助信息
-i 忽略文本大小写
-l 产生输出的长格式版本
-n 使用非交互模式(批处理)
-o 将所有输出重定向到的指定的输出文件
-q 以安静模式运行
-r 递归地处理目录和文件
-s 以安静模式运行
-v 生成详细输出
-x 排除某个对象
-y 对所有问题回答yes
read命令包含了-p选项,允许你直接在read命令行指定提示符。
$ cat test22.sh
#!/bin/bash
# testing the read -p option
#
read -p "Please enter your age: " age
days=$[ $age * 365 ]
echo "That makes you over $days days old! "
#
$
$ ./test22.sh
Please enter your age: 10
That makes you over 3650 days old!
$
read命令会将提示符后输入的所有数据分配给单个变量,要么你就指定多个变量。输入的每个数据值都会分配给变量列表中的下一个变量。如果变量数量不够,剩下的数据就全部分配给最后一个变量。
在read命令行中也可以不指定变量。如果是这样,read命令会将它收到的任何数据都放进
特殊环境变量REPLY中。
$ cat test24.sh
#!/bin/bash
# Testing the REPLY Environment variable
#
read -p "Enter your name: "
echo
echo Hello $REPLY, welcome to my program.
#
$
$ ./test24.sh
Enter your name: Christine
Hello Christine, welcome to my program.
$
REPLY环境变量会保存输入的所有数据,可以在shell脚本中像其他变量一样使用。
可以用-t选项来指定一个计时器。-t选项指定了read命令等待输入的秒数。当计时器过期后,read命令会返回一个非零退出状态码。
$ cat test25.sh
#!/bin/bash
# timing the data entry
#
if read -t 5 -p "Please enter your name: " name
then
echo "Hello $name, welcome to my script"
else
echo
echo "Sorry, too slow! "
fi
$
$ ./test25.sh
Please enter your name: Rich
Hello Rich, welcome to my script
$
$ ./test25.sh
Please enter your name:
Sorry, too slow!
$
在本例中,计时器过期时,if语句不成立,shell会执行else部分的命令。
也可以不对输入过程计时,而是让read命令来统计输入的字符数。当输入的字符达到预设
的字符数时,就自动退出,将输入的数据赋给变量。
$ cat test26.sh
#!/bin/bash
# getting just one character of input
#
read -n1 -p "Do you want to continue [Y/N]? " answer
case $answer in
Y | y) echo
echo "fine, continue on…";;
N | n) echo
echo OK, goodbye
exit;;
esac
echo "This is the end of the script"
$
$ ./test26.sh
Do you want to continue [Y/N]? Y
fine, continue on…
This is the end of the script
$
$ ./test26.sh
Do you want to continue [Y/N]? n
OK, goodbye
$
本例中将-n选项和值1一起使用,告诉read命令在接受单个字符后退出。只要按下单个字符
回答后,read命令就会接受输入并将它传给变量,无需按回车键。
用read命令来读取Linux系统上文件里保存的数据。每次调用read命令,它都会从文件中读取一行文本。当文件中再没有内容时,read命令会退出并返回非零退出状态码。
最常见的方法是对文件使用cat命令,将
结果通过管道直接传给含有read命令的while命令。下面的例子说明怎么处理。
$ cat test28.sh
#!/bin/bash
# reading data from a file
#
count=1
cat test | while read line
do
echo "Line $count: $line"
count=$[ $count + 1]
done
echo "Finished processing the file"
$
$ cat test
The quick brown dog jumps over the lazy fox.
This is a test, this is only a test.
O Romeo, Romeo! Wherefore art thou Romeo?
$
$ ./test28.sh
Line 1: The quick brown dog jumps over the lazy fox.
Line 2: This is a test, this is only a test.
Line 3: O Romeo, Romeo! Wherefore art thou Romeo?
Finished processing the file
$
标准的文件描述符有3个。0、1、2,分别对应输入,输出,错误输出
Linux的标准文件描述符
文件描述符 缩 写 描 述
0 STDIN 标准输入
1 STDOUT 标准输出
2 STDERR 标准错误
重定向> 和追加重定向 >> 重定向后不再提供屏幕显示
如果需要重定向和屏幕显示同时存在,可以使用tee(重定向),tee –a(追加重定向)
临时重定向的使用方法:直接在命令后跟 > 或 >> 重定向文件。 cat file >t.log
永久重定向的使用方法:使用exec命令。 exec 1>testout
注意,文件描述符,重定向符和重定向文件之间没有空格
在shell中最多可以有9个打开的文件描述符。其他6个从3~8的文件描述符均可用作输入或输出重定向。
在重定向到文件描述符时,你必须在文件描述符数字之前加一个&:
echo "This is an error message" >&2
下面这个例子举例了标准输出的恢复。
$ cat test14
#!/bin/bash
# storing STDOUT, then coming back to it
exec 3>&1
exec 1>test14out
echo "This should store in the output file"
echo "along with this line."
exec 1>&3
echo "Now things should be back to normal"
$
$ ./test14
Now things should be back to normal
$ cat test14out
This should store in the output file
along with this line.
$
首先,脚本将文件描述符3重定向到文件描述符1的当前位置,也就是STDOUT。这意味着任何发送给文件描述符3的输出都将出现在显示器上。
第二个exec命令将STDOUT重定向到文件,shell现在会将发送给STDOUT的输出直接重定向到输出文件中。但是,文件描述符3仍然指向STDOUT原来的位置,也就是显示器。如果此时将输出数据发送给文件描述符3,它仍然会出现在显示器上,尽管STDOUT已经被重定向了。在向STDOUT(现在指向一个文件)发送一些输出之后,脚本将STDOUT重定向到文件描述符3的当前位置(现在仍然是显示器)。这意味着现在STDOUT又指向了它原来的位置:显示器。
这个方法可能有点叫人困惑,但这是一种在脚本中临时重定向输出,然后恢复默认输出设置
的常用方法
下面这个例子举例了标准输入的恢复
$ cat test15
#!/bin/bash
# redirecting input file descriptors
exec 6<&0
exec 0< testfile
count=1
while read line
do
echo "Line #$count: $line"
count=$[ $count + 1 ]
done
exec 0<&6
read -p "Are you done now? " answer
case $answer in
Y|y) echo "Goodbye";;
N|n) echo "Sorry, this is the end.";;
esac
$ ./test15
Line #1: This is the first line.
Line #2: This is the second line.
Line #3: This is the third line.
Are you done now? y
Goodbye
$
在这个例子中,文件描述符6用来保存STDIN的位置。然后脚本将STDIN重定向到一个文件。
read命令的所有输入都来自重定向后的STDIN(也就是输入文件)。
在读取了所有行之后,脚本会将STDIN重定向到文件描述符6,从而将STDIN恢复到原先的
位置。该脚本用了另外一个read命令来测试STDIN是否恢复正常了。这次它会等待键盘的输入。
要关闭文件描述符,将它重定向到特殊符号&-。脚本中看起来如下:
exec 3>&-
lsof命令会列出整个Linux系统打开的所有文件描述符。它会向非系统管理员用户提供Linux系统的信息。在很多Linux系统中(如Fedora),lsof命令位于/usr/sbin目录。要想以普通用户账户来运行它,必须通过全路径名来引用:
$ /usr/sbin/lsof
常用的参数有-p和-d,前者允许指定进程ID(PID),后者允许指定要显示的文件描述符编号
要想知道进程的当前PID,可以用特殊环境变量$$(shell会将它设为当前PID)。-a选项用来
对其他两个选项的结果执行布尔AND运算,这会产生如下输出。
$ /usr/sbin/lsof -a -p $$ -d 0,1,2
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
bash 3344 rich 0u CHR 136,0 2 /dev/pts/0
bash 3344 rich 1u CHR 136,0 2 /dev/pts/0
bash 3344 rich 2u CHR 136,0 2 /dev/pts/0
$
上例显示了当前进程(bash shell)的默认文件描述符(0、1和2)。lsof的默认输出中有7列信息。
lsof的默认输出
列 描 述
COMMAND 正在运行的命令名的前9个字符
PID 进程的PID
USER 进程属主的登录名
FD 文件描述符号以及访问类型(r代表读,w代表写,u代表读写)
TYPE 文件的类型(CHR代表字符型,BLK代表块型,DIR代表目录,REG代表常规文件)
DEVICE 设备的设备号(主设备号和从设备号)
SIZE 如果有的话,表示文件的大小
NODE 本地文件的节点号
NAME 文件名
Linux使用/tmp目录来存放不需要永久保留的文件。大多数Linux发行版配置了系统在启动时自动删除/tmp目录的所有文件。
mktemp会在本地目录中创建一个文件。要用mktemp命令在本地目录中创建一
个临时文件,你只要指定一个文件名模板就行了。模板可以包含任意文本文件名,在文件名末尾加上6个X就行了。
$ mktemp testing.XXXXXX
$ ls -al testing*
-rw------- 1 rich rich 0 Oct 17 21:30 testing.UfIi13
$
mktemp命令会用6个字符码替换这6个X,从而保证文件名在目录中是唯一的。你可以创建多个临时文件,它可以保证每个文件都是唯一的。
-t选项会强制mktemp命令来在系统的临时目录来创建该文件。在用这个特性时,mktemp命
令会返回用来创建临时文件的全路径,而不是只有文件名。
$ mktemp -t test.XXXXXX
/tmp/test.xG3374
$ ls -al /tmp/test*
-rw------- 1 rich rich 0 2014-10-29 18:41 /tmp/test.xG3374
$
-d选项告诉mktemp命令来创建一个临时目录而不是临时文件。
$ cat test21
#!/bin/bash
# using a temporary directory
tempdir=$(mktemp -d dir.XXXXXX)
cd $tempdir
tempfile1=$(mktemp temp.XXXXXX)
tempfile2=$(mktemp temp.XXXXXX)
exec 7> $tempfile1
exec 8> $tempfile2
echo "Sending data to directory $tempdir"
echo "This is a test line of data for $tempfile1" >&7
echo "This is a test line of data for $tempfile2" >&8
$ ./test21
Sending data to directory dir.ouT8S8
$ ls –al
total 72
drwxr-xr-x 3 rich rich 4096 Oct 17 22:20 ./
drwxr-xr-x 9 rich rich 4096 Oct 17 09:44 ../
drwx------ 2 rich rich 4096 Oct 17 22:20 dir.ouT8S8/
-rwxr--r-- 1 rich rich 338 Oct 17 22:20 test21*
$ cd dir.ouT8S8
[dir.ouT8S8]$ ls -al
total 16
drwx------ 2 rich rich 4096 Oct 17 22:20 ./
drwxr-xr-x 3 rich rich 4096 Oct 17 22:20 ../
-rw------- 1 rich rich 44 Oct 17 22:20 temp.N5F3O6
-rw------- 1 rich rich 44 Oct 17 22:20 temp.SQslb7
[dir.ouT8S8]$ cat temp.N5F3O6
This is a test line of data for temp.N5F3O6
[dir.ouT8S8]$ cat temp.SQslb7
This is a test line of data for temp.SQslb7
[dir.ouT8S8]$
这段脚本在当前目录创建了一个目录,然后它用cd命令进入该目录,并创建了两个临时文件。
之后这两个临时文件被分配给文件描述符,用来存储脚本的输出。
trap命令允许你来指定shell
脚本要监看并从shell中拦截的Linux信号。如果脚本收到了trap命令中列出的信号,该信号不再
由shell处理,而是交由本地处理。
trap命令的格式是:
trap commands signals
这里有个简单例子,展示了如何使用trap命令来忽略SIGINT信号,并控制脚本的行为。
$ cat test1.sh
#!/bin/bash
# Testing signal trapping
#
trap "echo ' Sorry! I have trapped Ctrl-C'" SIGINT
#
echo This is a test script
#
count=1
while [ $count -le 10 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
echo "This is the end of the test script"
#
本例中用到的trap命令会在每次检测到SIGINT信号时显示一行简单的文本消息。捕获这些
信号会阻止用户用bash shell组合键Ctrl+C来停止程序。
$ ./test1.sh
This is a test script
Loop #1
Loop #2
Loop #3
Loop #4
Loop #5
^C Sorry! I have trapped Ctrl-C
Loop #6
Loop #7
Loop #8
^C Sorry! I have trapped Ctrl-C
Loop #9
Loop #10
This is the end of the test script
$
每次使用Ctrl+C组合键,脚本都会执行trap命令中指定的echo语句,而不是处理该信号并
允许shell停止该脚本。
除了在shell脚本中捕获信号,你也可以在shell脚本退出时进行捕获。这是在shell完成任务时执行命令的一种简便方法。
要捕获shell脚本的退出,只要在trap命令后加上EXIT信号就行。
$ cat test2.sh
#!/bin/bash
# Trapping the script exit
#
trap "echo Goodbye..." EXIT
#
count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
$
$ ./test2.sh
Loop #1
Loop #2
Loop #3
Loop #4
Loop #5
Goodbye...
$
当脚本运行到正常的退出位置时,捕获就被触发了,shell会执行在trap命令行指定的命令。
如果提前退出脚本,同样能够捕获到EXIT。
$ ./test2.sh
Loop #1
Loop #2
Loop #3
^CGoodbye...
$
因为SIGINT信号并没有出现在trap命令的捕获列表中,当按下Ctrl+C组合键发送SIGINT信
号时,脚本就退出了。但在脚本退出前捕获到了EXIT,于是shell执行了trap命令。
要想在脚本中的不同位置进行不同的捕获处理,只需重新使用带有新选项的trap命令。
$ cat test3.sh
#!/bin/bash
# Modifying a set trap
#
trap "echo ' Sorry... Ctrl-C is trapped.'" SIGINT
#
count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
trap "echo ' I modified the trap!'" SIGINT
#
count=1
while [ $count -le 5 ]
do
echo "Second Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
$
修改了信号捕获之后,脚本处理信号的方式就会发生变化。但如果一个信号是在捕获被修改
前接收到的,那么脚本仍然会根据最初的trap命令进行处理。
$ ./test3.sh
Loop #1
Loop #2
Loop #3
^C Sorry... Ctrl-C is trapped.
Loop #4
Loop #5
Second Loop #1
Second Loop #2
^C I modified the trap!
Second Loop #3
Second Loop #4
Second Loop #5
$
也可以删除已设置好的捕获。
$ cat test3b.sh
#!/bin/bash
# Removing a set trap
#
trap "echo ' Sorry... Ctrl-C is trapped.'" SIGINT
#
count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
# Remove the trap
trap -- SIGINT
echo "I just removed the trap"
#
count=1
while [ $count -le 5 ]
do
echo "Second Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
$ ./test3b.sh
Loop #1
Loop #2
Loop #3
Loop #4
Loop #5
I just removed the trap
Second Loop #1
Second Loop #2
Second Loop #3
^C
$
窍门 也可以在trap命令后使用单破折号来恢复信号的默认行为。单破折号和双破折号都可以
正常发挥作用。
nohup命令运行了另外一个命令来阻断所有发送给该进程的SIGHUP信号。这会在退出终端会
话时阻止进程退出。
nohup命令的格式如下:
$ nohup ./test1.sh &
[1] 3856
$ nohup: ignoring input and appending output to 'nohup.out'
$
和普通后台进程一样,shell会给命令分配一个作业号,Linux系统会为其分配一个PID号。区
别在于,当你使用nohup命令时,如果关闭该会话,脚本会忽略终端会话发过来的SIGHUP信号。
由于nohup命令会解除终端与进程的关联,进程也就不再同STDOUT和STDERR联系在一起。
为了保存该命令产生的输出,nohup命令会自动将STDOUT和STDERR的消息重定向到一个名为
nohup.out的文件中。
注意:如果在运行位于同一个目录中的多个命令时一定要当心,因为所有的输出都会被发送到同一个nohup.out文件中。
通过jobs可以查看当前所有的作业情况,如果想看作业的PID,通过jobs -l便可以查看。(小写的L)带加号的作业会被当做默认作业。带减号的作业成为下一个默认作业。在任何情况下,只有一个加号和一个减号,不管多少正在运行的作业。
$ jobs -l
[1]+ 1897 Stopped ./test10.sh
[2]- 1917 Running ./test10.sh > test10.out &
$
jobs命令参数
参 数 描 述
-l 列出进程的PID以及作业号
-n 只列出上次shell发出的通知后改变了状态的作业
-p 只列出作业的PID
-r 只列出运行中的作业
-s 只列出已停止的作业
bg(以后台方式运行)+作业号
$ bg 2
fg(以前台方式运行)+作业号
$ fg 2
调度优先级是个整数值,从20(最高优先级)到+19(最低优先级)。默认情况下,bash shell
以优先级0来启动所有进程。
只要记住那句俗语“好人难做”就行了。越是“好”或高的值,获得CPU时间的机会越低。
nice命令允许你设置命令启动时的调度优先级。要让命令以更低的优先级运行,只要用nice
的-n命令行来指定新的优先级级别。
$ nice -n 10 ./test4.sh > test4.out &
[1] 4973
$
$ ps -p 4973 -o pid,ppid,ni,cmd
PID PPID NI CMD
4973 4721 10 /bin/bash ./test4.sh
$
注意,必须将nice命令和要启动的命令放在同一行中。ps命令的输出验证了谦让度值(NI
列)已经被调整到了10
nice命令的-n选项并不是必须的,只需要在破折号后面跟上优先级就行了。
$ nice -10 ./test4.sh > test4.out &
[1] 4993
$
$ ps -p 4993 -o pid,ppid,ni,cmd
PID PPID NI CMD
4993 4721 10 /bin/bash ./test4.sh
$
有时你想改变系统上已运行命令的优先级。这正是renice命令可以做到的。它允许你指定
运行进程的PID来改变它的优先级。
$ ./test11.sh &
[1] 5055
$
$ ps -p 5055 -o pid,ppid,ni,cmd
PID PPID NI CMD
5055 4721 0 /bin/bash ./test11.sh
$
$ renice -n 10 -p 5055
5055: old priority 0, new priority 10
$
$ ps -p 5055 -o pid,ppid,ni,cmd
PID PPID NI CMD
5055 4721 10 /bin/bash ./test11.sh
$
renice命令会自动更新当前运行进程的调度优先级。和nice命令一样,renice命令也有一
些限制:
只能对属于你的进程执行renice;
只能通过renice降低进程的优先级;
root用户可以通过renice来任意调整进程的优先级。
如果想完全控制运行进程,必须以root账户身份登录或使用sudo命令。
cron时间表
cron时间表采用一种特别的格式来指定作业何时运行。其格式如下:
min hour dayofmonth month dayofweek command
cron时间表允许你用特定值、取值范围(比如1~5)或者是通配符(星号)来指定条目。例
如,如果想在每天的10:15运行一个命令,可以用cron时间表条目:
15 10 * * * command
如何在每个月最后一天执行,常用的方法是加一条使用date命令的if-then语句来检查明天的日期是不是01:
00 12 * * * if [`date +%d -d tomorrow` = 01 ] ; then ; command
它会在每天中午12点来检查是不是当月的最后一天,如果是,cron将会运行该命令。
Linux提供
了crontab命令来处理cron时间表。要列出已有的cron时间表,可以用-l选项。
$ crontab -l
no crontab for rich
$
要为cron时间表添加条目,可以用-e选项。
可以指定一个整数值来定义函数的退出状态码,但当用这种方法从函数中返回值时,要小心了。记住下面两条技巧来避免问题:
记住,函数一结束就取返回值;
记住,退出状态码必须是0~255。
$ cat test5
#!/bin/bash
# using the return command in a function
function dbl {
read -p "Enter a value: " value
echo "doubling the value"
return $[ $value * 2 ]
}
dbl
echo "The new value is $?"
$
$ ./test5
Enter a value: 2
doubling the value
The new value is 4
$
正如可以将命令的输出保存到shell变量中一样,你也可以对函数的输出采用同样的处理办
法。可以用这种技术来获得任何类型的函数输出,并将其保存到变量中:
result='dbl'
这个命令会将dbl函数的输出赋给$result变量。下面是在脚本中使用这种方法的例子。
$ cat test5b
#!/bin/bash
# using the echo to return a value
function dbl {
read -p "Enter a value: " value
echo $[ $value * 2 ]
}
result=$(dbl)
echo "The new value is $result"
$
$ ./test5b
Enter a value: 200
The new value is 400
$
$ ./test5b
Enter a value: 1000
The new value is 2000
$
新函数会用echo语句来显示计算的结果。该脚本会获取dbl函数的输出,而不是查看退出状
态码。
这个例子中演示了一个不易察觉的技巧。你会注意到dbl函数实际上输出了两条消息。read
命令输出了一条简短的消息来向用户询问输入值。bash shell脚本非常聪明,并不将其作为STDOUT输出的一部分,并且忽略掉它。如果你用echo语句生成这条消息来向用户查询,那么它会与输出值一起被读进shell变量中。
说明 通过这种技术,你还可以返回浮点值和字符串值。这使它成为一种获取函数返回值的强
大方法。
默认情况下,你在脚本中定义的任何变量都是全局变量。在函数外定义的变量可在函数内正
常访问。
$ cat test8
#!/bin/bash
# using a global variable to pass a value
function dbl {
value=$[ $value * 2 ]
}
read -p "Enter a value: " value
dbl
echo "The new value is: $value"
$
$ ./test8
Enter a value: 450
The new value is: 900
$
$value变量在函数外定义并被赋值。当dbl函数被调用时,该变量及其值在函数中都依然有
效。如果变量在函数内被赋予了新值,那么在脚本中引用该变量时,新值也依然有效。
函数内部使用的任何变量都可以被声明成局部变量。要实现这一点,只要在变量声明的前面加上local关键字就可以了。temp为变量
local temp
也可以在变量赋值语句中使用local关键字:
local temp=$[ $value + 5 ]
local关键字保证了变量只局限在该函数中。如果脚本中在该函数之外有同样名字的变量,
那么shell将会保持这两个变量的值是分离的。现在你就能很轻松地将函数变量和脚本变量隔离开
了,只共享需要共享的变量。
$ cat test9
#!/bin/bash
# demonstrating the local keyword
function func1 {
local temp=$[ $value + 5 ]
result=$[ $temp * 2 ]
}
temp=4
value=6
func1
echo "The result is $result"
if [ $temp -gt $value ]
then
echo "temp is larger"
else
echo "temp is smaller"
fi
$
$ ./test9
The result is 22
temp is smaller
$
现在,在func1函数中使用$temp变量时,并不会影响在脚本主体中赋给$temp变量的值。
你必须将该数组变量的值分解成单个的值,然后将这些值作为函数参数使
用。在函数内部,可以将所有的参数重新组合成一个新的变量。下面是个具体的例子。
$ cat test10
#!/bin/bash
# array variable to function test
function testit {
local newarray
newarray=(;'echo "$@"')
echo "The new array value is: ${newarray[*]}"
}
myarray=(1 2 3 4 5)
echo "The original array is ${myarray[*]}"
testit ${myarray[*]}
$
$ ./test10
The original array is 1 2 3 4 5
The new array value is: 1 2 3 4 5
$
该脚本用$myarray变量来保存所有的数组元素,然后将它们都放在函数的命令行上。该函
数随后从命令行参数中重建数组变量。在函数内部,数组仍然可以像其他数组一样使用。
$ cat test11
#!/bin/bash
# adding values in an array
function addarray {
local sum=0
local newarray
newarray=($(echo "$@"))
for value in ${newarray[*]}
do
sum=$[ $sum + $value ]
done
echo $sum
}
myarray=(1 2 3 4 5)
echo "The original array is: ${myarray[*]}"
arg1=$(echo ${myarray[*]})
result=$(addarray $arg1)
echo "The result is $result"
$
$ ./test11
The original array is: 1 2 3 4 5
The result is 15
$
addarray函数会遍历所有的数组元素,将它们累加在一起。你可以在myarray数组变量中
放置任意多的值,addarry函数会将它们都加起来。
创建函数库就是吧一个函数写在一个固定的文件里,然后再脚本中使用时直接调用即可。
这里有个叫作myfuncs的库文件,它定义了3个简单的函数。
$ cat myfuncs
# my script functions
function addem {
echo $[ $1 + $2 ]
}
function multem {
echo $[ $1 * $2 ]
}
function divem {
if [ $2 -ne 0 ]
then
echo $[ $1 / $2 ]
else
echo -1
fi
}
$
使用函数库的关键在于source命令。source命令会在当前shell上下文中执行命令,而不是创建一个新shell。可以用source命令来在shell脚本中运行库文件脚本。这样脚本就可以使用库中的函数了。
source命令有个快捷的别名,称作点操作符(dot operator)。要在shell脚本中运行myfuncs库文件,只需添加下面这行:
. ./myfuncs
这个例子假定myfuncs库文件和shell脚本位于同一目录。如果不是,你需要使用相应路径访问该文件。这里有个用myfuncs库文件创建脚本的例子。
$ cat test14
#!/bin/bash
# using functions defined in a library file
. ./myfuncs
value1=10
value2=5
result1=$(addem $value1 $value2)
result2=$(multem $value1 $value2)
result3=$(divem $value1 $value2)
echo "The result of adding them is: $result1"
echo "The result of multiplying them is: $result2"
echo "The result of dividing them is: $result3"
$
$ ./test14
The result of adding them is: 15
The result of multiplying them is: 50
The result of dividing them is: 2
$
该脚本成功地使用了myfuncs库文件中定义的函数。
可以在命令行上直接定义一个函数。有两种方法。
一种方法是采用单行方式定义函数。
$ function divem { echo $[ $1 / $2 ]; }
$ divem 100 5
20
$
另一种方法是采用多行方式来定义函数。在定义时,bash shell会使用次提示符来提示输入更多命令。用这种方法,你不用在每条命令的末尾放一个分号,只要按下回车键就行。
$ function multem {
> echo $[ $1 * $2 ]
> }
$ multem 2 5
10
$
注意在命令行定义函数时不要和系统的内建命令重复,否则函数会覆盖之前的内建命令。
bash shell在每次启动时都会在主目录下查找这个文件,不管是交互式shell还是从现有shell中启动的新shell。所以把函数定义在这个文件结尾,就可以在命令行和脚本中直接使用,也不需要在脚本中使用source或者点操作符操作库文件。
可以在网上找别人写好的函数库拿来直接用,这样更节省时间
下载库shtool软件包的下载地址是:
ftp://ftp.gnu.org/gnu/shtool/shtool-2.0.8.tar.gz
完成复制操作后,使用tar命令提取文件。
tar -zxvf shtool-2.0.8.tar.gz
该命令会将打包文件中的内容提取到shtool-2.0.8目录中。接下来就可以构建shell脚本库文件。配置工作必须使用标准的configure和make命令
$ ./confifgure
$ make
configure命令会检查构建shtool库文件所必需的软件。一旦发现了所需的工具,它会使用
工具路径修改配置文件。
make命令负责构建shtool库文件。最终的结果(shtool)是一个完整的库软件包。你也可
以使用make命令测试这个库文件。
$ make test
Running test suite:
echo...........ok
mdate..........ok
table..........ok
prop...........ok
。。。。
$
如果全部通过测试,就可以将库安装到Linux系统中
的公用位置,这样所有的脚本就都能够使用这个库了。要完成安装,需要使用make命令的
install选项。
clear命令用当前终端会话的terminfo数据来清理出现在屏幕上的文本。运行
clear命令之后,可以用echo命令来显示菜单元素。
echo用-e选项可以显示非可打印字符,例如制表符和换行符。
echo -e "1.\tDisplay disk space"
会生成如下输出行:
最后一行的-en选项会可以掉末尾的换行符。
clear
echo
echo -e "\t\t\tSys Admin Menu\n"
echo -e "\t1. Display disk space"
echo -e "\t2. Display logged on users"
echo -e "\t3. Display memory usage"
echo -e "\t0. Exit menu\n\n"
echo -en "\t\tEnter option: "
read -n 1 option
在read命令中用了-n选项来限制只读取一个字符。这样用户只需要输入一个
数字,也不用按回车键:
read -n 1 option
接下来,你需要创建自己的菜单函数。
还有一点有助于制作shell脚本菜单,那就是将菜单布局本身作为一个函数来创建。
function menu {
clear
echo
echo -e "\t\t\tSys Admin Menu\n"
echo -e "\t1. Display disk space"
echo -e "\t2. Display logged on users"
echo -e "\t3. Display memory usage"
echo -e "\t0. Exit program\n\n"
echo -en "\t\tEnter option: "
read -n 1 option
}
这样一来,任何时候你都能调用menu函数来重现菜单。
创建菜单图像化界面工具有很多,
有dialog程序,
对KDE桌面来说,kdialog程序,
对GNOME桌面来说,有gdialog和zenity程序
记住,sed编辑器不会修改原始文件。所做的任何修改只是从sed编辑器的输出中修改,并不会修改源文件
sed命令的格式如下。
sed options script file
选项允许你修改sed命令的行为,可以使用的选项已在下面列出。
sed命令选项
选 项 描 述
-e script 在处理输入时,将script中指定的命令添加到已有的命令中
-f file 在处理输入时,将file中指定的命令添加到已有的命令中
-n 不产生命令输出,使用print命令来完成输出
常用的使用方式
sed 's/dog/cat/' data1.txt #修改文件中所有的dog为cat。但不会修改源文件
在sed命令行上执行多个命令时,只要用-e选项
$ sed -e 's/brown/green/; s/dog/cat/' data1.txt
两个命令都作用到文件中的每行数据上。命令之间必须用分号隔开,并且在命令末尾和分号之间不能有空格。
文件结尾使用.sed便于查看是什么文件。
每行都是一条单独的命令
在sed命令中用-f选项来指定文件。
$ cat script1.sed
s/brown/green/
s/fox/elephant/
s/dog/cat/
$
$ sed -f script1.sed data1.txt
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
$
在这种情况下,不用在每条命令后面放一个分号。sed编辑器知道每行都是一条单独的命令。跟在命令行输入命令一样,sed编辑器会从指定文件中读取命令,并将它们应用到数据文件中的每一行上。
如果一行存在多个匹配的词,默认只会替换第一个,本行其余的匹配词不做修改。
基本命令格式
s/pattern/replacement/flags
命令中的/可以用任意符号替代。
s#pattern#replacement#flags
有4种可用的替换标记:
数字,表明新文本将替换第几处模式匹配的地方;
g,表明新文本将会替换所有匹配的文本;
p,表明原先行的内容要打印出来;
w file,将替换的结果写到文件中。
数字的使用方法
$ sed 's/test/trial/2' data4.txt
This is a test of the trial script.
This is the second test of the trial script.
$
g的使用方法
$ sed 's/test/trial/g' data4.txt
This is a trial of the trial script.
This is the second trial of the trial script.
$
p的使用方法,和-n选项一同使用,n选项是禁止sed编辑器输出,P表示只打印修改那行
$ cat data5.txt
This is a test line.
This is a different line.
$
$ sed -n 's/test/trial/p' data5.txt
This is a trial line.
$
w替换标记会产生同样的输出,不过会将输出保存到指定文件中。
$ sed 's/test/trial/w test.txt' data5.txt
This is a trial line.
This is a different line.
$
$ cat test.txt
This is a trial line.
$
会把替换的行保存到test.txt中
在sed编辑器中有两种形式的行寻址:
以数字形式表示行区间
用文本模式来过滤出行
两种形式都使用相同的格式来指定地址:
[address]command
也可以将特定地址的多个命令分组:
address {
command1
command2
command3
}
数字形式的行寻址(ns###)
指定行号的例子。
$ sed '2s/dog/cat/' data1.txt
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
$
指定行地址区间的例子(n,ns###)
$ sed '2,3s/dog/cat/' data1.txt
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy dog
$
从某行开始的所有行,可以用特殊地址——美元符(n,$s###)
$ sed '2,$s/dog/cat/' data1.txt
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy cat
$
使用文本模式过滤器
格式如下:
/pattern/command
必须用正斜线将要指定的pattern封起来
只修改用户Samantha的默认shell,可以使用sed命令。
$ grep Samantha /etc/passwd
Samantha:x:502:502::/home/Samantha:/bin/bash
$
$ sed '/Samantha/s/bash/csh/' /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
[...]
Christine:x:501:501:Christine B:/home/Christine:/bin/bash
Samantha:x:502:502::/home/Samantha:/bin/csh
Timothy:x:503:503::/home/Timothy:/bin/bash
$
多条命令合并使用
这里举例了一个指定行修改的例子,其他使用方法和数字形式行寻址一样。
$sed '2{
> s/fox/elephant/
> s/dog/cat/
> }' data1.txt
The quick brown fox jumps over the lazy dog.
The quick brown elephant jumps over the lazy cat.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
$
指定地址区间
$ sed '3,${
> s/brown/green/
> s/lazy/active/
> }' data1.txt
使用匹配模式时,如果文本中包含多个匹配文字,会多次打开编辑,并不是编辑一次,
使用d命令,如果不加入寻址模式的话,会把sed流编辑器中的所有文本删除。
$ cat data1.txt
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
$
$ sed 'd' data1.txt
$
从数据流中删除特定的文本行,通过行号指定:
$ cat data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
$
$ sed '3d' data6.txt
This is line number 1.
This is line number 2.
This is line number 4.
$
指定行区间
$ sed '2,3d' data6.txt
指定某行到文件结尾
$ sed '3,$d' data6.txt
sed编辑器的文本模式匹配特性也适用于删除命令。
$ sed '/number 1/d' data6.txt
This is line number 2.
This is line number 3.
This is line number 4.
$
格式如下:
sed '[address]command\new line'
插入(insert)命令(i)会在指定行前增加一个新行;
$ echo "Test Line 2" | sed 'i\Test Line 1'
Test Line 1
Test Line 2
$
附加(append)命令(a)会在指定行后增加一个新行。
$ echo "Test Line 2" | sed 'a\Test Line 1'
Test Line 2
Test Line 1
$
指定行插入
$ sed '3i\This is an inserted line.' data6.txt
指定行附加
$ sed ‘3a\This is an append line.’ data6.txt
添加到行尾使用$符号
$ sed '$a\This is a new line of text.' data6.txt
指定第三行修改
$ sed '3c\This is a changed line of text.' data6.txt
This is line number 1.
This is line number 2.
This is a changed line of text.
This is line number 4.
$
使用文本匹配模式
$ sed '/number 3/c\This is a changed line of text.' data6.txt
转换命令格式如下。
[address]y/inchars/outchars/
使用转换命令的简单例子。
$ sed 'y/123/789/' data8.txt
This is line number 7.
This is line number 8.
This is line number 9.
This is line number 4.
This is line number 7 again.
This is yet another line.
This is the last line in the file.
$
sed编辑器转换了在文本行中匹配到的字符1的两个实例。你无法限定只转换在特定地方出现
的字符。
# echo "this is a 1 or 2 or 1" |sed 'y#123#jkl#'
this is a j or k or j
打印命令最常见的用法是打印包含匹配文本模式的行。
$ cat data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
$
$ sed -n '/number 3/p' data6.txt
This is line number 3.
$
每次数据流中出现一个换行符,就认为一行文本结束了
$ cat data1.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
$ sed '=' data1.txt
1
The quick brown fox jumps over the lazy dog.
2
The quick brown fox jumps over the lazy dog.
多条命令一起使用
$ sed -n '/number 4/{
> =
> p
> }' data6.txt
4
This is line number 4.
$
命令(l)可以打印数据流中的文本和不可打印的ASCII字符。任何不可打印字符要么在其八进制值前加一个反斜线,要么使用标准C风格的命名法(用于常见的不可打印字符),比如\t,来代表制表符。
$ cat data9.txt
This line contains tabs.
$
$ sed -n 'l' data9.txt
This\tline\tcontains\ttabs.$
$
如果数据流包含了转义字符,列出命令会在必要时候用八进制码来显示。
$ cat data10.txt
This line contains an escape character.
$
$ sed -n 'l' data10.txt
This line contains an escape character. \a$
$
data10.txt文本文件包含了一个转义控制码来产生铃声。当用cat命令来显示文本文件时,你看不到转义控制码,只能听到声音(如果你的音箱打开的话)。但是,利用列出命令,你就能显示出所使用的转义控制码。
sed会把data12.txt中的文本插入到data6.txt文本的第3行后面。
$ cat data12.txt
This is an added line.
This is the second added line.
$
$ sed '3r data12.txt' data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is an added line.
This is the second added line.
This is line number 4.
$
文本匹配模式的用法
sed ‘/number 3/r data12.txt’ data6.txt
添加到行尾的用法
sed ‘$r data12.txt’ data6.txt
和删除命令配合使用,能在匹配的行后面添加内容,再删除匹配的内容
$ cat notice.std
Would the following people:
LIST
please report to the ship's captain.
$
$ sed '/LIST/{
> r data11.txt
> d
> }' notice.std
Would the following people:
Blum, R Browncoat
McGuiness, A Alliance
Bresnahan, C Browncoat
Harken, C Alliance
please report to the ship's captain.
$
把notice.std中的LIST替换为data11.txt的内容并删除
单行使用小写n。先做匹配,然后用n移动到下一行,对下一行做处理。
单行next命令的使用,下面的文件中包含了2行空白,只删除第一行空白,保留第二行
$ cat data1.txt
This is the header line.
This is a data line.
This is the last line.
$
使用header匹配第一行,再用n命令移动到文本的下一行,进行处理。
$ sed '/header/{n ; d}' data1.txt
This is the header line.
This is a data line.
This is the last line.
$
多行使用大写N,先匹配,然后再用N把下一行合并到这行,当作一行来处理。
$ cat data2.txt
This is the header line.
This is the first data line.
This is the second data line.
This is the last line.
$
$ sed '/first/{ N ; s/\n/ / }' data2.txt
This is the header line.
This is the first data line. This is the second data line.
This is the last line.
$
如果在文本中查找并替换可能分散在两行的一个短语,可以使用下面命令。
先使用单行匹配,如果没有匹配到,再使用多行匹配,这样可以保证在替换时不修改格式。
因为要保证原始格式,有可能是一行,也有可能是两行。
$ sed '
> s/System Administrator/Desktop User/
> N
> s/System\nAdministrator/Desktop\nUser/
> ' data4.txt
如果删除上一行的空白
它会删除数据流中出现在第一行前的空白行。
$ cat data5.txt
This is the header line.
This is a data line.
This is the last line.
$
$ sed '/^$/{N ; /header/D}' data5.txt
This is the header line.
This is a data line.
This is the last line.
$
D,它只删除模式空间中的第一行。该命令会删除到换行符(含换行符)为止的所有字符。
用法:
sed 'N ; /System\nAdministrator/D' data4.txt
删除文本的前一个空白行
sed ‘/^$/{N;/System/D}’ data.txt
它只打印多行模式空间中的第一行。在模式空间中直到换行符为止的所有字符。
$ sed -n 'N ; /System\nAdministrator/P' data3.txt
On Tuesday, the Linux System
$
N、D、P命令组合在一起使用才能发挥奇效
sed编辑器的保持空间命令
命 令 描 述
h 将模式空间复制到保持空间
H 将模式空间附加到保持空间
g 将保持空间复制到模式空间
G 将保持空间附加到模式空间
x 交换模式空间和保持空间的内容
sed -n '/first/ {h ; p ; n ; p ; g ; p }' data2.txt
打印出不匹配header的行
sed -n '/header/!p' data2.txt
用!处理使用N时无法处理的最后一行数据
$ sed 'N;
> s/System\nAdministrator/Desktop\nUser/
> s/System Administrator/Desktop User/
> ' data4.txt
On Tuesday, the Linux Desktop
User's group meeting will be held.
All System Administrators should attend.
$
$ sed '$!N;
> s/System\nAdministrator/Desktop\nUser/
> s/System Administrator/Desktop User/
> ' data4.txt
On Tuesday, the Linux Desktop
User's group meeting will be held.
All Desktop Users should attend.
$
下面的命令可以反转打印文本内容,虽然tac也能做到。
sed -n '{1!G ; h ; $p }' data2.txt
分支(branch)命令b的格式如下:
[address]b [label]
address参数决定了哪些行的数据会触发分支命令。label参数定义了要跳转到的位置。如果没有加label参数,跳转命令会跳转到脚本的结尾。注意在命令行使用label参数时不要一次写完,会提示错误。
$ cat data2.txt
This is the header line.
This is the first data line.
This is the second data line.
This is the last line.
$
$ sed '{2,3b ; s/This is/Is this/ ; s/line./test?/}' data2.txt
Is this the header test?
This is the first data line.
This is the second data line.
Is this the last test?
$
分支命令在数据流中的第2行和第3行处跳过了两个替换命令。
$ sed '{/first/b jump1 ; s/This is the/No jump on/
> :jump1
> s/This is the/Jump here on/}' data2.txt
No jump on header line
Jump here on first data line
No jump on second data line
No jump on last line
$
上面的命令解释:
首先符合first的行会跳过s/This is the/No jump on/
跳转到jump1的命令块中,
:jump1后面就是jump1的命令块 即s/This is the/Jump here on/
还可以制作循环效果,下面的例子是每次删除一个逗号
$ echo "This, is, a, test, to, remove, commas." | sed -n '{
> :start
> s/,//1p
> b start
> }'
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
^C
$
注意b前面没有匹配的模式,所以一直匹配成功,就一直持续跳到start标识的命令块。所以会一直循环,需要使用ctrl+c停止。
如果在b钱前面添加一个匹配模式,如果没有匹配到,就跳到脚本结尾。
$ echo "This, is, a, test, to, remove, commas." | sed -n '{
> :start
> s/,//1p
> /,/b start
> }'
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
$
另一种方式是使用t命令
$ echo "This, is, a, test, to, remove, commas. " | sed -n '{
> :start
> s/,//1p
> t start
> }'
测试命令会根据替换命令的结果跳转到某个标签,如果替换命令成功匹配并替换了一个模式,测试命令就会跳转到指定的标签。如果未能匹配,测试命令就不会跳转。
测试命令的使用格式。
[address]t [label]
$ sed '{
> s/first/matched/
> t
> s/This is the/No match on/
> }' data2.txt
No match on header line
This is the matched data line
No match on second data line
No match on last line
$
first如果成功替换了matched,就执行s/This is the/No match on/。
可以使用&符号来替换刚才匹配到的单词。
给想匹配的单词增加双引号。
$ echo "The cat sleeps in his hat." | sed 's/.at/"&"/g'
The "cat" sleeps in his "hat".
$
使用.通配符匹配了cat和hat,然后用&符号代替了cat和hat,并增加了双引号。
sed编辑器用圆括号来定义替换模式中的子模式,在替代模式中使用特殊字符来引用每个子模式。替代字符由反斜线和数字组成。数字表明子模式的位置。sed编辑器会给第一个子模式分配字符\1,给第二个子模式分配字符\2,依此类推。
把(System)替换成了.
# echo "The (System) Administrator manual"|sed 's#(System)#.#'
The . Administrator manual
用替换模式的子模式标记了System,并替换成了.
# echo "The (System) Administrator manual"|sed 's#\(System\)#.#'
The (.) Administrator manual
用替换模式的子模式标记了System,并替换成了添加了一个User,添加在括号中
# echo "The (System) Administrator manual"|sed 's#\(System\)#\1 User#'
The (System User) Administrator manual
用替换模式的子模式标记了System,并替换成了添加了一个User,添加在括号外
# echo "The (System) Administrator manual"|sed 's#\((System)\)#\1 User#'
The (System) User Administrator manual
下面这个脚本在数字中插入了逗号,使用了label参数,
$ echo "1234567" | sed '{
> :start
> s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/
> t start
> }'
1,234,567
$
个人修改了一下,可以在每个数字后都添加逗号,共有两种方式
# echo "123456789" |sed '{
:start
s#\(.*[0-9]\)\([0-9]\{1\}\)#\1,\2#
t start
}'
1,2,3,4,5,6,7,8,9
# echo "123456789" |sed '{
:start
s#\(.*[0-9]\)\([0-9]\+\)#\1,\2#
t start
}'
1,2,3,4,5,6,7,8,9
如果看不懂过程可以使用P打印出来,
s#\(.*[0-9]\)\([0-9]\{1\}\)#\1,\2#p
gawk程序的基本格式如下:
gawk options program file
gawk选项
选 项 描 述
-F fs 指定行中划分数据字段的字段分隔符
-f file 从指定的文件中读取程序
-v var=value 定义gawk程序中的一个变量及其默认值
-mf N 指定要处理的数据文件中的最大字段数
-mr N 指定数据文件中的最大数据行数
-W keyword 指定gawk的兼容模式或警告等级
下面的例子在命令行上指定了一个简单的gawk程序脚本:
$ gawk '{print "Hello World!"}'
但是回车后你会发现,并没有什么输出,如果你尝试的输入一些文字,再回车,会发现只打印Hello word!原因在于没有在命令行上指定文件名,所以gawk程序会从STDIN接收数据。在运行这个程序时,它会一直等待从STDIN输入的文本。
如果你输入一行文本并按下回车键,gawk会对这行文本运行一遍程序脚本。跟sed编辑器一样,gawk程序会针对数据流中的每行文本执行程序脚本。由于程序脚本被设为显示一行固定的文本字符串,因此不管你在数据流中输入什么文本,都会得到同样的文本输出。
要终止这个gawk程序,你必须表明数据流已经结束了。bash shell提供了一个组合键来生成
EOF(End-of-File)字符。Ctrl+D组合键会在bash中产生一个EOF字符。这个组合键能够终止该gawk程序并返回到命令行界面提示符下。
默认情况下,gawk会将如下变量分配给它在文本行中发现的数据字段:
$0代表整个文本行;
$1代表文本行中的第1个数据字段;
$2代表文本行中的第2个数据字段;
$n代表文本行中的第n个数据字段。
下面的例子只取了文件中每一行的第一个字段。
$ cat data2.txt
One line of test text.
Two lines of test text.
Three lines of test text.
$
$ gawk '{print $1}' data2.txt
One
Two
Three
-F选项可以指定字段分隔符
$ gawk -F: '{print $1}' /etc/passwd
root
bin
daemon
…
$ echo "My name is Rich" | gawk '{$4="Christine"; print $0}'
My name is Christine
$
文件结尾使用.gawk便于查看是什么文件
每行都是一条单独的命令
$ cat script2.gawk
{print $1 "'s home directory is " $6}
$
$ gawk -F: -f script2.gawk /etc/passwd
root's home directory is /root
bin's home directory is /bin
daemon's home directory is /sbin
adm's home directory is /var/adm
lp's home directory is /var/spool/lpd
[...]
Christine's home directory is /home/Christine
Samantha's home directory is /home/Samantha
Timothy's home directory is /home/Timothy
$
在gawk程序中引用变量不需要加$符号。
{print $1 "'s home directory is " $6}
也可以写成
{
text = "'s home directory is "
print $1 text $6
}
BEGIN关键字字允许你指定一个程序脚本,gawk会在读完数据前执行它。
$ gawk 'BEGIN {print "The data3 File Contents:"}
> {print $0}' data3.txt
The data3 File Contents:
Line 1
Line 2
Line 3
$
END关键字允许你指定一个程序脚本,gawk会在读完数据后执行它。
$ gawk 'BEGIN {print "The data3 File Contents:"}
> {print $0}
> END {print "End of File"}' data3.txt
The data3 File Contents:
Line 1
Line 2
Line 3
End of File
$
下面是一个完整的例子,同时使用了BEGIN和END关键字
$ cat script4.gawk
BEGIN {
print "The latest list of users and shells"
print " UserID \t Shell"
print "-------- \t -------"
FS=":"
}
{
print $1 " \t " $7
}
END {
print "This concludes the listing"
}
$
awk -v OFS='\t' '{$1=$1;print $3,$6,$7}' data.txt
分别打印出$3,$6,$7,并以制表符作为分隔符
# echo "1 2 3 4 5 6 7 8 9"|awk -v OFS='\t' '{$1=$1;print $3,$5,$7}'
3 5 7
正则表达式匹配时区分大小写,空格也是一个字符,可以使用空格和数字
甚至可以创建匹配多个连续空格的正则表达式模式。
纯文本匹配时,需要注意表达式多余数据内容不能匹配
$ echo "The book is expensive" | sed -n '/books/p'
$
匹配的是books,数据中是book,所以没有结果
$ echo "The books are expensive" | sed -n '/book/p'
The books are expensive
$
匹配的是book,数据中是books,所以可以匹配
. * [ ] ^ $ { } \ + ? | ( )
.表示必须任意一个字符,如果点号位置没有字符,则不能匹配。匹配的是.前面的字符
*表示匹配0次或多次
.*能匹配任意多个字符
?匹配0次或1次
+匹配1次或多次,至少1次
^表示从数据流中文本行的行首开始的模式。如果模式出现在行首之外的位置,正则表达式模式则无法匹配。会在每个由换行符决定的新数据行的行首检查模式。
$ cat data3
This is a test line.
this is another test line.
A line that tests this feature.
Yet more testing of this
$ sed -n '/^this/p' data3
this is another test line.
$
$表示从行尾查找。将这个特殊字符放在文本模式之后来指明数据行必须以该文本模式结尾。
$ echo "This is a good book" | sed -n '/book$/p'
This is a good book
注意要想匹配,文本模式必须是行的最后一部分。
下面的例子,尽管book在数据中,但数据不是以book结尾的,所以无法匹配。
$ echo "There are a lot of good books" | sed -n '/book$/p'
$
^$可以组合使用,
^$中间加字符可以匹配指定的行
$ cat data4
this is a test of using both anchors
I said this is a test
this is a test
$ sed -n '/^this is a test$/p' data4
this is a test
$
^$中间什么都不加可匹配空行,
$ cat data5
This is one test line.
This is another test line.
$ sed '/^$/d' data5
This is one test line.
This is another test line.
$
[]表示字符组,匹配该字符组中的一个,如果在[]中,字符前加^表示排除这些字符。
[0-24-6]表示匹配0-2和4-6中的一个,其余数字不匹配。
[Aa]表示匹配A或a
[^a-k]表示排除a-k,匹配其余字母。
sed –n ‘/[0-24-6]/p’ data.txt
sed -n '/[Yy]es/p'
组 描 述
[[:alpha:]] 匹配任意字母字符,不管是大写还是小写
[[:alnum:]] 匹配任意字母数字字符0~9、A~Z或a~z
[[:blank:]] 匹配空格或制表符
[[:digit:]] 匹配0~9之间的数字
[[:lower:]] 匹配小写字母字符a~z
[[:print:]] 匹配任意可打印字符
[[:punct:]] 匹配标点符号
[[:space:]] 匹配任意空白字符:空格、制表符、NL、FF、VT和CR
[[:upper:]] 匹配任意大写字母字符A~Z
echo "abc" | sed -n '/[[:digit:]]/p'
你不用为每个需要创建的新用户账户手动输入useradd命令,而是可以将需要添加的新用户账户放在一个文本文件中,然后创建一个简单的脚本进行处理。这个文本文件的格式如下:
userid,user name
$ cat users.csv
rich,Richard Blum
christine,Christine Bresnahan
barbara,Barbara Blum
tim,Timothy Bresnahan
$
我们将IFS分隔符设置成逗号,并将其放入while语句的条件测试部分。然后使用read命令读取文件中的各行。实现代码如下:
while IFS=’,’ read –r userid name
read命令会自动读取.csv文本文件的下一行内容,所以不需要专门再写一个循环来处理。当
read命令返回FALSE时(也就是读取完整个文件时),while命令就会退出。要想把数据从文件中送入while命令,只需在while命令尾部使用一个重定向符就可以了。
将各部分处理过程写成脚本如下。
$ cat test26
#!/bin/bash
# process new user accounts
input="users.csv"
while IFS=',' read -r userid name
do
echo "adding $userid"
useradd -c "$name" -m $userid
done < "$input"
$
#!/usr/bin/env bash
# ******************************************************
# Author : haoyongnan
# Version : 1.0
# Last modified: 2011-07-17 03:33
# Email : [email protected]
# Telephone : 17600186556
# Filename : checkXfile.sh
# Description :
# ******************************************************
IFS=:
for folder in $PATH
do
for file in $folder/*
do
if [ -x $file ];then
echo "$fiel is X fiel"
fi
done
done