为编写Linux上的Shell脚本,需要对Linux系统上的一些常用的操作命令有所了解,这里不会专门进行介绍。但是,在编写中也能大致了解各种命令的使用方式。
在Linux上的脚本语言几乎默认为bash,其实还有其它这类的Shell语言,但是bash在这其中属于功能齐全且易学的。其文件以 .sh为结尾,原本有一个Shell语言就是叫sh,最初运行在Unix系统上,而bash则是容纳了sh的所有命令并有所扩充。因此,sh和bash在Unix上是两种不同的语言。那么在Linux上则无需考虑过多。
至于文件的编写,网上或书本可能会介绍eclipse等编辑器,又或是介绍windows上的一些脚本模拟器。但实际上,如果需要彻底地了解Linux的编程,最好租用一个Linux的服务器,不需要太高的性能,价格也不会太贵,既可以使用Linux上的vi/vim编辑器,也可以直接运行操作,如果不习惯vim类的编辑器,直接用windows的文本编辑器也是完全可以的。
在编写最开始,首先要表明这是一个bash语言的脚本,在文件第一行写上,
xxxxxxxxxx因为bash包含了sh语言,也可以写#!/bin/sh。不过最好按上述的内容写。#!后跟的是解释器,针对语言的不同,可填写不同程序语言的对应目录。
其次,符号 #作为注释符号存在,不直接参与命令操作。
为了脚本能输出内容,采用命令 echo后跟需要输出的内容,最好带双引号。
在完成编写后,首先需要更改文件权限,默认这类文件无法执行,
xxxxxxxxxxchmod +x 文件名或目录运行脚本,只需要在文件目录前加上点 .,假设存在一个文件 /usr/bin/test.sh,
xxxxxxxxxx./usr/bin/test.sh若是当前目录在bin中,则直接用命令 ./test.sh执行。此时,即使未设置执行权限,也可用命令 source 文件名执行脚本。
bash中的变量属于动态型,不需要事先指明类型,类似python这类语言。变量基本上就是整数型与字符串型,但本质上都是字符串型。
使用时,自定义变量名并赋值后,使用时需要在变量名前加上符号 $,例如,输出数字或字符串,
xxxxxxxxxxecho $$ number$echo $$string$需注意的是,赋值时 =两边不能有空格。此外,=右边可以不加值,即赋值为空;也可以不事先声明变量而直接引用,但是前面的变量通过 命令 set可显示出所有存在的变量,没有事先声明的变量是不会出现的。若是需要消除脚本中定义的变量,通过 unset 变量名实现。
引用时,若是变量名存在可能的歧义,可使用花括号 {}将其包含在内。
字符串1{字符串a,字符串b,...}字符串2。
除了在文件中对变量赋值,还可以在脚本运行时,由用户通过键盘输入自定义的值,命令为 read 变量名,例如,xxxxxxxxxxecho "输入值"read varecho {I am,$var}运行后,
xxxxxxxxxx> source 脚本输入值a supermanI am a superman当输入时不加上双引号,则中间的空格全都视为一个空格。
其次,前面的例子中提示用户 ‘输入值’,可将该值合并到 read命令中,read -p 提示信息 变量名,提示信息可以用引用变量替代。
有时需要限制用户的输入时间,则用命令 read -t 秒数 -n 字符个数 -p "快点写且不要多写" 变量,该命令除了指定时限,也指定了输入的个数,当字符数足够后将自动退出。
另外,read包括选项 -r允许输入反斜杠 ,-d后跟一个双引号括起来的符号,作为输入行的定界符。
在使用中,会发现在输入密码时,系统总是不做显示,使用的是选项 -s,为静默模式。
由于bash的变量本质是字符串,上述定义的变量即使赋值了数字,首先也是作为字符串处理,但同时的是,bash又会当字符串在做数值运算时尝试看作数字。
为了明确变量为数字,使用命令 declare -i 变量名。
最后,还可设置变量仅可读,readonly 变量 赋值 或者 declare -r 变量 赋值,此后,变量不可更改,也不可取消定义。为显示所有只读变量,使用命令 readonly -p或 declare -rp。declare -p可显示变量的属性和值。
除了自定义变量外,还存在系统的环境变量,即系统中本身就存在的变量,在脚本中可直接引用,可查询一些常用的环境变量。或者用
set显示出当前系统中存在的所有的环境变量,可能有非常多。最好根据需要查询是否存在对应的环境变量。
为了保证是作为数字操作,需将运算放在命令 let后。
或者放在 $(())的括号中,其中$(())的结果也可赋值给变量,若是使用 expr 后跟着表达式来计算时,则不需要里面的小括号。
bash支持数组,但支持一维数组。数组内元素需保证类型统一。
read -a,元素用空格分隔,例如,xxxxxxxxxxy=(1 2 3)read -a y #输入 1 2 3readarray 与 mapfile同样可以将键盘输入作为数组,不同点在于,这两个命令以行为分隔。
${数组名[*]} 可以得到数组的所有元素,通过 ${#数组名[*]} 可以得到数组元素的个数,使用某一元素,使用 ${数组名[序号]}。此外可通过命令 declare -a创建数组,不需要在创建时指明元素。类似于不同的编程,可在之后针对各个下标赋值,同时下标可不连续。
xxxxxxxxxxdeclare -a ww[1]=1w[3]=4#或者w=([1]=1 [3]=4)${#w[*]}显示元素个数,${w[*]}显示所有元素的值, ${!w[*]}显示数组的所有下标, ${w[@]}同样显示所有元素的值,不同在于,${w[*]}将结果作为字符串输出,而后者将结果按空格分隔一个个输出。
最后,同样可用 readonly 或 declare -ar声明只读数组。
declare -A创建数组后,数组的下标不仅可以是数字,也可以是字符串。实际上,使得数组称为一个哈希表。
declare -Ap可显示当前所有的关联数组。关于declare的其它命令,可使用 declare --help查看。
其中反引号 `` 已介绍是用来包括命令,如 date等。
单引号 '',内部的字符全都看作普通字符。
双引号 "",对符号 $,\,'`,`"仍保存其特殊功能,其它的作为普通字符。特殊的是,双引号中的单引号中的内容若存在特殊符号仍具有功能。下面举例,
xxxxxxxxxxa='sd'echo $$a$#sdecho '$$a'$#$$a$b="a is $$a$ '$$a' "$echo $$b$#a is sd 'sd'echo '$$b'$#$$b$echo "$$b"$#a is sd 'sd'类似系统的许多命令,本身可能就是一个脚本,且这些脚本可以处理我们给定的各种参数。为此,这里需要介绍,脚本如何处理外部给定的各种参数,同时,脚本内部也可以处理内部参数。外部参数从1开始编号。
| 变量名 | 说明 |
|---|---|
| $0 | 当前脚本名称 |
| $n | 外部的第n个参数,但需注意达到10个后, 无法简单的这样指定参数(在3.2节中有方法) |
| $# | 外部参数的个数 |
| $* | 以“参数1参数2参数3……”的形式返回所有参数的值 |
| $@ | 以“参数1”“参数2”“参数3”……的形式返回所有参数的值 |
| $_ | 保存之前执行的命令的最后一个参数 |
反斜线可屏蔽特殊符号的作用。
打印八进制字符时,也需要在前面加上反斜线,否则作为普通数字处理。(使用 echo -e激活转义字符)
使用计算命令 expr,直接用 *表示乘法会出现错误,需要使用 \*。
当一组命令前后的结果和输入变量是连通的,则将前后的命令语句用管道 |连接起来。
bc计算,sacle表示计算后小数点后的位数,"scale=位数;表达式|bc",例如,xxxxxxxxxx> echo "scale=8;sqrt(2)|bc"1.41421356xxxxxxxxxxif 条件1then 命令1elif 条件2 命令2elif . . .
else 命令fi条件判断的表达式可放在方括号中 []。
条件可设置为任意命令语句,只要该语句能够返回成功与否的结果。
其中,一些常用的比较大小的语句如下,
| 作用 | 语句 | 全称 |
|---|---|---|
| 大于 | -gt | greater than |
| 小于 | -lt | less than |
| 大于或等于 | -ge | greater than or equal |
| 小于或等于 | -le | less than or equal |
| 不相等 | -ne | not equal |
xxxxxxxxxxcase 值 in模式1) 命令1 ;;模式2) 命令2 ;; . . . ;;esacdone特殊模式,
| 模式 | 作用 |
|---|---|
| * | 任意字符 |
| ? | 任意单字符 |
| […] | 给定范围 |
| A|B | A或B |
| #### 3.2 循环 |
xxxxxxxxxxfor 变量 in 列表do 命令done其中列表可由 "$@"替代,或省略,此时变量将从外部参数中取值。
xxxxxxxxxxuntil 条件 命令
donexxxxxxxxxxwhile 条件do 命令done当条件由符号 :代替,则代表死循环。
Shell的循环中也包含 break和continue命令,用法与常用方式相同
参数传递
$10指定参数,则系统只会识别为 $1 0,即指定为第一个参数。可使用 ${10}指定对应参数,但为了方便循环操作,还是需要 shift命令。
为导入全部的参数,需要将前面已在脚本内保存的外部参数移除,于是剩余的外部参数则不断前移,通过循环指定 $1即可导入全部外部参数。该命令使用非常简单,写入一个 shift则将外部参数的头部位置右移一个位置,也可通 shift 位置数指定迁移间隔。
若只是需要获取最后一个外部参数可用命令,eval echo \$$# 或 shift 'expr $# -2'。
-符号。命令 getopts将捕获脚本后面跟的参数选项,并在脚本内进行比较判断。
假设脚本具有多个参数选项,例如 -a,-b,则编写例子如下,xxxxxxxxxx# 输出参数索引echo "OPTIND starts at $$OPTIND $"#OPTIND: getopts使用OPTIND作为索引,来处理下一个需要处理的参数,记录当前的状态。#OPTARG: 在循环中,a,b两个参数后面各有一个冒号,冒号表示该输入的参数后面还有一个参数值,当getopts发现冒号后,会处理用户输入的参数值,这个参数值被保存在OPTARG中。# 接收参数while getopts :a:b: optname do case "$$optname $" in "a") echo "Option $$optname $ is specified" ;; "b") echo "Option $$optname $ has value $$OPTARG $" ;; "?") echo "Unknown option $$OPTARG $" ;; ":") echo "No argument value for option $$OPTARG $" ;; *) # Should not occur echo "Unknown error while processing options" ;; esac echo "OPTIND is now $$OPTIND $"done上述代码的重点在 :a:b:,第一个:指忽略错误信息,当出现错误后,不会显示相关信息,即进入静默模式。而 a或b的这类参数选项后跟着:则意味着该选项后面还会跟着一个参数,如指定一个目录或其它信息,若没有此类信息,则不需要:。
在脚本内定义函数,有以下两种格式,
xxxxxxxxxxfunction 函数名{ 操作}#或函数名(){ 操作}区别在于,使用 function,后面的函数名之后可以不添加小括号。启动函数,直接使用函数名即可。与常用语言类似,通过 return可产生返回值,且只能返回0~255内的整数值。
$n可引用函数的第n个外部参数。因此不同区域使用 $n意味着不同位置的外部参数,距离如下,xxxxxxxxxx#------------func_len.sh-----------length(){# 接收参数str=$1result=0if [ "$str" != "" ]; then# 计算字符串长度result=${#str}fi# 将长度值写入标准输出echo "$result"}# 调用函数str="123456"len=$(length $str)# 输出执行结果echo "the string $1's length is $len"echo "$1 length $(length $1)"运行,
xxxxxxxxxx> ./func_len 123the string 123456's length is 6123 length 3declare -g定义全部变量。
若是参数为数组,假设xxxxxxxxxxfunc(){...}a=(1 2 3)func "${a[@]}"#导入数组元素为显示当前的函数名,可使用 $FUNCNAME引用,当内部嵌套着多个函数,则$FUNCNAME作为数组,保存多个函数名。
有时函数本身名字较为复杂,可为其设置别名
alias 别名="命令"
别名的使用可适用任何命令语句。
类似的,有时变量本身保存的是另一个变量的名字,如何适用变量真正的值,操作如下, 假设
xxxxxxxxxxA=B#B是个变量名B="真正的值"${A}#结果是B${!A}#结果为"真正的值"当已有写好的脚本,可让其它脚本在内部使用其功能,方式为
xxxxxxxxxx#-------------ku_test.sh--------. 一个脚本文件目录 #中间有空格#假设该库文件有一个函数 func()#则直接调用func #如同内部写好的一个函数一样使用在脚本中,使用命令 tput,并附带各种命令,对屏幕输出做出相应的操作。具体可查看附录tput命令。
xxxxxxxxxx变量名="tput 命令名"通过输出对应的变量名,可完成对应的命令操作。 例如,利用脚本完成清屏操作,
xxxxxxxxxxCLEAR=" tput clear" #定义一个清屏的变量$CLEAR上述只是为演示操作,实际上,若是上向 clear这类系统命令,可直接在脚本中当作已有函数使用。
通过在终端输入命令 infocmp $TERM可显示全部的tput命令。
echo -e "\033[背景值;前景值m",可将当前屏幕的输出字符转变对应的颜色。详细的颜色值,可查看附录颜色值。关于进程的知识,脚本并没有太多额外的知识,大多是Linux本身关于进程的命令。
最重要且关键的两个是,启动一个程序和终止程序。Linux启动程序的方式就是在对应目标的目录前加上一个点 .,或者将一条Linux的启动某程序的命令赋值给一个字符串变量,引用该变量则运行了该命令。终止则是命令 kill pid,pid可通过命令 ps或 top 查看当前运行的进程的相关信息。
后台运行,在终端输入一条命令后,最后尾部加上符号&后,该命令会放在后台运行。
使用命令 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可在获得对应的信号后执行对应的操作,
xxxxxxxxxxtrap "命令" 信号其它的还有 suspend,disown等命令。
关于文本处理,需要且非常重要的是了解正则表达式,关于这一知识的介绍已非常多,这里不做介绍。
通过正则表达式,我们可以匹配我们需要的可能的结果。常用的过滤命令 grep "正则匹配模式"。
系统中可对某个文件内的内容做过滤,如下,
xxxxxxxxxxgrep [选项] 匹配模式 文件目录针对文本的处理有许多命令,如提取内容的命令 cut,合并文件内容的 paste,以及类似的命令 join,又或者转换字符的命令 tr。
也存在文本格式化的命令,如指定内容宽度的 fold,或是作用较复杂的 fmt,或是反转字符顺序的 rev,又或是转化为打印格式的 pr等。
nl可为文本添加行号。wc可对文本进行各种统计。
sort默认排序为字典排序。| 选项 | 作用 |
|---|---|
| -r | 逆序 |
| -R | 随机排序 |
| -u | 排序的同时,排除重复项 |
| -n | 按数值排序 |
| -k 正整数 | 文本若存在多个列,以对应正整数的列为基准排序 |
| -o | 将排序的结果输入到指定文件中 |
sed,该命令会将标准输入的内容或文本内容按行处理,即将内容一行行地复制到缓存中,每次仅对各行做处理。xxxxxxxxxxsed [选项] [脚本] [文本文件]其中脚本是编写的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 | 将所选文本写入文件 |
sed,awk实际上已经不是bash语言中的组成部分,而是独立的脚本语言,因此 awk既可以在终端完成相关的命令,也可以编写对应的脚本文件,文件后缀为 .awk,第一行为 #!/usr/bin/awk -f。脚本内部的编写方式,与bash类似,只不过包含着 awk自身特有的相关命令。
下面简要介绍相关的命令操作,xxxxxxxxxxawk '命令' 文件当未指明文件,则从标准输入中读取内容。
$0指读取文件的每一行,$n(n>0)意味读取对应的列。
假设文件 test.txt的内容如下,xxxxxxxxxxhang1 lie12 lie13hang2 lie22 lie23命令 awk '{print $0}' test.txt,将按行输出文件内容,print为awk内置的输出命令。
命令 awk '{print NR,$2}' test.txt,其中 NR为awk内置的变量,表示当前行的序号,输出如下
xxxxxxxxxx1 lie122 lie22当不存在文件,则键盘输入的内容,也会被按行读取,之后也可按列输出,等同于文件内容。
~。
其它的,为提取内容时,有时需要按某一分隔符提取,比如提取的内容是两个列的内容被用 ‘:’分隔,则用选项 -F,命令为 awk -F: '命令' 文件,也可以用 FS命令。当内部的命令包括多个不同的命令语句,用分号分隔开。
上述介绍了从文本提取,若需要在输出时将内容按指定的分隔符分开(默认空格),使用命令 OFS指定。当从文本中按分隔符 ’:’提取,并输出时用 ’#’分隔。awk '{FS=':';OFS="#";print 列}' 文件。BEGIN{},END{}。
同时,这里也可以使用if,while的判断循环函数,前后的语句用花括号包裹,花括号内部的命令用分号分隔。当然也可以将则这些语句写在awk脚本文件中。exit用于终止awk程序。
若是不在文件第一行写入 #!/usr/bin/awk -f,则使用 awk [选项] -f 脚本文件名。
此外还有特殊的命令 next,可以跳过当前awk后面的命令,例如对文本中的每一行进行操作,若某一行存在问题可直接跳过其它命令并开始执行文本的下一行。针对文件的操作,Linux系统本身便具有多种命令,可详细了解,如查询的 find,比较用的 common和 diff
>或 <可将结果输出到指定文件,或从指定文件输入内容。
其中在符号前可附带数字 0、1或2,这些数字为文件描述符,每个进程都会附带着着三个文件描述符。可将指定的内容输出。0为标准输入,代表默认输入文件,即执行命令的来源,通常是键盘输入,也可以是某个文件或是其它命令的结果,此时就需要输入重定向。 1为标准输出,即执行结果将存放的位置,通常为显示器,其它位置则需要重定向。 2为标准错误,通常结果输出到显示器,其它位置需重定向。 例如,将系统某个目录下的内容及其权限等信息输出到某个文件内,
xxxxxxxxxxls -l 目录 > 文件ls -l 目录 2> 文件1#将错误记录输出到文件1中ls -l 目录 &> 文件2#将标准输出于标准错误输出到文件2中上述的符号在输出到对应文件中时,会将原有的内容清空。
可采用追加方式,避免清空,即符号 >>。
<< 分隔符,表示命令需要不断地读取输入,直到遇到指定的分隔符。
文件描述符重定向
xxxxxxxxxx文件描述符a>&文件描述符b将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 |
| 名字 | 含义 |
|---|---|
| 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 | 白(或灰)色 |