Bash编程

在Linux系统使用过程中,不可避免的要编写脚本,如进行大量重复操作,或需要根据条件自动执行某操作。本文介绍了bash脚本编写的相关内容,文末有一些示例。

文章目录

  • 一、bash脚本介绍
    • 1. shell
    • 2. bash脚本
  • 二、bash脚本基础
    • 1. 编程语言分类
    • 2. 编写代码的约定
  • 三、bash语法介绍
    • 1. 变量
      • bash中的变量类型
      • 变量声明
        • 本地变量的声明
        • 只读变量的声明
        • 环境变量的声明
        • 局部变量的声明
        • 数组的声明
        • 数组的初始化或赋值
      • 变量的引用
        • 数组的引用
      • 特殊引用
      • 变量的撤销
    • 2. 逻辑运算
      • 与(AND)
      • 或(OR)
      • 非(NOT)
      • 逻辑短路
        • 概念
        • 应用
      • 德摩根定律
    • 3. 条件分支
      • if
        • 单分支if
        • 双分支if
        • 多分支if
      • case
    • 4. 算术运算
      • 增强型赋值
    • 5. 脚本的执行与测试
      • 执行脚本
      • 测试脚本
    • 6. 循环
      • while
      • until
      • for
        • bash风格的for语句
          • 列表的生成方式
            • 直接给出列表
            • 整数列表
            • 能返回列表的命令
            • Glob
            • 变量引用
        • C风格的for语句
        • 循环控制
          • break
          • continue
    • 7. 比较与测试
      • 条件测试表达式
        • 关于[ ... ]与[[ ... ]]
          • [ ]
          • [[ ]]
      • 数值测试
      • 字符测试
      • 文件测试
        • 单目测试
          • 存在性测试
          • 大小测试(测试文件是否有内容)
          • 类型测试
          • 权限测试
          • 特殊权限测试
          • 打开性测试
          • 时间戳测试
          • 从属关系测试
        • 双目测试
    • 7. 脚本的参数
      • shift
      • 脚本返回值
    • 8. 数组
      • 引用数组中的所有元素
      • 获取数组中某一元素的长度
      • 数组元素切片
    • 9. 字符串处理
      • 字符串切片
      • 基于模式取子串
      • 查找替换
      • 字符串大小写转换
    • 10. 函数
      • 定义函数
      • 调用函数
      • 函数的返回值
        • 函数的执行状态返回值
        • 函数的运行结果
      • 函数中的变量
        • 局部变量的使用
        • 函数的参数
    • 11. 信号捕捉
  • 四、例

一、bash脚本介绍

1. shell

严格来讲,这里的bash是工作在用户空间的一个程序而已,与其他程序不同的是,该程序负责与使用者交互,可以说是计算机与用户的沟通的媒介。

我们通过输入设备输入指令或数据,有它负责进行相应操作,或启动另一个进程处理。从这个意义来讲,他就像是一个“外壳”,普通用户与计算机的交互都是通过该程序。这样的程序我们将其称为Shell,Windows平台如桌面,cmd,powershell,Linux平台有bash,zsh,csh,KDE,Gnome等。

2. bash脚本

作为一款shell程序,bash的强大之处在于,其内部支持其特有的命令输入方式,如,可以将多个命令写在一行,中间使用分号分隔即可;再如,它可以进行条件判断等1

这里要说明的其另一个重要特性,他可以将以文本文件的内容,按照其规则进行解释后执行,效果如用户键入的相同。该规则可以称为bash的语法,该文件可称为bash脚本。

由于其这种特性,它几乎可以被当做一门编程语言。

二、bash脚本基础

1. 编程语言分类

为了从头讲明,也便于同其他语言对比,这里提一下这个话题。关于软件编程,这里有基础介绍:https://blog.csdn.net/xiyangyang410/article/details/85043737#2__46

这里我们只讨论高级语言,我们知道,开发人员写的代码最初为文本文件,而其需要被“翻译”为计算机能识别的指令才能执行。此处可从代码的“翻译”方式对于编程语言做以简要说明:

  • 编译型语言
    代码需要由“翻译”工具翻译为二进制格式,在进行该操作之前会进行一系列检查,检查全部通过才会继续。
    该“翻译”工具被叫做 编译器(Compiler),翻译过程被称为 编译(Compilation)
    这样的编程语言如 C、C++、Pascal等,而这类软件的构建过程一般还有汇编与链接等过程
  • 解释型语言
    解释型语言的翻译工具被称为 解释器(Interpreter),这类语言开发的程序运行时,需要事先启动一个解释器,先进行基本的语法检查,无误后由解释器逐句解释代码执行
    这种编程语言只需事先安装对应平台的解释器即可,遂其可以做到较编译型语言好的跨平台性
  • 半解释型语言
    由于解释型语言的执行特点,其运行速度自然不比编译型语言快,而半解释型语言兼顾了以上二者的有点,有的地方也称之为半编译型语言。而他的“翻译”方式为,先将文本代码编译为字节码,该文件为二进制,但是不能直接执行,需要相应的解释器解释执行。如Java、Python等

一般的,我们将解释型语言的源代码文件叫做脚本,有时也将半解释型语言代码文件这么称呼,但是一般编译型语言的源代码文件不这么叫。

比如你可能听过bat脚本、vb脚本,甚至Python脚本,但是你听过C脚本吗?

2. 编写代码的约定

几乎所有的编程语言,在代码编写过程中都会有一些约定俗成的规则,并且也有很多团队有自己内部的开发规范,这里对基本的规则予以介绍,以避免养成一些陋习。

  1. 变量名要做到见名知意
    不要出现类似a,b,c,或data1,data2之类的变量名
    拼音并不能很好的做到见名知意,应使用英语
    可使用驼峰法,如PeopleCount,或使用下划线分隔单词,如people_count
  2. 代码需要有注释
    对于某些复杂逻辑,或一些标识值的意义等,应该在注释部分做以说明
    在代码首部,应该有对该代码文件的相关说明,如代码的功能介绍、开发日期、作者,有的甚至需要注明版本变更与内容

三、bash语法介绍

bash可以说是最好学的编程语言了,因为他在工作时是直接调用的系统命令。而其他编程语言,我们将其本身的语法学完之后,很难直接使用它快速完成实际工作——我们还需要学一大堆的库。

bash编程,只要了解相关命令的用法,以及bash的语法即可。

1. 变量

变量(Variable) 相信大家并不陌生,其实质上就是一段命名的内存空间。而在bash中,同样支持变量的概念。

bash中的变量类型

