Fork me on GitHub

Shell编程入门

认识Shell环境

  • Shell是工作在Linux内核与用户之间的==解释程序==,相当于操作系统的“外壳”,是向Linux内核传达用户指令的“翻译官”,通常指BASH(/bin/bash),常见的还有zsh、tcsh,用于和操作系统内核交互,操作系统内核控制底层硬件
  • windows下的Shell为cmd.exe
  • 查看当前的Shell的命令为:echo $SHELL
  • 可以手动选择Shell环境,直接在Shell中输入shell如:tcsh,相当于新建了一个子环境(父–子进程)
  • shell操作有交互式和非交互式两种
交互式 非交互式
人工干预、智能化程度低 需提前设计、智能化难度大
逐条解释执行、效率低 批量执行、效率高
方便在后台静悄悄地运行

shell脚本

  • shell脚本指提前写好的可执行代码,用来完成特定任务的文件,特点为:顺序、批处理,解释型程序
  • shell脚本编写步骤:
1. 理清任务 自然语言:步骤拆分、顺序化整理
2. 编写可执行语句 脚本语言:各步骤如何实现
3. 完善脚本 界面友好/结构规范/代码优化
  • shell脚本一般以.sh结尾,==第一行 #!/bin/bash== 用来申明Shell环境

  • shell脚本的免交互及输出处理

    • 免交互:通过选项 ==–stdin== 可以从标准输入读取字串;也可以从键盘或由另一个命令给出
    1
    2
    3
    // 修改root密码
    passwd --stdin root //从键盘读入
    echo 1234567 | passwd --stdin root //由echo命令给出
    • 输出处理:

      • 忽略无关输出:黑洞设备/dev/null
      1
      2
      相当于只能写入、不能读出的单向文件;存放到其中的数据都会丢失,用法:可执行语句 &> /dev/null,如:
      echo 1234567 | passwd --stdin root &> /dev/null
      • 记录错误输出:根据需要,将出错信息保存到指定文件
      1
      2
      是针对后台脚本的有效排错手段,适用于不便交互但又需要查看报错的情况,用法:可执行语句 2>/路径/文件,如:
      useradd root 2>/tmp/err.log
  • 命令的组合运用

    • 顺序分割–==使用分号==,格式:命令1;命令2;命令3…,依次执行,只有先后,没有逻辑关系
    • 逻辑“与”分割–==使用&&==,格式:命令1 && 命令2 && 命令3…,逻辑关系为“而且”(and),期望所有命令都能执行成功,一旦出现失败,后续命令不再执行
    • 逻辑“或”分割–==使用||==,格式:命令1 || 命令2 || 命令3…,逻辑关系为“或者”(or),任何一条成功都符合期望,只在前面的命令失败时,后续命令才执行
    • 管道–==使用|==,格式:命令1|命令2,后续命令要能正确处理传来的文本,否则无意义,如:
    1
    find /etc -type f | wc -l   //计算/etc下文件数量,将结果交给wc统计行数
  • shell脚本的运行方式

    • 作为指定shell解释程序的参数
    1
    2
    -sh 代码文件路径    ===     bash 代码文件路径
    -. 代码文件路径 === source 代码文件路径
    • 作为可独立运行的脚本程序
    1
    2
    为shell代码文件添加x权限,指定脚本路径即可运行.如:
    chmod +x /root/first.sh;/root/first.sh

标准输入输出和重定向

Unix/Linux基本思想:普通文件、目录、鼠标、键盘…都是以文件形式存在;要访问硬件设备,必须找对应的设备文件

I/O交互设备

类型 设备文件 文件描述号 默认设备 备注
标准输入 /dev/stdin 0 键盘 从此设备接收用户输入的数据
标准输出 /dev/stdout 1 显示器 通过此设备向用户报告正常的命令输出结果
标准错误输出 /dev/stderr 2 显示器 通过此设备报告执行中的错误信息

重定向:重新指定命令执行是I/O设备的方向

根据I/O方向和类型区分

