为编写Linux上的Shell脚本,需要对Linux系统上的一些常用的操作命令有所了解,这里不会专门进行介绍。但是,在编写中也能大致了解各种命令的使用方式。

1 简要介绍

在Linux上的脚本语言几乎默认为bash,其实还有其它这类的Shell语言,但是bash在这其中属于功能齐全且易学的。其文件以 .sh为结尾,原本有一个Shell语言就是叫sh,最初运行在Unix系统上,而bash则是容纳了sh的所有命令并有所扩充。因此,sh和bash在Unix上是两种不同的语言。那么在Linux上则无需考虑过多。

至于文件的编写,网上或书本可能会介绍eclipse等编辑器,又或是介绍windows上的一些脚本模拟器。但实际上,如果需要彻底地了解Linux的编程,最好租用一个Linux的服务器,不需要太高的性能,价格也不会太贵,既可以使用Linux上的vi/vim编辑器,也可以直接运行操作,如果不习惯vim类的编辑器,直接用windows的文本编辑器也是完全可以的。

2 脚本基础操作

2.1 编写

在编写最开始,首先要表明这是一个bash语言的脚本,在文件第一行写上,

因为bash包含了sh语言,也可以写#!/bin/sh。不过最好按上述的内容写。#!后跟的是解释器,针对语言的不同,可填写不同程序语言的对应目录。

其次,符号 #作为注释符号存在,不直接参与命令操作。

为了脚本能输出内容,采用命令 echo后跟需要输出的内容,最好带双引号。

在完成编写后,首先需要更改文件权限,默认这类文件无法执行,

运行脚本,只需要在文件目录前加上点 .,假设存在一个文件 /usr/bin/test.sh

若是当前目录在bin中,则直接用命令 ./test.sh执行。此时,即使未设置执行权限,也可用命令 source 文件名执行脚本。

2.2 变量

bash中的变量属于动态型,不需要事先指明类型,类似python这类语言。变量基本上就是整数型与字符串型,但本质上都是字符串型。 使用时,自定义变量名并赋值后,使用时需要在变量名前加上符号 $,例如,输出数字或字符串,

需注意的是,赋值时 =两边不能有空格。此外,=右边可以不加值,即赋值为空;也可以不事先声明变量而直接引用,但是前面的变量通过 命令 set可显示出所有存在的变量,没有事先声明的变量是不会出现的。若是需要消除脚本中定义的变量,通过 unset 变量名实现。 引用时,若是变量名存在可能的歧义,可使用花括号 {}将其包含在内。

运行后,

当输入时不加上双引号,则中间的空格全都视为一个空格。 其次,前面的例子中提示用户 ‘输入值’,可将该值合并到 read命令中,read -p 提示信息 变量名,提示信息可以用引用变量替代。

有时需要限制用户的输入时间,则用命令 read -t 秒数 -n 字符个数 -p "快点写且不要多写" 变量,该命令除了指定时限,也指定了输入的个数,当字符数足够后将自动退出。

另外,read包括选项 -r允许输入反斜杠 ,-d后跟一个双引号括起来的符号,作为输入行的定界符。 在使用中,会发现在输入密码时,系统总是不做显示,使用的是选项 -s,为静默模式。

由于bash的变量本质是字符串,上述定义的变量即使赋值了数字,首先也是作为字符串处理,但同时的是,bash又会当字符串在做数值运算时尝试看作数字。 为了明确变量为数字,使用命令 declare -i 变量名

最后,还可设置变量仅可读,readonly 变量 赋值 或者 declare -r 变量 赋值,此后,变量不可更改,也不可取消定义。为显示所有只读变量,使用命令 readonly -pdeclare -rpdeclare -p可显示变量的属性和值。

除了自定义变量外,还存在系统的环境变量,即系统中本身就存在的变量,在脚本中可直接引用,可查询一些常用的环境变量。或者用 set显示出当前系统中存在的所有的环境变量,可能有非常多。最好根据需要查询是否存在对应的环境变量。

为了保证是作为数字操作,需将运算放在命令 let后。 或者放在 $(())的括号中,其中$(())的结果也可赋值给变量,若是使用 expr 后跟着表达式来计算时,则不需要里面的小括号。

2.2.1 数组

bash支持数组,但支持一维数组。数组内元素需保证类型统一。

readarraymapfile同样可以将键盘输入作为数组,不同点在于,这两个命令以行为分隔。