变量类型 说明
环境变量 作用域为当前shell进程及其子进程
本地变量 作用域为整个bash进程
局部变量 作用域为当前代码段(通常指函数)
位置变量 $1,$2,…用于让脚本在脚本代码中调用通过命令行传递给它的参数
特殊变量 保存某些特殊数据
特殊变量
$0: 脚本名称本身
$?: 上一条命令的执行状态
  • 状态用数字表示:0-255
  • 成功:0
  • 失败:1-255
$$:脚本运行的当前进程ID
$!:Shell最后运行的后台进程的PID
$#: 参数数量
$*: 所有参数的一个字符串
$@:所有参数单独作为每个字符串
$1$2…:位置变量,对应第一、第二个参数
关于$@与$*的区别:
只有在双引号中体现出来。假设在脚本运行时写了三个参数(分别存储在 $1 $2 $3)则" $*" 等价于 “ $1 $2 $3"(传递了一个参数);而“ $@" 等价于 “ $1” “ $2” “ $3”(传递了三个参数)

变量声明

bash中变量的命名规则同其他语言类似,变量名只能包含数字、字母和下划线,而且不能以数字开头,关键字不能用作变量名,另外,变量赋值时不能使用$。

本地变量的声明

bash为弱类型语言,故在声明变量时不必指定变量类型(但这不代表这些变量没有类型),可直接使用如下形式声明变量:

[set] VARNAME=VALUE
	set可省略

使用不带参数的set命令可查看系统已定义的所有变量

只读变量的声明

readonly VARNAME
或
declare -r VARNAME

在声明时指定readonly,或使用declare -r,可声明只读变量(常量),由于其在声明后不可更改内容,需要在声明时进行初始化。

环境变量的声明

export VARNAME=VALUE
或
VARNAME=VALUE; export VARNAME
或
VARNAME=VALUE; declare -x VARNAME
或
declare -x VARNAME=VALUE
Tips;
在同一行执行的多条命令中间可使用;(分号)隔开
环境变量的查看
printenv
env
export
declare -x

环境变量对当前shell及其子shell都有效

局部变量的声明

local VARNAME=VALUE

仅对局部代码生效

数组的声明

所谓数组,是有序的元素序列。若将有限个类型相同的变量的集合命名,那么这个名称为数组名。它使用连续的内存空间,可以使用索引来获取相关元素。

在bash-4及以后的版本中,支持关联数组(Associative Array) 的概念,即可自定义索引,而不是仅仅以0,1,2……为其索引的索引数组(Indexed Array),类似于Python中字典(dict) 的概念。bash中声明数组的方式为:

declare -a ARRAY_NAME
	# 声明一个索引数组
declare -A ARRAY_NAME
	# 声明一个关联数组

数组的初始化或赋值

在声明一个数组后,可对其进行初始化,使用ARRAY_NAME=("VAR1" "VAR2" "VAR3")的形式,各元素之间使用空白分隔,bash将按给出的次序给予每一个元素索引(0,1,2……),若数组声明为关联数组,可使用ArrayName=([INDEX]='VAR' [INDEX]='VAR')的形式,此处的INDEX并非必须是数字。

可以使用如下方式对数组的某个元素进行赋值:
ARRAY_NAME[INDEX]=VAR

注意,此处没有$,关于bash中数组的其他用法,下文将做介绍

变量的引用

  • ${VARNAME} 不会引起混淆的话,{}可省略
  • “” 弱引用,其中的变量引用会被替换为变量值
  • ‘’ 强引用,其中的变量引用不会被替换为变量值,而保持原字符串

数组的引用

关于数组的引用,可使用如下格式:

${ARRAY_NAME[INDEX]}

注意,此处的{}(花括号)不能省略,而数组名将引用数组首元素

特殊引用

变量的赋值可使用VAR_NAME=VALUE的形式,此处VALUE可以为字面值,也可以引用变量,此处介绍以下特殊引用方式

  • ${var:-VALUE}
    var变量为空,或未设置,则返回 VALUE,否则返回 var变量的值
  • ${var:=VALUE}
    var变量为空,或未设置,则返回 VALUE,并将 VALUE赋值给var变量,否则返回 var变量的值
  • ${var:+VALUE}
    var变量不空,或未设置,则返回 VALUE,否则返回空,与 ${var:-VALUE}相反
  • ${var:?ERROR_INFO}
    var为空,或未设置,那么返回 ERROR_INFO为错误提示,否则返回 var的值

变量的撤销

变量将在当前shell的声明周期结束时被自动撤销,而欲手动撤销变量,可使用unset VAR_NAME,此时,若VAR_NAME为变量或数组,则可直接撤销,若为数组的某元素,则可仅撤销该元素

2. 逻辑运算

与(AND)

“与”表示“并且”之意,即两种事件都发生,使用 && 表示,其运算结果为

  • 1 && 1 = 1
  • 1 && 0 = 0
  • 0 && 1 = 0
  • 0 && 0 = 0

即二者都为真(True),结果才为真(False),否则为假

或(OR)

“或”表示二者任一即可,使用 || 表示,其运算结果为

  • 1 || 1 = 1
  • 1 || 0 = 1
  • 0 || 1 = 1
  • 0 || 0 = 0

即二者都为假,结果才为假,否则为真

非(NOT)

“非”表示“不”,取相反结果之意,这是一个单目运算符,使用 ! 其运算结果为

  • ! 1 = 0
  • ! 0 = 1

逻辑短路

概念

由于“与”和“或”运算时自左而右的,加之其运算特性,我们可以得出如下结论

  • 若且(AND)运算的第一个运算数(Operand)为假,结果一定为假;
  • 若或(OR)运算的第一个运算数为真,结果一定为真;

如此则不必再去计算其二个运算数(或表达式)的结果,可直接得出最终结果,这种运算方式成为短路运算,也叫作逻辑短路。

应用

通过这种特性,在bash中可是实现类似if条件判断的处理。

我们知道,程序在执行完后会返回一个执行状态,用于标识程序执行成功与否,使用0标识执行成功(即“真”),非0则标识执行失败(即“假”)

可通过判断该状态返回值,对不同的结果(成功或失败)处以不同的操作,如

查看user1是否存在,若存在,则输出其信息,否则创建之:

id user1 2> /dev/null || useradd user1

可以组合多个这样的命令以实现更复杂的控制

  • 如果用户user1存在,就系显示用户已存在,否则就添加此用户
    id user1 && echo "user1 exists." || useradd user1
    
  • 如果用户user1不存在,就添加此用户,否则就显示用户已存在
    ! id user1 && useradd user1 || echo "user1 exists."
    # 或
    id user1 || useradd user1 && echo "user1 exists."
    
  • 如果用户user1不存在,就添加并且给密码,否则显示其已存在
    ! id user1 && useradd user1 && echo "user1" | passwd --stdin user1 || echo "user1 exists."
    

德摩根定律