类型 操作符 用途 举例
重定向输入 < 将文本输入来源由键盘改为指定的文件 mial -s ‘A Test Mail’ root@localhost < /root/mail.txt
重定向输出 > 将命令行的正常输出保存到文件,而非显示器 echo ‘nameserver 8.8.8.8’ > /etc/resolv.conf
重定向输出 >> 与“>”类似,但操作是追加而不是覆盖
重定向错误 2> 将命令行的错误输出保存到文件,而非显示器
重定向错误 2>> 与“2>”类似,但操作是追加而不是覆盖
混合重定向 &> 相当于“>”和“2>”

脚本逻辑

变量值及范围控制

  1. 引号在赋值中的应用
  • 双引号
1
2
3
- 在双引号内允许$扩展,可调用其他变量的值
- 出现特殊字符时,可采用\符号转义
- 当变量值不包括空格、制表符时,双引号通常被省略,如:A=CentOS7
  • 单引号
1
2
- 所有字符均视为该字符本身(无特殊含义)
- 不允许\转义
  1. read读入变量值
1
2
3
4
* 基本格式
- read 变量名.. ..
- read -p "提示信息" 变量名 .. .. //read -p "svr5 login" YOUR_NAME
- 启用read命令的 -s 选项,可关闭输入回显,适合敏感信息,提高安全性,如密码
  1. 变量的作用范围
  • 局部变量:只在定义此变量的shell环境有效;自定义变量默认都是局部变量
  • 全局变量:在当前shell及所有子shell环境下有效;子shell中若赋值同名变量,与父shell中变量无关

发布全局变量

1
2
3
export 局部变量名[=变量值] .. ..
- 直接定义/赋值指定的变量,作为全局变量发布
- 对已有的局部变量,只需发布不需赋值

数值运算及处理

bash内置机制仅支持整数运算

基本运算类别

加减乘除求模 ±*/%

1
2
3
4
5
6
1. expr工具:==expr 数值1 运算符 数值2==  
乘法操作需转义
2. 使用$[]表达式: ==$[数值1 运算符 数值2]==
* 乘法操作无需转义,运算符两侧可以无空格,引用变量可省略$符号,如:echo $[x+100-Y*2/7]
* 支持混合运算,乘除优先
* 支持乘方运算,如:echo $[10**3]

自增表达式

1
2
1. 加减乘除求模自增,如:i+=2 或 i=i+2 或 $[i+=2]
2. 如果步长为1,则支持如: --i,++i/i--,i++

let命令操作变量

1
操作变量值运算,并保存新结果,适用于不需要输出的情况,如:let  x++

使用随机整数

  1. 环境变量RANDOM:随机生成0~32767之间的一个整数
  2. 扩大随机数范围:多个随机数相乘
  3. 缩小随机数范围:取余数,如:$[RANDOM%1000]取0~999的整数

整数序列

使用seq命令

1
2
3
4
5
6
7
使用格式:默认分隔符为\n
- seq 末数 ;默认从1开始,步长为1
- seq 首数 末数
- seq 首数 步长 末数
参数选项:
-s:定义分隔符,如:seq -s' ' 3,结果“1 2 3”
-w:等宽显示,不足前面补0

bc计算器

bc运算器支持高精度的数值运算,输入bc进入交互式预算界面,quit退出,设置scale=n可约束小数位,也支持小数值的比较,大多数Linux系统已内置

使用举例:

  1. echo “scale=4;123.4*2.5” | bc
  2. echo “A>A>B” | bc

Shell变量

  • 变量:以固定名称存放的可能会变化的值
  • 定义/赋值变量
1
2
3
4
5
变量名=变量值
1. 如果变量名已存在,则相当于赋值
2. 等号两边不能有空格
3. 变量区分大小写
4. 变量名不能以数字开头,不要使用关键字和特殊字符
  • 查看/引用变量:$变量名 或 ${变量名}
  • 取消变量:退出shell变量自动失效 或 ==unset 变量名== 方式手动取消

变量的分类

  1. 存储类型:shell不作为高级编程语言,对存储类型的要求比较松散
  2. 使用类型