此外可通过命令 declare -a创建数组,不需要在创建时指明元素。类似于不同的编程,可在之后针对各个下标赋值,同时下标可不连续。

${#w[*]}显示元素个数,${w[*]}显示所有元素的值, ${!w[*]}显示数组的所有下标, ${w[@]}同样显示所有元素的值,不同在于,${w[*]}将结果作为字符串输出,而后者将结果按空格分隔一个个输出。

最后,同样可用 readonlydeclare -ar声明只读数组。

关于declare的其它命令,可使用 declare --help查看。

2.3 引号

其中反引号 `` 已介绍是用来包括命令,如 date等。 单引号 '',内部的字符全都看作普通字符。 双引号 "",对符号 $\'`,`"仍保存其特殊功能,其它的作为普通字符。特殊的是,双引号中的单引号中的内容若存在特殊符号仍具有功能。下面举例,

2.4 参数

类似系统的许多命令,本身可能就是一个脚本,且这些脚本可以处理我们给定的各种参数。为此,这里需要介绍,脚本如何处理外部给定的各种参数,同时,脚本内部也可以处理内部参数。外部参数从1开始编号。

变量名说明
$0当前脚本名称
$n外部的第n个参数,但需注意达到10个后,
无法简单的这样指定参数(在3.2节中有方法)
$#外部参数的个数
$*以“参数1参数2参数3……”的形式返回所有参数的值
$@以“参数1”“参数2”“参数3”……的形式返回所有参数的值
$_保存之前执行的命令的最后一个参数

2.5 反斜线(\)

反斜线可屏蔽特殊符号的作用。 打印八进制字符时,也需要在前面加上反斜线,否则作为普通数字处理。(使用 echo -e激活转义字符) 使用计算命令 expr,直接用 *表示乘法会出现错误,需要使用 \*

2.6 管道

当一组命令前后的结果和输入变量是连通的,则将前后的命令语句用管道 |连接起来。

3 判断、循环和函数

3.1 判断

条件判断的表达式可放在方括号中 [] 条件可设置为任意命令语句,只要该语句能够返回成功与否的结果。 其中,一些常用的比较大小的语句如下,

作用语句全称
大于-gtgreater than
小于-ltless than
大于或等于-gegreater than or equal
小于或等于-leless than or equal
不相等-nenot equal

特殊模式,

模式作用
*任意字符
任意单字符
[]给定范围
A|BA或B
#### 3.2 循环 

其中列表可由 "$@"替代,或省略,此时变量将从外部参数中取值。

当条件由符号 :代替,则代表死循环。

Shell的循环中也包含 break和continue命令,用法与常用方式相同

参数传递

该命令使用非常简单,写入一个 shift则将外部参数的头部位置右移一个位置,也可通 shift 位置数指定迁移间隔。 若只是需要获取最后一个外部参数可用命令,eval echo \$$#shift 'expr $# -2'

上述代码的重点在 :a:b:,第一个:指忽略错误信息,当出现错误后,不会显示相关信息,即进入静默模式。而 ab的这类参数选项后跟着:则意味着该选项后面还会跟着一个参数,如指定一个目录或其它信息,若没有此类信息,则不需要:

3.3 函数

在脚本内定义函数,有以下两种格式,

区别在于,使用 function,后面的函数名之后可以不添加小括号。启动函数,直接使用函数名即可。与常用语言类似,通过 return可产生返回值,且只能返回0~255内的整数值。

运行,

为显示当前的函数名,可使用 $FUNCNAME引用,当内部嵌套着多个函数,则$FUNCNAME作为数组,保存多个函数名。 有时函数本身名字较为复杂,可为其设置别名 alias 别名="命令" 别名的使用可适用任何命令语句。

类似的,有时变量本身保存的是另一个变量的名字,如何适用变量真正的值,操作如下, 假设

3.3.1 库文件

当已有写好的脚本,可让其它脚本在内部使用其功能,方式为

4 屏幕输出

在脚本中,使用命令 tput,并附带各种命令,对屏幕输出做出相应的操作。具体可查看附录tput命令

通过输出对应的变量名,可完成对应的命令操作。 例如,利用脚本完成清屏操作,

上述只是为演示操作,实际上,若是上向 clear这类系统命令,可直接在脚本中当作已有函数使用。 通过在终端输入命令 infocmp $TERM可显示全部的tput命令。

5 进程

关于进程的知识,脚本并没有太多额外的知识,大多是Linux本身关于进程的命令。 最重要且关键的两个是,启动一个程序和终止程序。Linux启动程序的方式就是在对应目标的目录前加上一个点 .,或者将一条Linux的启动某程序的命令赋值给一个字符串变量,引用该变量则运行了该命令。终止则是命令 kill pid,pid可通过命令 pstop 查看当前运行的进程的相关信息。

后台运行,在终端输入一条命令后,最后尾部加上符号&后,该命令会放在后台运行。 使用命令 jobs可查看当前后台的命令,包括运行与暂停的。这些后台的命令,会显示着自身的作业号。 这些作业号有的会附带着符号 +-+代表最近的(最新)的作业或当前作业,-代表前一个作业。

前台,为了将前述的后台中的暂停的命令继续执行,则使用命令 fg 作业号

引 用所指的后台作业引 用所指的后台作业
%N编号为 N 的作业%+最近的被放在后台的作业
%string命令以 string 开头的作业%%同上
%?string命令包含 string 的作业%-第二近的被放在后台的作业

如果某个命令并没有一开始设定为后台运行,但是在运行期间被终止,也可以通过 jobs查看它当前的作业号。使用命令 bg可以将对应的命令转移到后台执行。

如果,需要将后台的命令终止,除了简单地用命令 kill 命令对应的pid,或者 kill %作业号。有时,简单的 kill无法彻底终止命令,则需要执行强制终止,使用命令 kill -s SIGKILL pid或%作业号,其中 SIGKILL是传递的信号。实际上命令kill不仅简单的执行终止进程,也可以向进程发送各种信号,使用命令 kill -l可显示可用的各种信号。常用的信号如下,

信 号作 用
信号 1,SIGHUP挂起——关闭进程通信连接
信号 2,SIGINT中断——通知进程退出(<Ctrl+C>键)
信号 3,SIGQUIT退出——强制进程退出(<Ctrl+>键)
信号 6,SIGABRT放弃进程
信号 9,SIGKILL终止进程,该信号不能被截获
信号 15,SIGTERM软件终止,kill 默认信号,通知进程中运行的程序退出
信号 20,SIGTSTP挂起正在运行的进程(<Ctrl+Z>键)

与上述的命令类似,还有命令wait,使得只有等待某个进程执行完才可以做其它动作。 在前面已介绍了 kill可以传输不同的信号。存在一个命令 trap可在获得对应的信号后执行对应的操作,

其它的还有 suspenddisown等命令。

6 文本处理

关于文本处理,需要且非常重要的是了解正则表达式,关于这一知识的介绍已非常多,这里不做介绍。 通过正则表达式,我们可以匹配我们需要的可能的结果。常用的过滤命令 grep "正则匹配模式" 系统中可对某个文件内的内容做过滤,如下,

针对文本的处理有许多命令,如提取内容的命令 cut,合并文件内容的 paste,以及类似的命令 join,又或者转换字符的命令 tr 也存在文本格式化的命令,如指定内容宽度的 fold,或是作用较复杂的 fmt,或是反转字符顺序的 rev,又或是转化为打印格式的 pr等。 nl可为文本添加行号。wc可对文本进行各种统计。

选项作用
-r逆序
-R随机排序
-u排序的同时,排除重复项
-n按数值排序
-k 正整数文本若存在多个列,以对应正整数的列为基准排序
-o将排序的结果输入到指定文件中

其中脚本是编写的sed脚本文件,内部包含一系列 sed命令,输入的内容将按照内部的命令执行相关操作。并且sed脚本文件的第一行为 #!/bin/sed 如果不指定文本文件,则从标准输入中读取内容。

选项作用
-n取消默认输出
-e允许执行多个脚本
-f从脚本文件中读取命令
-i直接修改原始文件
-l指定行的长度
-r在脚本中使用扩展正则表达式
-s默认情况下,sed将把命令行指定的多个文件名作为一个长的连续的输入流。而GNU sed则允许把它们当做单独的文件,这样的话,正则表达式不进行跨文件匹配
-u最低限度的缓存输入与输出

sed编辑命令,

命 令作 用
a\在当前行之后添加一行或多行,多行时除最后一行外,每行末尾需加续行符\
c\用新文本替换当前行中的文本,多行时除最后一行外,每行末尾需加续行符\
d删除文本
i\在当前行之前插入文本,多行时除最后一行外,每行末尾需加续行符\
l显示不可打印字符
p打印文本
r从文件中读取输入行
s匹配查找和替换
w将所选文本写入文件
  1. 格式

当未指明文件,则从标准输入中读取内容。

  1. 读取 $0指读取文件的每一行,$n(n>0)意味读取对应的列。 假设文件 test.txt的内容如下,

命令 awk '{print $0}' test.txt,将按行输出文件内容,printawk内置的输出命令。 命令 awk '{print NR,$2}' test.txt,其中 NRawk内置的变量,表示当前行的序号,输出如下

当不存在文件,则键盘输入的内容,也会被按行读取,之后也可按列输出,等同于文件内容。

  1. 匹配 文本处理自然需要支持正则表达式。某一列作模式匹配时,使用符号 ~ 其它的,为提取内容时,有时需要按某一分隔符提取,比如提取的内容是两个列的内容被用 :分隔,则用选项 -F,命令为 awk -F: '命令' 文件,也可以用 FS命令。当内部的命令包括多个不同的命令语句,用分号分隔开。 上述介绍了从文本提取,若需要在输出时将内容按指定的分隔符分开(默认空格),使用命令 OFS指定。当从文本中按分隔符 :提取,并输出时用 #分隔。awk '{FS=':';OFS="#";print 列}' 文件
  2. 函数 有时,需要指定一段命令中,某一命令是首先执行,或某一命令是最后执行的。对应着语句 BEGIN{}END{} 同时,这里也可以使用if,while的判断循环函数,前后的语句用花括号包裹,花括号内部的命令用分号分隔。当然也可以将则这些语句写在awk脚本文件中。exit用于终止awk程序。 若是不在文件第一行写入 #!/usr/bin/awk -f,则使用 awk [选项] -f 脚本文件名 此外还有特殊的命令 next,可以跳过当前awk后面的命令,例如对文本中的每一行进行操作,若某一行存在问题可直接跳过其它命令并开始执行文本的下一行。

7 文件处理

针对文件的操作,Linux系统本身便具有多种命令,可详细了解,如查询的 find,比较用的 commondiff

0为标准输入,代表默认输入文件,即执行命令的来源,通常是键盘输入,也可以是某个文件或是其它命令的结果,此时就需要输入重定向。 1为标准输出,即执行结果将存放的位置,通常为显示器,其它位置则需要重定向。 2为标准错误,通常结果输出到显示器,其它位置需重定向。 例如,将系统某个目录下的内容及其权限等信息输出到某个文件内,

上述的符号在输出到对应文件中时,会将原有的内容清空。

可采用追加方式,避免清空,即符号 >> << 分隔符,表示命令需要不断地读取输入,直到遇到指定的分隔符。

文件描述符重定向

将a对应的输出变成向b一样输出到对应的位置。

上述的方式只是对当前命令的输入输出作操作。若是需要让以后的命令都能够定向到某个文件,可使用命令 exec。相关使用如下,

重定向说 明
exec 2>file将所有命令的标准错误重定向到文件file
exec n<file只读的方式打开名称为file的文件,并且使用文件描述符n,n是大于3的整数
exec n>file以写入的方式打开名称为file的文件,并且使用文件描述符n,n是大于3的整数
exec n<>file以读写方式打开文件file,并且使用文件描述符n,n是大于3的整数
exec n>&-关闭文件描述符n
exec n>&m使得文件描述符n成为文件描述符m的副本,即将文件描述符m复制到n
exec n>&-关闭文件描述符n

 

附录

tput命令

名字含义
bel警铃
blink闪烁模式
bold粗体
civis隐藏光标
clear
cnorm不隐藏光标
cup移动光标到屏幕位置(x,y)
el清除到行尾
ell清除到行首
smso启动突出模式
rmso停止突出模式
smul开始下划线模式
rmul结束下划线模式
sc保存当前光标位置
rc恢复光标到最后保存位置
sgr0正常屏幕
rev逆转视图
名字含义
cols列数目
ittab设置宽度
lines屏幕行数
名字含义
chts光标不可见
hs具有状态行

颜色值

数字颜色数字颜色
30黑色34蓝色
31红色35紫色
32绿色36青色
33黄(或棕)色37白(或灰)色
数 字颜 色数 字颜 色
40黑色44青色
41红色45蓝色
42绿色46青色
43黄(或棕)色47白(或灰)色