该定律应用很广泛,此处也将其列出
A ⋂ B ‾ ≡ A ‾ ⋃ B ‾ \overline{A \bigcap B} \equiv \overline{A} \bigcup \overline{B} ABAB

A ⋃ B ‾ ≡ A ‾ ⋂ B ‾ \overline{A \bigcup B} \equiv \overline{A} \bigcap \overline{B} ABAB

3. 条件分支

if

对于以上的根据不同条件进行不同操作的方式,可使用一个更易读的语法:if语句

单分支if

表示,如果某条件满足,则执行特性操作,否则不行该操作,其使用格式为

if BOOL_EXP; then
	STATEMENT
fi

BOOL_EXP为一个布尔表达式,可以是一个命令,此时将取命令的执行状态返回值作为布尔值参与表达式计算

BOOL_EXP值为真,则执行代码块中内容(STATEMENT),否则不执行

then关键字也可以另起一行,另外对于bash而言,缩进不是必要的,但为了代码易读,建议对代码块中的语句使用缩进。而有的编程语言这一要求是必须的,如Python

双分支if

if BOOL_EXP; then
	STATEMENT1
else
	STATEMENT2
fi

表示若BOOL_EXP为真,执行STATEMENT1,否则执行STATEMENT2

多分支if

if BOOL_EXP1; then
	STATEMENT1
elif BOOL_EXP2; then
	STATEMENT2
elif BOOL_EXP3; then
	STATEMENT3
...
else
	STATEMENT4
fi

表示若BOOL_EXP1为真,执行STATEMENT1,若BOOL_EXP2位真,执行STATEMENT2…
elif段可以出现多次,最后的else也是可选的,若有,表示若所有条件都不满足,执行STATEMENT4

故以上的代码使用if语句的实现为

if ! id user1 2> /dev/null; then
	useradd user1
fi

case

有时在处理有多种条件分支的情况时,若使用if语句,我们不得不编写冗长的elif段,此时,可以使用case语句来处理该情形,case语句的基本使用格式为

case EXPRESSION in
PATTERN1)
	STATEMENTS
	;;
PATTERN2)
	STATEMENTS
	;;
...
esac

PATTERN为过滤的模式,其支持的方式为

  • | 或,如PATTERN1|PATTERN2表示PATTERN1或PATTERN2均满足条件
  • * 匹配任意长度的任意字符
  • ? 匹配任意单个字符
  • [] 匹配范围

#!/bin/bash
case $1 in
'start')
	echo "start server...";;
'stop')
	echo "stop server...";;
'restart')
	echo "restarting server...";;
'status')
	echo "running...";;
*)
	echo "`basename $0` {start|stop|restart|ststus}";;
esac

4. 算术运算

由于bash是弱类型的语言,其声明的变量默认为字符,运算法则默认也是按照字符运算进行的,若要使其进程算术运算,可使用如下几种方式:

  • let VAR = ARITH_EXPR
  • VAR = $[ARITH_EXPR]
  • VAR = $((ARITH_EXPR))
  • VAR = $(expr ARITH_EXPR)

bash中常用的运算符如下

运算符 描述
+
-
*
/
** 乘方
% 取模

若欲计算变量A与变量B的和,以上表示的实现为:

#let VAR = ARITH_EXPR
	let C = $A + $B
#VAR = $[ARITH_EXPR]
	C = $[$A + $B]
#VAR = $((ARITH_EXPR))
	C = $(($A + $B))
#VAR = $(ARITH_EXPR)
	C = $(expr $A + $B)
	C = `expr $A + $B`

如,计算user1,user2,user3用户的UID之和

#!/bin/bash

uid1=`id -u user1`
uid2=`id -u user2`
uid3=`id -u user3`

uid_sum=$[$uid1 + $uid2 + $uid3]

echo "The sum of uid if $uid_sum."

增强型赋值

bash中的增强型赋值类似于C语言,可较高效的实现引用并赋值的操作,以变量AB为例:

增强赋值 等效操作
A+=B A=$A+$B
A*=B A=$A*$B
A-=B A=$A-$B
A/+B A=$A/$B
A%=B A=$A%$B

5. 脚本的执行与测试

执行脚本

同其他编程不同,所谓的bash编程,调用的是系统上已有的程序命令,通过bash内置的变量、流程控制等机制实现的。

程序的执行方式
  • 编译执行
    由源代码直接一次性编译为而进行文件,可直接执行
  • 解释执行
    程序文件内核无法直接理解,需要外部程序解释源文件,逐条执行

代码的源文件问文本格式,而内核只能执行二进制格式文件,故直接执行该文件内核是无法理解的,而bash是解释执行的,需要为其程序文件执行一个解释器。

在程序文件内容的最开始处使用以 #! 开头,后跟解释器程序路径的字符串来通知内核,使用该程序对文件进行解释执行,而不是直接执行,这个字符串被称为 Shebang

#!/PATH/TO/SHELL_INTERPRETER

事实上在Linux系统中,解释型语言普遍都是采用以上方式,如Python、Java等代码,此处以bash脚本为例,可指定为

#!/bin/bash

上文程序的第一行就是Shebang。

若不指定,则脚本无法直接执行,但可以明确指定解释器,将该脚本文件当做参数让解释器执行,如上面的文件为:

uid1=`id -u user1`
uid2=`id -u user2`
uid3=`id -u user3`

uid_sum=$[$uid1 + $uid2 + $uid3]

echo "The sum of uid is $uid_sum."

可使用bash SCRIPT_NAME执行之

另外,默认新建的文件是没有执行权限的,若要直接执行,则需要为其赋予执行权限2
chmod +x /PATH/TO/SCRPTE

测试脚本

测试脚本中是否有语法错误:

bash -n SCRIPT

调试脚本(单步执行)

bash -x SCRIPT

如,脚本文件test.sh内容为

#!/bin/bash

declare stra=root
[[ $stra ~= oot ]] && echo Yes || echo No

进行语法测试:

[root@localhost ~]# bash -n scripts/test.sh
scripts/test.sh: line 4: conditional binary operator expected
scripts/test.sh: line 4: syntax error near `~='
scripts/test.sh: line 4: `[[ $stra ~= oot ]] && echo Yes || echo No'

将文件中的~=修改为=~再次测试则没有报错,表示无语法错误。

单步执行1-10中的偶数和,test.sh文件内容为:

#!/bin/bash

declare -i sum=0

for((i=1;i<=10;i++));do
    if [ $[$i % 2] -eq 0 ];then
        let sum+=$i
    fi