类型 说明 备注
环境变量 变量名通常大写,由系统维护,用来设置工作环境,其中只有个别变量用户可以直接修改 配置文件:/etc/profile,~/.bash_profile
位置变量 由bash内置,用来存储在执行脚本时提供的命令行参数 n,1,n>10==n,从1开始,当n>10,格式为 =={n}==
预定义变量 由bash内置,一类有特殊用途的变量,可以直接调用,但不能直接赋值或修改 参见下表与定义变量定义
自定义变量 用户自主设置、修改及使用

env-列出所有环境变量;
set-列出所有变量

常见环境变量:PWD,PATH,USER,LOGNAME,UID,SHELL,HOME,PS1,PS2…

变量名 含义 备注
$0 当前所在进程或脚本名
$$ 当前运行进程PID
$? 命令执行后的状态值,0-正常 1或其他-异常
$# 已加载位置变量个数 如传入参数个数判断: if[ $# -ne 2 ]
$* 所有位置变量的值

条件测试

返回状态值:$?

专用测试工具test

1
2
3
格式:
* test 选项 参数...
* [ 选项 参数... ]

文件状态检测:test的参数则为文件或目录

测试选项 含义 举例
-e 检测对象是否存在Exist [ -e /etc/test ];echo $?
-d 检测对象是否为目录Directory
-f 检测对象是否为文件File
-r 检测对象是否有可读权限Read
-w 检测对象是否有可写权限Write
-x 检测对象是否有可执行权限eXcute

整数值比较

测试选项 含义 举例
-eq 等于Equal [ $(who|wc -l) -eq 10 ]//判断登陆用户数等于10
-ne 不等于Not Equal
-ge 大于或等于 Greater or Equal
-le 小于或等于 Lesser or Equal
-gt 大于 Greater Than
-lt 小于 Lesser Than

组合多个条件

1
2
3
格式:
* test 测试条件1 -a或-o 测试条件2...
* [[ 测试条件1 &&或|| 测试条件2...]]

字符串比较

1
2
3
格式:
* [ 字符串1 选项 字符串2 ]
* [ 选项 字符串 ]
测试选项 含义 举例/说明
= 两字符串相同 [ $USER = ‘root’ ] && echo $USER
!= 两字符串不同
-z 字符串值为空 Zero
-n 字符串值不为空 Not zero

将条件取反

1
2
3
4
格式:
[ ! 测试条件 ]
举例:
[ ! 100 -gt 200] && echo YES

字符串处理

截取字符串

  1. 使用expr命令,==expr substr $var1 起始位置 截取长度==
  2. 使用cut命令
1
2
* 命令输出|cut -c 起始位置-结束位置,echo $var1|cut -c 5-6
* 命令输出|cut -d '分隔符' -f 字段编号....,echo "CentOS6.5" |cut -d 't' -f2 //OS6.5,相当于java中的split[2]
  1. 使用${}
1
2
3
格式:
* ${var1:起始位置:截取长度}
* ${var1::截取长度} //默认从头开始

字符串替换

使用${}