done
echo $sum
[root@localhost ~]# bash -x scripts/test.sh
+ declare -i sum=0
+ (( i=1 ))
+ (( i<=10 ))
+ '[' 1 -eq 0 ']'
+ (( i++ ))
+ (( i<=10 ))
+ '[' 0 -eq 0 ']'
+ let sum+=2
+ (( i++ ))
+ (( i<=10 ))
+ '[' 1 -eq 0 ']'
+ (( i++ ))
+ (( i<=10 ))
+ '[' 0 -eq 0 ']'
+ let sum+=4
+ (( i++ ))
+ (( i<=10 ))
+ '[' 1 -eq 0 ']'
+ (( i++ ))
+ (( i<=10 ))
+ '[' 0 -eq 0 ']'
+ let sum+=6
+ (( i++ ))
+ (( i<=10 ))
+ '[' 1 -eq 0 ']'
+ (( i++ ))
+ (( i<=10 ))
+ '[' 0 -eq 0 ']'
+ let sum+=8
+ (( i++ ))
+ (( i<=10 ))
+ '[' 1 -eq 0 ']'
+ (( i++ ))
+ (( i<=10 ))
+ '[' 0 -eq 0 ']'
+ let sum+=10
+ (( i++ ))
+ (( i<=10 ))
+ echo 30
30

6. 循环

有时需要重复执行一些特定的操作,比如,对某文件的每一行内容做特定操作,循环执行某计算知道某特定条件满足。此时可使用循环控制,bash中提供三种基本的循环控制语句。

while

while的基本使用格式为:

while CONDITION; do
	STATEMENTS
done

CONDITION为进入循环的条件,是布尔表达式,其结果为真时进入循环。

如:计算100以内整数的和

#!/bin/bash
declare -i I=1
declare -i SUM=0
while [ $I -le 100 ]; do
	let SUM+=$I
	let I++
done
echo $SUM

until

until的作用于while相似,不同的是当条件为假时进入循环,其使用格式为

until CONDITION; do
	STATEMENS
done

如使用until计算100以内整数的和

#!/bin/bash
declare -i I=1
declare -i SUM=0
until [ $I -gt 100 ]; do
	let SUM+=$I
	let I++
done
echo $SUM

for

bash风格的for语句

for的基本使用格式为:

for VAR in LIST; do
	COMMANDS;
done

LIST是提供了一系列用于迭代的值的列表,在for语句执行时,LIST中的每一个元素将在每次循环时赋给变量VAR,在循环体内部可饮用VAR进行相应操作。

列表的生成方式

列表由以下几种常见的生成方式

  • 直接给出列表
  • 整数列表
  • 能返回列表的命令
  • Glob
  • 变量引用
直接给出列表
for i in one two three; do
	echo $i
done

执行结果:

one
two
three
整数列表
  • {START…END},如
    for i in {1..10}; do
    # 将生成1 2 3 4 5 6 7 8 9 10
    
能返回列表的命令

某些命令的直接结果就是以列表形式给出的,可以使用命令替换使用该结果,如

for i in `ls /`; do
	echo $i
done

Tips:使用seq命令生成
seq命令可生成一个数字列表,其使用格式为

seq [START [STEP]] END

[root@localhost scripts]# seq 3
1
2
3
[root@localhost scripts]# seq 3 6
3
4
5
6
[root@localhost scripts]# seq 1 2 10
1
3
5
7
9
Glob

bash的Glob特性所匹配到的各个结果也是以列表的形式存在,如

for file in /etc/*.conf
	...
done

代码将遍历/etc目录中以.conf结尾的文件

变量引用

此处需要指出的是两个特殊变量$@$*,他们都存储了传递给脚本的参数,其区别只有在双引号中体现出来。假设在脚本运行时写了三个参数(分别存储在$1 $2 $3)则$* 等价于 “$1 $2 $3”(传递了一个参数);而$@ 等价于 “$1” “$2” “$3”(传递了三个参数)

C风格的for语句

语法格式

for((INIT_EXPR;EXIT_COND;ITER_EXPR)); do
	COMMANDS;
done

这种for语句与C语言的语法类似,INIT_EXPR为变量的初始化表达式,EXIT_COND为循环退出的条件测试,结果为假时退出循环,ITER_EXPR为每次循环的迭代操作,常用作修正循环变量,如,计算[1,100]的整数和的实现为:

declare -i sum=0
for((i=1;i<=100;i++)); do
	let sum+=$i
done
echo "$sum"

需要说明的是,C风格的写法中,变量的引用方式、布尔运算符等都都与bash风格有所不同:

  • 变量赋值可以包含空格
  • 变量引用不必使用$做前缀
  • 迭代的处理式不使用expr表达式

循环控制

break

break用于跳出当前循环语句,其后加一个数字可跳出多层循环

continue

continue用于结束本轮循环

如,计算1-100的偶数和:

#!/bin/bash

declare -i sum=0
declare -i idx=1

while true; do
    let idx++
    if [ $[$idx%2] -ne 0 ]; then
        continue
    fi

    if [ $idx -gt 100 ]; then
        break
    fi
    let sum+=$idx
done

echo $sum

7. 比较与测试

在使用条件判断时,经常需要通过一系列比较与测试,根据其结果做出相应的操作。此处再次强调,bash中0表示真,非0则为假

条件测试表达式

可使用一下三种方式

[ EXPRESSION ]
	# 中括号与EXPRESSION之间必须有空格,该中括号是命令

[[ EXPRESSION ]]
	# 内侧的中括号与EXPRESSION之间必须有空格,这两个中括号是关键字

test EXPRESSION

EXPRESSION为测试表达式,常用的有以下三类

  • 数值测试
  • 字符测试
  • 文件测试

关于[ … ]与[[ … ]]

[ ]
  • [ ]是bash的内部命令,[test是等同的。如果我们不用绝对路径指明,通常我们用的都是bash自带的命令。if/test结构中的左中括号是调用test的命令标识,右中括号]是关闭条件判断的。这个命令把它的参数作为比较表达式或者作为文件测试,并且根据比较的结果来返回一个退出状态码。if/test结构中并不是必须右中括号,但是新版的Bash中要求必须这样

  • test[ ]中可用的比较运算符只有==!=,两者都是用于字符串比较的,不可用于整数比较(下文将做介绍),整数比较只能使用-eq-gt这种形式。无论是字符串比较还是整数比较都不支持大于号小于号。如果实在想用,对于字符串比较可以使用转义形式,如果比较"ab"和"bc":[ ab \< bc ],结果为真,也就是返回状态为0。[ ]中的逻辑与和逻辑或使用-a-o表示

[[ ]]
  • [[是bash程序语言的关键字。并不是一个命令,[[ ]] 结构比[ ]结构更加通用。在[[]]之间所有的字符都不会发生文件名扩展或者单词分割,但是会发生参数扩展和命令替换

  • 支持字符串的模式匹配,使用=~操作符时甚至支持shell的正则表达式。字符串比较时可以把右边的作为一个模式,而不仅仅是一个字符串,比如[[ hello =~ hell? ]],结果为真。[[ ]] 中匹配字符串或通配符,不需要引号

  • 使用[[ ... ]]条件判断结构,而不是[ ... ],能够防止脚本中的许多逻辑错误。比如,&&||<>操作符能够正常存在于[[ ]]条件判断结构中,但是如果出现在[ ]结构中的话,会报错

  • bash把双中括号中的表达式看作一个单独的元素,并返回一个退出状态码

数值测试

数值比较使用如下形式

NUM1 OPRAND NUM2

OPRAND为一个双目比较的操作操作符,有如下为常用的操作符

OPRAND 说明
-eq 即equal,测试两个数是否相等,若是则返回真
-ne 即not equal,测试两个数是否不等,若是则返回真
-gt 即greater than,测试第一个操作数是否大于第二个操作数,若是返回真
-ge 即greater equal,测试第一个操作数是否大于或等于第二个操作数,若是返回真
-lt 即less than,测试第一个操作数是否小于第二个操作数,若是返回真
-le 即less equal,测试第一个操作数是否小于或等于第二个操作数,若是返回真

字符测试

bash中字符测试域数值测试类似,可测试字符或字符串,只是OPRAND的形式不同

OPRAND 说明
== 测试两个字符串是否相等,若是则返回真
!= 测试两个字符串是否不等,若是则返回真
> 测试第一个操作数是否大于第二个操作数,若是返回真
>= 测试第一个操作数是否大于或等于第二个操作数,若是返回真
< 测试第一个操作数是否小于第二个操作数,若是返回真
<= 测试第一个操作数是否小于或等于第二个操作数,若是返回真
=~ 模式匹配,若左侧的字符串可以被右侧的模式匹配,则返回真

字符串大小比较 将逐位比较两个操作数中每一个字符的大小,若相等则比较下一位,直至比较结果不等,以该结果作为整个表达式的结果。

关于=~模式匹配测试,注意一下几点

  • 通常在[[ ]]中使用
  • 模式中可以使用行首、行尾锚定
  • 模式用不用加引号

如,测试stringA中是否包含oot:

#!/bin/bash
declare stringA=root
echo $stringA
[[ $stringA =~ oot ]] && echo "Matched." || echo "Not Match."
stringA=Linux
echo $stringA
[[ $stringA =~ oot ]] && echo "Matched." || echo "Not Match."

输出结果:

root
Matched.
Linux
Not Match.

此外,字符串测试还有单目测试符:

-n "STRING"		测试指定字符串是否不空,不空为真
-z "STRING"		测试指定字符串是否为空,空则为真

Tips

  • 用于字符测试时用到的操作数都应该使用引号
    • 不做变量替换使用单引号,做变量替换使用双引号
  • 要使用双中括号[[ ]]

文件测试

文件测试使用测试符对文件内容或某属性进行测试

单目测试

存在性测试
操作符 描述
-e FILE 测试文件是否存在
-a FILE 测试文件是否存在
大小测试(测试文件是否有内容)
操作符 描述
-s FILE 测试文件是否不空
类型测试
操作符 描述
-f FILE 测试文件是否为普通文件
-d FILE 测试文件是否为路径(即目录)
-b FILE 测试文件是否为块设备文件
-c FILE 测试文件是否为字符设备文件
-h FILE 测试文件是否为符号链接文件,同-L
-L FILE 测试文件是否为符号链接文件,同-h
-p FILE 测试文件是否为命名管道文件
-S FILE 测试文件是否为套接字文件

若文件不存在,则测试结果直接为假

权限测试
操作符 描述
-r FILE 测试当前用户对指定文件是否有读权限
-w FILE 测试当前用户对指定文件是否有写权限
-x FILE 测试当前用户对指定文件是否有执行权限

若文件不存在,则测试结果直接为假

特殊权限测试
操作符 描述
-g FILE 测试文件是否设置了SGID
-u FILE 测试文件是否设置了SUID
-k FILE 测试文件是否设置了sticky
打开性测试
操作符 描述
-t FD FD表示文件描述符,是否已经打开且与某终端相关
时间戳测试
操作符 描述
-N FILE 文件自上一次被读取之后是否被修改过
从属关系测试
操作符 描述
-O FILE 当前有效用户是否为文件属主
-G FILE 当前有效用户是否为文件属组

双目测试

操作符 描述
FILE1-nt FILE2 若FILE1比FILE2更新,则为真(若FILE1存在,FILE2不存在,也为真)
FILE1 -ot FILE2 若FILE1比FILE2更老,则为真
FILE1 -ef FILE2 若FILE1与FILE2引用了相同的设备以及inode,则为真

  • 测试/etc/inittab文件是否存在
    [ -e /etc/inittab ]
    
  • 测试/etc/rc.d/rc.sysinit文件是否可执行
    [ -x /etc/rc.d/rc.sysinit ]
    
  • 写一个脚本,给定一个文件,如果是一个普通文件,就显示之,如果是一个目录,亦显示之,否则,此为无法识别之文件
    #!/bin/bash
    FILE=/etc/rc.d/rc.sysinit
    if [ ! -e $FILE ]; then
    	echo "No such file."
    	exit6
    fi
    if [ -f $FILE ]; then
    	echo "Common file."
    elif [ -d $FILE ];then
    	echo "Directory."
     else
    	echo "Unknown."
     fi
    
  • 写一个脚本,完成:
    • 1、分别复制/var/log下的文件至/tmp/logs目录中
    • 2、复制目录时,使用cp -r
    • 3、复制文件是,使用cp
    • 4、复制链接文件,使用cp -d
    • 5、余下的类型,使用cp -a
    #!/bin/bash
    targetDir='/tmp/logs'
    [ -e $targetDir ] || mkdir $targetDir
    for fileName in /var/log/*; do
    	if [ -d $fileName ]; then
    		copyCommand='cp -r'
    	elif [ -f $fileName ]; then
    		copyCommand='cp'
    	elif [ -h $fileName ]; then
    		copyCommand='cp -d'
    	else
    		copyCommand='cp -a'
    	fi
    	$copyCommand $fileName $targetDir
    done
    

7. 脚本的参数

我们在执行命令时可以在命令后附加一个或多个参数,在脚本中也支持传递参数,向脚本传递参数的方式与使用命令相同,即SCRIPT ARG1 ARG2 ...

而传递的参数在脚本中可以向变量一样使用,这些参数存储在特定变量中。

向脚本传递的参数将被bash依次传递给$1,$2,$3……,如./test.sh root /etc/fstab linux
$1为root,$2为/etc/fstab,$3为linux

此处再次列出相关的特殊变量3

特殊变量 描述
$? 上一条命令的退出状态码
$# 参数的个数
$* 参数列表,会合并各个参数
$@ 参数列表,不会合并参数
$0 执行的命令或脚本名

shift

有时,我们事先并不知道用户会传递多少个参数给脚本,而又需要处理各个参数,如计算给出的所有数字的和。此时可以使用shift命令来切换各参数,即若有多个参数,shift后,当前参数剔除,先一个参数变为当前参数,以此类推,使用格式为:

shift [N]
	#N为数字,可省略,默认是1

如,脚本代码内容为

#!/bin/bash
echo $1
shift
echo $1
shift
echo $1

若给出了3个参数,则脚本将依次显示之

脚本返回值

默认情况下,当脚本的最后一条命令执行完成后,脚本将退出,我们也可以使用exit来显式控制脚本退出,使用方式为

exit RETURN_CODE

exit后指定一个退出状态码,即$?所查看的值,再次说明,0表示执行成功,而执行失败的值没有具体规定,但是一般将使用如下习惯:

代码 描述
0 命令成功完成
1 通常的未知错误
2 误用shell命令
126 命令无法执行
127 没有找到命令
128 无效的退出命令
128+x 使用Linux信号x的致命错误
130 使用Ctrl+C终止命令
255 规范以外的退出状态

  • 获取用户id号(不使用id命令),若其uid与gid相同,则显示Good guy.,否则显示Bad guy.
    #!/bin/bash
    USERNAME=$1
    if ! grep "^$USERNAME\>" /etc/passwd &> /dev/null; then
    	echo "No such user: $USERNAME."
    exit 1
    fi
    USERID=`grep "^$USERNAME\>" /etc/passwd | cut -d: -f3`
    GROUPID=`grep "^$USERNAME\>" /etc/passwd | cut -d: -f4`
    if [ $USERID -eq $GROUPID ]; then
    	echo "Good guy."
    else
    	echo "Bad guy."
    fi
    

8. 数组

关于数组的基础用法(声明,引用,赋值),前文已有说明, 此处从其他角度进行进一步介绍。

引用数组中的所有元素

使用数组名将引用数组的第一个元素,要引用其所有元素,可使用${ARRAY_NAME[*]}的形式

获取数组中某一元素的长度

${#ARRAY_NAME[INDEX]},同理,${#ARRAY_NAME}将获取数组中第一个元素的长度

${#ARRAY_NAME[*]}${#ARRAY_NAME[@]}可获取数组中所有有效元素的个数

因此,可以使用如下方式向数组中追加元素:

ARRAY_NAME[$#{ARRAY_NAME[*]}]=VAR

数组元素切片

数组切片的使用语法为

${ARRAY_NAME[@]:OFFSET:NUMBER}
	# OFFSET:偏移量
	# NUMBER:获取的元素个数

[root@localhost ~]# files=(/etc/[Pp]*)
[root@localhost ~]# echo $files
/etc/pam.d
[root@localhost ~]# echo ${files[*]}
/etc/pam.d /etc/passwd /etc/passwd- /etc/pbm2ppa.conf /etc/php.d /etc/php.ini /etc/pinforc /etc/pkcs11 /etc/pki /etc/plymouth /etc/pm /etc/pnm2ppa.conf /etc/polkit-1 /etc/popt.d /etc/postfix /etc/ppp /etc/prelink.conf.d /etc/printcap /etc/profile /etc/profile.d /etc/protocols /etc/pulse /etc/python
[root@localhost ~]# echo ${files[*]:2:4}
/etc/passwd- /etc/pbm2ppa.conf /etc/php.d /etc/php.ini

9. 字符串处理

字符串切片

字符串切片的使用格式为${VAR:OFFSET:NUMBER}OFFSET默认为0,NUMBER默认为0,如

[root@localhost ~]# name=Jerry
[root@localhost ~]# echo ${name:2:2}
rr
[root@localhost ~]# echo ${name::1}
J

可以使用负数,实现从右向左截取:

[root@localhost ~]# name=Jerry
[root@localhost ~]# echo ${name: -1}
y
[root@localhost ~]# echo ${name: -4}
erry

注意,冒号后有空格

基于模式取子串

  • ${VAR#*word}
    • word为指定的分隔符
    • 功能:自左而右搜索,删除VAR字符串开头至第一次出现word之间的所有字符,如
    [root@localhost scripts]# mypath="/etc/init.d/functions"
    [root@localhost scripts]# echo ${mypath#*e}
    tc/init.d/functions
    
  • ${VAR##*word}
    • word为指定的分隔符
    • 功能:自左而右搜索,删除VAR字符串开头至最后一次出现word之间的所有字符,如
    [root@localhost scripts]# mypath="/etc/init.d/functions"
    [root@localhost scripts]# echo ${mypath##*/}
    functions
    
  • ${VAR%word*}
    • word为指定的分隔符
    • 功能:自右而左搜索,删除VAR字符串末尾至第一次出现word(包括word)之间的所有字符,如
    [root@localhost scripts]# mypath="/etc/init.d/functions"
    [root@localhost ~]# echo ${mypath%/*}
    /etc/init.d
    
  • ${VAR%%word*}
    • word为指定的分隔符
    • 功能:自右而左搜索,删除VAR字符串末尾至最后一次出现word(包括word)之间的所有字符,如
    [root@localhost scripts]# mypath="/etc/init.d/functions"
    [root@localhost ~]# echo ${mypath%%/*}
    
    [root@localhost scripts]# mypath="etc/init.d/functions"
    [root@localhost ~]# echo ${mypath%%/*}
    etc
    