1
2
3
格式:
* ${var1/old/new} //只替换一个
* ${var1//old/new} //替换全部

tr单字替换工具

1
2
3
格式:
* 命令输出|tr 'abc' 'ABC' //abc替换为大写ABC
* 命令输出|tr -d 'abc' //删除所有的abc

路径分割

  • dirname “路径字符串”:取目录位置
  • basename “路径字符串”:取基本名称

使用随机字符串

  • 随机数变量:RANDOM
  • 特殊设备文件:/dev/urandom
  • UUID生成命令:uuidgen

随机信息的转换:md5sum

依据输入文本计算出256位(32字符)的MD5编码值

  • echo $RANDOM |md5sum
  • head -1 /dev/urandom|md5sum

命令替换

  1. 反撇号`
  • 适用场景:在命令行潜入另一个命令的输出结果;将命令的输出保存到指定变量
  • 优先执行反撇号内的命令,并将执行结果作为输入
  • 举例:rpm -qf which tr
  1. $(可执行命令),优点为==可嵌套使用==,举例:rpm -qf $(which tr)

流程控制

if语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//单分支
if 条件测试
then 命令序列
fi

//双分支
if 条件测试
then 命令序列1
else 命令序列2
fi

//多分支(嵌套)
if 条件测试1
then 命令序列1
elif 条件测试2 ;then
命令序列2
else
命令序列n
fi

说明:如果then要跟着条件测试写在一行,则需在then前加封号,如:

1
2
3
if 条件测试 ;then
命令序列
fi

for循环

1
2
3
4
for 变量名 in 值列表
do
命令序列
done

while循环

1
2
3
4
while 条件测试
do
命令序列
done

case分支

1
2
3
4
5
6
7
8
9
case 变量值  in
模式1)
命令序列1;;
模式2)
命令序列2;;
.. ..
*)
默认命令序列
esac

中断及退出控制

类型 含义
break 跳出当前所在循环体,执行循环体后的语句块
continue 跳过循环体内余下的语句,重新判断条件以便执行下一次循环
exit 退出脚本,可以指定返回的状态值,默认返回0

编写服务脚本

shell函数

type 名称 //查看名称对应类型

1
2
3
4
5
6
7
8
9
10
11
12
13
基本格式:
function 函数名(){
命令序列
.. ..
}
或🔥
函数名(){
命令序列
.. ..
}

调用: 函数名
传参: 函数名 参数1 参数2 .. .. //传递的值作为函数的位置参数

系统服务管理

  • 查看服务列表、自启状态
    • chkconfig --list [服务名]
    • chkconfig 服务名 on/off
  • 启动/停止/重启服务
    • service 服务名 start/stop/restart
    • /etc/init.d/服务名 start/stop/restart
  • ntsysv:可视化设置自启动状态
  1. shell脚本直接交给service控制,则将脚本或者脚本的快捷方式放到/etc/init.d/下即可
  2. shell脚本交给chkconfig工具管理,需设置适用级别、启动/停止顺序、服务说明
  • 添加设置
1
2
3
4
#!/bin/bash
#
# chkconfig:2345 10 95
# description:服务相关说明
  • 添加服务:chkconfig --add 服务名

文件的排序集统计

ls列表排序

  • -S:按文档大小降序排列
  • -t:按文档的修改时间降序排列
  • -r:反序排列

uniq去重工具

1
2
用法1:uniq 文件
用法2: 命令|uniq

缺点:==未排序的文本会出现重复现象==,需配合其他方式先排序,再去重
方法:sort 文件名|uniq -c

sort工具

1
2
3
4
5
6
7
8
用法1:sort 文件
用法2:命令|sort

常用选项:
* -u: 去除重复行
* -n:按数字升序排列
* -r:反向排序
* -k:优先对第几列内容排序,即优先对哪个字段排序,只是字段是通过显示顺序编号的

文本处理的的特殊应用

tac、rev反序输出

  • tac:以行为单位反序,与cat命令效果相反
  • rev: 以字符为单位反序,与echo命令效果相反

tee整合重定向

举例:uname -r|tee un.txt

unix2dos与dos2unix转换

windows和linux系统文本格式转换

1
2
3
4
安装:yum -y install unix2dos dos2unix
基本用法:
* unix2dos Linux文件
* dos2unix Windows文件

xargs多参数处理

背景:ls、mv、rm、cp等命令给出的参数长度不能超过2.5M,基于*匹配、find等方式处理大量文件时容易出错

多参数的分散处理

  • 利用find… … -exec… … 查找处理 //仅适用于处理文件或目录参数,如:find / -exec ls -lh {} ;
  • xargs工具:根据给定的参数组(以行为单位)分次执行目标命令行
1
2
3
4
5
用法1:提供参数的命令 | xargs 目标命令,如:find /|args ls -lh
用法2: xargs --arg-file=提供参数的文件 目标命令
选项:
* -I:定义标记,如:ls txt.log* | xargs -I{} cp {} {}.new
* -d: 制定分割符,默认按行分割,如:head -1 /etc/passwd |xargs -d: -I{} echo {}

expect预期交互控制

基于TCL编写的自动化交互式程序,可以用在Shell脚本中,为FTP、SSH等交互过程自动输送预先准备的文本或指令,而无需人工干预,出发的依据是预期会出现的特征提示文本

  • 安装:yum -y install expect
  1. 手动交互
  2. 自动交互
1
2
3
4
5
6
7
8
9
10
11
12
13
cat mike-ftp.sh
--------------------
spawn ftp 192.168.4.5
expect "):"
send "mike\r"
expect "Password:"
send "123456\r"
expect "ftp>"
send "put install.log\r"
expect "ftp>"
send "quit\r"
---------------------
epxect mike-ftp.sh
  • 调整执行环境:添加Sha-Bang环境声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
cat mike-ftp.sh
-----------------------
#!/usr/bin/expect
spawn ftp 192.168.4.5
expect "):"
send "mike\r"
expect "Password:"
send "123456\r"
expect "ftp>"
send "put install.log\r"
expect "ftp>"
send "quit\r"
------------------------
sh mike-ftp.sh
  • SSH登陆免交互举例
1
2
3
4
5
6
7
8
#!/usr/bin/expect
set host 192.168.4.5
set user mike
set password "1234567"
spawn ssh $user@$host
expect "password:"{send "$password\r"}
expect "\[$user\@"{send "pwd>/tmp/$user.txt;exit\r"}
expect eof

应用技巧

正则表达式

使用“一串符号”来描述有共同属性的数据

egrep测试工具

  • 用法1:egrep [选项] ‘正则表达式’ 文件
  • 用法2:前置命令|egrep [选项] ‘正则表达式’

选项

1
2
3
4
5
-i:忽略大小写
-v: 取反
-c:统计匹配行数
-q:静默、无任何输出
-n:显示出所匹配结果所在的行号

基本元字符

类型 含义 示例 说明
^ 匹配行首 ^abc 或 ^# 以abc开头的行 或 以#开头的行(比如注释行)
$ 匹配行尾 abc$ 或 ^$ 以abc结尾的行 或 空行
. 单个字符 . 除换行符\n以外的任意单个字符
+ 最少匹配一次 a+ 或 (abc)+ 一个或多个连续的a 或 一个或多个连续的abc
? 最多匹配一次 a? 或 (abc)? 0个或1个a 或 0或1个abc
* 匹配任意次数 a* 或 (abc)* 或 .* 0个或多个连续的a 或 0个或多个连续的abc 或 任意长度的任意字符串
{n} 匹配n次 (ab){3} 匹配ababab
{n,m} 匹配n~m次 (ab){1,3} 匹配ab、abab、ababab
{n,} 匹配至少n次 (ab){2,} 匹配2个及以上连续的ab
[] 匹配范围内的单个字符 [a-z] 匹配任意小写字母
[]内加^ 可取反 [^a-z] 匹配任意非小写字母
() 组合为整体 (ab) 连续的ab
或者 root|bin 匹配root、bin
\b 单词边界 \broot\b 匹配root,不匹配keroot、rooty、brooty等字符串
< 单词的开头 <th 匹配以th开头的单词
> 单词的结尾 th> 匹配以th结尾的单词

awk文本过滤

awk:模式扫描及处理语言,创始人Aho、Weinberger、Kernighan,基于模式匹配检查输入的文本,逐行处理并print结果,目前最常用的实际是GNU版本的gawk

  • 用法1:前置命令|awk [选项] ‘[条件]{编辑指令}’
  • 用法2:awk [选项] ‘[条件]{编辑指令}’ 文件,如:awk -F “:” ‘{print $1,$2} /etc/passwd’

条件的表现形式

  • 正则表达式,/正则表达式/ 🔥 ~匹配、!~不匹配,如:awk -F: ‘/^ro/{print}’ /etc/passwd
  • 数值/字符串比较
  • 逻辑比较
  • 运算符

选项

1
2
-F:指定分隔符,可省略,默认空格或Tab位,如:awk -F: '$1==ENVIRON["USER"]{print $3}' /etc/passwd  //输出当前用户的UID信息
-f:调用awk脚本进行处理

awk内置变量

变量 用途
FS 保存或设置字段分隔符,如 FS=“:”
$n 指定分隔符的第n个字段,如$1、$3分别表示第1、第3列
$0 当前读入的整行文本内容
NF 记录当前处理的字段个数(列数)
NR 记录当前已读入行的数量(行数)
FNR 保存当前处理行在元文本内的序号(行号)
FILENAME 当前处理的文件名
ENVIRON 调用Shell环境变量,格式:ENVIRON[“变量名”]

awk处理的时机:可单独使用,也可以同时一起使用,具体如下:

  • 行前处理,BEGIN{}:读入第一行文本之前执行,一般用来初始化操作
  • 逐行处理,{}:逐行读入文本执行相应的处理,是最常见的编辑指令块
  • 行后处理,END{}:处理完最后一行文本之后执行,一般用来输出处理结果
1
awk 'BEGIN{print NR}END{print NR}' m.txt

awk的流程控制

  • if分支,如:if(条件){编辑指令1}else if(条件){编辑指令12}…else{编辑指令n}
1
awk -F: 'BEGIN{i=0;j=0}{if($3<500){i++}else{j++}}END{print i,j}'     /etc/passwd
  • while循环,如:while(条件){编辑指令}
  • do while循环,如:do {编辑指令}while(条件)
  • for循环,如:for(初值;条件;步长){编辑指令}
关键字 含义
break 结束当前的循环体
continue 中止本次循环,转入下一次循环
next 跳过当前行,读入下一行文本开始处理
exit 结束文本读入,转入END{}执行,没有则直接退出

sed文本流处理

sed:Stream EDitor,流式编辑器,基于模式匹配过滤及修改文本,逐行处理并将结果输出到屏幕,可实现文本过滤/删除/替换/复制/剪切,以及导入/导出等各种操作

  • 用法1:前置命令|sed [选项] ‘编辑指令’,如:se -n /etc/inittab |sed -n ‘4,7p’
  • 用法2:sed [选项] ‘编辑指令’ 文件
1
编辑指令由“[定址符]处理动作”组成,[定址符]的格式为“[地址1,[地址2]]”

选项:

1
2
3
4
5
6
-n:屏蔽默认输出
-i:直接修改文件内容
-f:使用sed脚本
-e:可指定多个处理动作
-r:启用扩展的正则表达式,若与其他选项一起使用,应作为首个选项
{}:可组合多个命令,以分号分隔

处理动作:替换操作的分隔符“/”可用其他字符,如#、&等以便于修改文件路径

操作符 用途 指令示例
p 打印行 2,4p 输出2,3,4行 🔥 2p;4p输出第2行、4行
d 删除行 2,4d 删除第2,3,4行
s 字符串替换 s/old/new/ 将每行第1个old替换为new
s/old/new/3 将每行第3个old替换为new
s/old/new/g 将所有的old替换为new
4,+10p 加10行 输出第4行及其后10行内容
n 表示下一行 p:n输出奇数行
n:p输出偶数行
$= 输出文件的行数 sed -n ‘$=’ a.txxt
i 行前插入文本 2iYY 在第2行前添加文本行YY
4,7iYY 在第4-7行每一行钱添加文本行YY
a 行后插入文本 2aYY 在第2行后插入YY
c 替换当前行 2cYY 将第2行内容修改为YY
r 读取文件,结合-i选项才会存入,否则只输出 3r b.txt 在第3行下发插入文本b.txt
w 保存到文件,以覆盖方式 3w b.txt 将第3行保存到b.txt

sed复制剪切

  • 模式空间:存放当前处理的行,将处理结果输出;若当前行不符合处理条件则原样输出;处理完当前行再读入下一行来处理
  • 保存空间:作用类似剪贴板,默认存放一个空行\n
    选项:
1
2
3
4
5
6
-H:模式空间 追加 到 保持空间 ,即复制
-h:模式空间 覆盖 到 保持空间 ,即复制
-G:保持空间 追加 到 模式空间 ,即粘贴
-g:保持空间 覆盖 到 模式空间 ,即粘贴

sed '1,3H;$G' reg.txt //把第1~3行复制到文件末尾

实战脚本

基本知识

  1. 查看用户信息:id gsuis

awk&sed应用实战

本文标题:Shell编程入门

文章作者:行锋

发布时间:2018年12月19日 - 15:12

最后更新:2019年02月14日 - 16:02

原始链接:https://chetaofeng.github.io/2018/12/19/Linux-Shell编程入门/

许可协议: 署名-非商业性使用 转载请保留原文链接及作者

-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!