查找替换

  • ${var/PATTERN/SUBSTI}
    查找var所表示的字符串中,将第一次被 PATTERN所匹配到的字符串,替换为 SUBSTI所表示的字符串
    ${var/PATTERN}:以 PATTERN为模式查找 var中第一次匹配到的内容,将其删除
  • ${var//PATTERN/SUBSTI}
    查找var所表示的字符串中,将所有被 PATTERN所匹配到的字符串,全部替换为SUBSTI所表示的字符串
    ${var//PATTERN}:以PATTERN为模式查找var中所有匹配到的内容,将其删除
  • ${var/#PATTERN/SUBSTI}
    查找 var所表示的字符串中,行首被 PATTERN所匹配到的字符串,替换为SUBSTI所表示的字符串
    ${var/#PATTERN}:以 PATTERN为模式查找var中行首被匹配到的内容,将其删除
  • ${var/%PATTERN/SUBSTI}
    查找var所表示的字符串中,行尾被 PATTERN所匹配到的字符串,替换为 SUBSTI所表示的字符串
    ${car/%PATTERN}:以 PATTERN为模式查找 var中行尾被匹配到的内容,将其删除

字符串大小写转换

  • ${var^^}:将var中的所有小写字符转换为大写
  • ${var,,}:将var中的所有大写字符转换为小写

[root@localhost ~]# str=abcABC
[root@localhost ~]# echo ${str^^}
ABCABC
[root@localhost ~]# echo ${str,,}
abcabc
[root@localhost ~]# echo $str
abcABC

10. 函数

在脚本中若有大量重复且复杂的操作,开发者若也一遍一遍地写大量重复的代码,这显示时及其低效的,此时可以使用函数(Function) 来 处理,其直接作用之一就是代码重用

定义函数

函数的定义使用关键字function,可使用如下两种方式使用

function FUNC_NAME {
	COMMAND
}
# 此处的小括号应紧跟在函数名之后
FUNC_NAME() {
	COMMAND
}

调用函数

函数的调用直接给出函数名即可,若函数支持参数,直接在函数名后给出各参数,使用空格分隔即可。

如,写一个脚本,完成如下功能

  • 1、显示如下菜单
    disk) show disk info
    mem) show memory info
    cpu) show cpuinfo
  • 2、显示用户选定的内容;
#!/bin/bash
ShowMenu() {
cat << EOF
disk) show disk info
mem) show memory info
cpu) show cpu info
EOF
}
main() {
ShowMenu
read -p "Please choose an option: " option
case $option in
disk)
	df -h
	;;
mem)
	free -m
	;;
cpu)
	cat /proc/cpuinfo
	;;
*)
	echo "Wrong option"
esac
}
main

函数的返回值

事实上,函数也可看做是更小型的shell脚本,故其返回值与脚本类似,此处的说明相信很好理解

函数的执行状态返回值

同脚本相同,函数中最后一条指令的执行状态返回值即为函数的执行状态返回值,不同的是,在脚本中可使用exit退出脚本并执行执行转台码,而函数中使用return

函数的运行结果

函数的运行结果的引用依然类似于脚本(或命令),函数中的输出语句(如echoprintf等)、函数中的命令执行结果都可作为函数的运行结果

执行状态返回值保存在变量$?中,而执行结果的引用要使用命令引用4

函数中的变量

局部变量的使用

若在函数中使用了在主程序中声明的变量,重新赋值会修改主程序中的变量。如果不期望函数与主程序中的变量冲突,函数中使用变量都用local修饰,即使用局部变量

在函数中使用了在主程序中没有声明的变量,在函数执行结束后即被撤销,无论是否使用了local修饰符
但若在函数中没有使用declare,直接声明,如A=10,则变量为全局的

函数的参数

如上所述,可以向给脚本传递参数一样给函数传参,需要注意的是函数的$1与脚本的$1不同,如
调用脚本时使用SCRIPT_FILE ARG1 ARG2 ARG3,在该脚本中有一行内容FUNC1 root $1 $2,则,此时,对于脚本而言,$1$2$3分别为ARG1ARG2ARG3,而对于函数而言,$1$2$3分别为root$ARG1$ARG2

可以把脚本的全部位置参数,统统传递给脚本中某函数使用:$*

  • 1、写一个脚本,判定172.16.0.0网络内有哪些主机在线,在线的用绿色显示,不在线的用红色显示;要求,编程中使用函数
#!/bin/bash
CnetPing(){
	for i in {0..255}; do
		ping -c 1 -w 1 $1.$i
	done
}
BnetPing(){
	for j in {0..255}; do
		CnetPing $1.$j
	done
}
AnetPing(){
	for m in {0.255}; do
		BnetPing $1.$m
	done
}
netType=`echo $1 | cut -d'.' -f1`
if [[ $netType -gt 0 -a $netType -le 126 ]]; then
	AnetPing $1
elif [[ $netType -ge 128 -a $netType -le 191 ]]; then
	BnetPing $1
elif [[ $netType -ge 192 -a $netType -le 223 ]]; then
	CentPing $1
else
	echo "Wrong"
	exit 3
fi
  • 2、写一个脚本,完成如下功能(使用函数):
    • 1、脚本使用格式:
      mkscript.sh [-D|–description “script description”] [-A|–author “script author”] /path/to/somefile
    • 2、如果文件事先不存在,则创建;且前几行内容如下所示:
      #!/bin/bash
      # Description: script description
      # Author: script author
      #
    • 3、如果事先存在,但不空,且第一行不是“#!/bin/bash”,则提示错误并退出;
      如果第一行是“#!/bin/bash”,则使用vim打开脚本;
      把光标直接定位至最后一行
    • 4、打开脚本后关闭时判断脚本是否有语法错误
      如果有,提示输入y继续编辑,输入n放弃并退出;
      如果没有,则给此文件以执行权限;
#!/bin/bash
showMenu() {
	while true; do
		if [[ $# -lt 5 ]]; then
			echo "mkscript.sh [-D|--description script description] [-A|--author script author] /path/to/somefile"
			exit 6
		else
			return 0
		fi
	done
}
option() {
		case $1 in
		-D|--description)
			authName=$4
			desInfo=$2
			;;
		-A|--author)
			authName=$2
			desInfo=$4
			;;
		*)
			showMenu
		;;
		esac
}
creatFile() {
	if [[ -f $1 ]]; then
		if [ `head -1 $1` == "#!/bin/bash" ]; then
			vim + $1
		else
			echo "Wrong"
			exit 5
		fi
	else
		touch $1 && echo -e "#!/bin/bash\n# Description: $desInfo\n# Author: $authName\n#\n" > $1
		vim + $1
	fi
}
reedit() {
	read -p "Press y to edit,n to exit: " choose
	if [ "$choose" == "y" ]; then
		vim $1
	fi
	if [ "$choose" == "n" ]; then
		exit 0
	fi
}
syntax() {
	bash -n $1 &> /dev/null && chmod +x $1 || reedit $1
}
showMenu $*
option $*
creatFile $5
syntax $5

11. 信号捕捉

在脚本中可以直接捕获信号,以避免内其中的命令捕获而影响执行5

首先,此处将常用信号再次列出

信号 描述
1 SIGHUP 挂起进程,让一个进程不必重启即可重读其配置文件
2 SIGINT 中断进程,Ctrl+C
9 SIGKILL 杀死进程
15 SIGTERM 终止一个进程
18 SIGCONT 调回后台进程
19 SIGSTOP 停止一个进程,即送往后台,Ctrl+Z

在脚本中可以捕获信号,但是一般9与15信号不能被捕捉,使用trap可实现信号捕捉,其使用格式为

trap 'COMMAND' SIGNALS

捕捉到信号SIGNALS后,执行COMMAND

kill -l,也可以使用trap -l查看信号列表,一般,脚本中经常需要被捕获的信号为SIGHUP与SIGINT

#!/bin/bash
#
trap 'echo "quit"; exit 5' INT

for i in {1..254}; do
	if ping -w 1 -c 1 172.16.254.$i &> /dev/null; then
		echo "172.16.254.$i is up."
	else
		echo "172.16.254.$i is down."
	fi
done
#!/bin/bash

declare -a hosttmpfiles

trap 'mytrap' INT

mytrap() {
	echo "Quit"
	rm -f ${hosttmpfiles[@]}
	exit 1
}

for i in {1..50}; do
	tempfile=`mktemp /tmp/ping.XXXX`
	if ping -W 1 -c 1 192.168.18.$i &> /dev/null; then
		echo "192.168.18.$i is up." | tee $tempfile
	else
		echo "192.168.18.$i is down." | tee $tempfile
	fi
	hosttmpfiles[${#hosttmpfiles[*]}]=$tempfile

done

rm -f ${hosttmpfiles[@]}

四、例

  • 1、传递一个用户名参数给脚本,判断此用户的用户名跟其基本组的组名是否一致,并将结果显示出来
#!/bin/bash
if ! id $1 &>/dev/null; then
	echo "No such user."
	ecit 10
fi
if [ $1 == `id -n -g $1` ]; then
	echo "Yiyang"
else
	echo "Bu Yiyang"
fi
  • 2、传递一个参数(单字符就行)给脚本,如参数为q、Q、quit或Quit,就退出脚本,否则,就显示用户的参数
#!/bin/bash
if [ $1 = 'q' ];then
	echo "Quiting..."
	exit 1
elif [ $1 = 'Q' ];then
	echo "Quiting..."
	exit 2	
elif [ $1 = 'quit' ];then
	echo "Quiting..."
	exit 3 
elif [ $1 = 'Quit' ];then
	echo "Quiting..."
	exit 4	
else
	echo $1
fi
  • 3、判定所有用户是否拥有可登陆shell
#!/bin/bash
for userName in `cut -d: -f1 /etc/passwd`; do
	if [[ `grep "^$userName\>" /etc/passwd | cut -d: -f7` =~ sh$ ]]; then
		echo "login user: $userName."
	else
		echo "nologin user: $userName."
	fi
done
  • 4、接受一个参数,若为–add,添加用户user1…user10,若为–del, 删除用户user1…user10,其它:退出
#!/bin/bash
if [ $# -lt 1 ]; then
	echo "Usage:adminusers ARG"
	exit 7
fi
if [ $1 == '--add' ]; then
	for I in {1..10}; do
		if id user$I &> /dev/null; then
			echo "user$I exists."
			else
			useradd user$I
			echo user$I | passwd --stdin user$I &> /dev/null
			echo "Add user$I finished."
		fi
	done
elif [ $1 == '--del' ]; then
	for I in {1..10}; do
		if id user$I &> /dev/null; then
			userdel -r user$I
			echo "Delete user$I finished."
			else
			echo "No user$I."
		fi
	done
else
	echo "Unknown ARG"
exit 8
  • 5、在上述基础上,在–add或–del后加上用户名作为参数列表,要求添加或删除参数列表中给出的用户,并且给出帮助信息
#!/bin/bash
if [ $1 == '--add' ]; then
	for I in `echo $2 | sed 's/,/ /g'`; do
		if id $I &> /dev/null; then
			echo "$I exists."
		else
			useradd $I
			echo $I | passwd --stdin $I &> /dev/null
			echo "Add $I finished."
		fi
	done
elif [ $1 == '--del' ]; then
	for I in `echo $2 | sed 's/,/ /g'`; do
		if id $I &> /dev/null; then
			userdel -r $I
			echo "Delete $I finished."
		else
			echo "$I NOT exist."
		fi
	done
elif [ $1 == '--help' ]; then
	echo "Usage:adminuser2.sh --add USER1,USER2,... | --del USER1,USER2,... | --help"
else
	echo "Unknown options."
fi
  • 6、写一个脚本,使用形式如下:
    userinfo.sh -u username [-v {1|2}]
    -u选项用于指定用户,而后脚本显示用户的UID和GID
    若同时使用了-v:
    -v后面的值若是1,则额外显示用户udev家目录路径
    -v后面的值若为2,则额外显示用户的家目录路径和shell;
#!/bin/bash
[ $# -lt 2 ] && echo "Too less argements,quit" && exit 3
if [[ "$1" == "-u" ]]; then
	userName="$2"
	shift 2
fi
if [ $# -ge 2 ] && [ "$1" == "-v" ]; then
	verFlag=$2
fi
verFlag=${verFlag:-0}
if [ -n $verFlag ]; then
	if ! [[ $verFlag =~ [012] ]]; then
		echo "Wrong parameter."
		echo "Usage: `basename $0` -u UserName -v {1|2}"
		exit 4
	fi
fi
if [ $verFlag -eq 1 ]; then
	grep "^$userName" /etc/passwd | cut -d: -f1,3,4,6
elif [ $verFlag -eq 2 ]; then
	grep "^$userName" /etc/passwd | cut -d: -f1,3,4,6,7
else
	grep "^$userName" /etc/passwd | cut -d: -f1,3,4
fi
  • 7、写一个脚本,能对/etc/目录进行打包备份,备份位置为/backup/etc-日期.后缀
    1、显示如下菜单给用户:
    xz) xz compress
    gzip) gzip compress
    bip2) bzip2 compress
    2、根据用户指定的压缩工具使用tar打包压缩;
    3、默认为xz;输入错误则需要用户重新输入;
#!/bin/bash
[ -d /backup ] || mkdir /backup
cat << EOF
Plz choose a compress tool:
xz) xz compress
gzip) gzip compress
bzip2) bzip2 compress
EOF
while true; do
	read -p "Your option: " option
	option=${option:-xz}
	case $option in
	xz)
		compressTool='J'
		suffix='xz'
		break;;
	gzip)
		compressTool='z'
		suffix='gz'
		break;;
	bzip2)
		compressTool='j'
		suffix='bz2'
		break;;
	*)
		echo "Wrong option." ;;
	esac
done
tar ${compressTool}cf /backup/etc-`date +%F-%H-%M-%S`.tar.$suffix /etc/*

  1. 关于bash的特性,详见这篇文章 https://blog.csdn.net/xiyangyang410/article/details/85090293#bash_385 ↩︎

  2. 关于权限的相关介绍 https://blog.csdn.net/xiyangyang410/article/details/85090293#_1324 ↩︎

  3. 关于bash变量,详见 https://blog.csdn.net/xiyangyang410/article/details/85454040#bash_156 ↩︎

  4. 命令引用相关内容详见 https://blog.csdn.net/xiyangyang410/article/details/85090293#_555 ↩︎

  5. 关于信号相关内容,后续将详细介绍 ↩︎

你可能感兴趣的:(Linux)