Shell脚本学习指南

作者:Danbo 2015-8-3

脚本编程语言与编译型语言的差异

许多中型、大型的程序都是用编译型语言写的,例如:C、C+、Java等。这类程序只要从源代码(Source Code)转换成目标代码(object code),便能直接通过计算机来执行。

编译型语言的好处是高效,缺点是:他们多半运作于底层,所处理的是字节、整数、浮点数或是其他机器层级的对象。而脚本语言Shell通常是解析型(interpreted)的。这类程序的执行,是由解析器(interpreter)读入程序代码,并将其转换成内部的形式。解析器本身是一般的编译型程序。

查找文本

以grep程序查找文本,传统上有三种程序:
1.grep:使用基本正则表达式(Basic  Regular Expression,BRE)
2.egrep:扩展grep使用扩展正则表达式(Extended Regular Expression),不过会消耗更多的资源。
3.fgrep:快速grep匹配固定字符串而非正则表达式,grep与egrep只能匹配单个正则表达式,而fgrep使用不同的算法,却能匹配多个字符串。

简单的grep

grep -F danbo,-F选项,以查找固定字符串danbo。事实上,只要匹配的模式里面未包含正则表达式的meta字符表达式的meta字符,grep默认行为模式就等于使用了-F。
grep的语法格式:
-E  使用扩展正则表达式进行匹配。grep -E等同于egrep。
-F  使用固定字符串进行匹配。grep -F可取代传统的。
-i   模式匹配时忽略字母大小写差异。
-q  静默地。不输出结果,echo $? 返回0成功,返回1没有找到匹配的内容。
-v  显示不匹配模式的行。
-c  计算找到搜索字符串的次数。
-n  输出含有搜索字符串所在行的行号。

正则表达式

正则表达式是一种表示方式,让你可以查找匹配特定准则的文本。
常用到的字符及其意义:
\  逃脱字符,关闭后续字符的特殊意义。有时则具有特殊意义:\{...\}
.  匹配任意单个字符,但是nul除外。
* 匹配之前的任意的单个字符。当位于第一个字符时,无意义。
^ 匹配在行或字符串的起始处。BRE:仅在正则表达式的开头出具有意义。EBR:置于任何位置都具有特殊含义。
$ 匹配前面的正则表达式,在字符串或行尾。BRE:仅在正则表达式结尾处具特殊含义。ERE:置于任何位置都具有特殊含义。
[...]  方括号表达式,匹配方括号内的任意字符。连接符“-”指的是连续字符的范围。“^”置于第一个位置取反。
\{n,m\}  区间表达式,匹配在它前面的单个字符重现的次数,\{n\}指的是重新n次;\{n,\}重新n次及以上;\{n,m\}指的是前面单个字符重新n至m次。
\( \)  将“\(”与“\)”的模式存储在特殊的“保留空间”。最多可以将9个独立的子模式(sbupattern)存储在单个模式中。可以通过\1至\9,被重复使用在相同模式里。例如\(ab\).*\1指的是匹配ab组合的两次重新,中间可以存在任意数目的字符。例如可以匹配:ab..........ab。另一个例如:\(ab\)\(cd\).*\2\1则是匹配:abcd........cdab注意后面的\n表示匹配前面第几个小括号。
+  匹配前面正则表达式的一个或多个实例。
?  匹配前面正则表示式的零个或一个实例。
|   匹配|左右的正则表达式。
( )   匹配括号括起来的正则表达式。

POSIX字符集

扩展正则表达式

我们比较一下BRE与ERE的区别,主要体现在以下几点:

BRE只定义了4组元字符:
[]      用于在多个字符中选定一个字符进行匹配,[]内可以有-以示范围,但-本身不是元字符
.         用于匹配任意字符
^       用于匹配时表示“非”的含义,还有一个用法是匹配行首
$       用于匹配行尾

ERE在此基础上增加了3组元字符的定义:
{}       用于表示重复匹配的次数。BRE中只将{}当作普通字符对待,要使用此功能必须加\进行转义,即“\{\}”
()       用于分组。BRE中只将()当作普通字符对待,要使用此功能必须加\进行转义,即“\(\)” 比如:(why)+匹配一个或多个why
|       完全为ERE新增的多项匹配能力定义的,BRE无多项匹配能力,只将|作普通字符对待。比如:(read|write)+表示一个或多个read或者一个或多个write。

注意|为所有运算中优先级最低的,^abcd|efgh$意思是“匹配字符串的其实处是否有a、b、c、d,或者结尾处是否有e、f、g、h”,这个和^(abcd|efgh)$不一样,后者表示“以abcd开头或者以efgh结尾”

ERE运算符的优先级

扩展:“\”<与“\>”分别匹配word的开头语结尾,其中word是由字母、数字、下划线组成的。我们称这类字符为单词组成(word-constituent)。例如:\<chop匹配chopsticks,却不匹配lambchop。

额外的GUN正则表达式运算符

在文本里进行替换

sed基本用法:
-e   当有多个命令需要应用时,就必须使用-e了。
-f script-file 读取脚本文件中的sed命令。
-n 安静模式,仅显示经过sed名利处理的行。通常与p一起使用。
/字符扮演定界符(delimiter)的角色,用于分割正则表达式与替换文本,任何可现现实的字符都能作为定界符。标点符号也是可以的(比如:分号、冒号、逗号)。
例如:echo /home/tolstoy/ | sed 's;\(/home\)/tolstory/;\1/lt/;'
注意:\1表示匹配前面()之间的内容,且向后引用最多可以用到9个,这点前面已经讲过了。
sed 's/Tolstory/Camus/g' < example.txt #g表示全局性(Global)。另一个结尾指定数字,指示第n个匹配出现才去取代。
例如:sed -e 's/foo/bar/g' -e 's/chicken/cow/g' < example.txt
不过,如果有很多要编辑的话,我们就需要将编辑命令全部放入脚本文件中,再使用sed搭配-f选项:
cat fixup.sh
s/foo/bar/g
s/chicken/cow/g
...
sed -f fixup.sh example1.xml > example2.xml
上面意思为读取fixup.sh内的编辑命令,然后修改example1.xml的内容,修改后结果保存在example2.xml文件中。

sed的运作原理

sed读入每个文件,一次读入一行,将读取的行放入内存的一个区域---称之为模式空间(pattern space)。类似变量:内存的一个区域在编辑命令的指示下可以改变,所有编辑上的操作都会应用到模式空间的内容。当所有操作完成后,sed会将模式空间的最后内容打印到标准输出,再回到开始处,读取另外一个输入行。

sed匹配特定行

我们可以限制一条命令应用到哪些行,只要在命令前置一个地址(address)即可。
例如:sed '/named/ s/nologin/fuck/g' </etc/passwd
先定为含有named的行,然后在该行中进行替换。
范围:
指定行的范围:sed -n '10,21p' /etc/passwd  #打印10-21行。
指定替换范围:sed '/foo/,/bar/ s/baz/quux/g' /etc/passwd  #从含有foo的行开始匹配到bar的行,再进行替换。 
否定正则表达式:
sed '/used/!s/new/used/g'  #将没有used的每一行里所有的new改为used。

行 v.s. 字符串

大部分简易程序都是处理输入的数据的行,想grep与egrep,以及sed。在这些情况下,不会有内嵌的换行字符出现在将要匹配的数据中,^与$分别表示行的开头与结尾。
然而对于awk、Perl、Python,所处理的就多半是字符串。不过这类都会让你标明每条输入记录的定界符,所以有可能单独的输入行里会有内嵌的换行符,这种情况下,^与$是无法匹配内嵌的换行字符的。

记录和字段

一条记录(record)指的是相关信息的单个集合;字段(filed)指的是记录的组成一部分。

/etc/passwd每条记录中的每个字段的含义如下:
root:x:0:0:root:/root:/bin/bash

使用cut选定字段

cut命令是用来剪下文本文件里的数据,文本文件可以是字段类型或是字符类型
例如:cut -d : -t 1,5 /etc/passwd    #范围1,5也可以使用1-5
-c 以字符为参考
-d 指定定界符,默认是制表字符(Tab)
-f  filed指定第几个字段。

使用join连接字段

join命令可以将多个文件结合在一起,每个文件里的每条记录,都共享一个键值(key),键值值得是记录中的主字段,通常会是用户名称、个人姓氏、员工编号之类的数据。
语法:join [options...] file1 file2
-1 field1
-2 field2
标明要结合的字段。-1 field1指的是从file1取出field1,而-2指的是从file2中取出field2。
-o file.field
输出file文件中的field字段。一般的字段则不打印。除非使用多个-o选项,即可显示多个输出字段。
-t separator
使用separator作为输入字段分割字符,而非使用空白。

使用awk重新编排字段

awk设计的重点就是在字段与记录上:awk读入输入记录(通常是一些行),然后自动将各个记录切分为字段。awk将每条记录内的字段数据,存储到内建变量NF。
默认以空白分割字段---例如空格与制表符字段(或两者混用),你可以将FS变量设置为一个不同的值。
如需字段值,则是搭配$字符。通常$之后会接着一个数值常数,也可能是接着一个表达式,不过多半是使用变量名称。例如:
awk '{print $1,$NF}'  #打印第1个与最后一个字段
awk 'NF > 0 {print $0}'  #打印非空行 等同于:grep -v "^$"

设置字段分割字符

例如:awk -F: '{print $1,$5}' /etc/passwd
-F选项会自动地设置FS变量。请注意,程序不必直接参照FS变量。awk的输入、输出分割字符用法是分开的,这点与其他工具程序不同。也就是说,必须设置OFS变量,改变出字段分割字符。方式是在命令行里使用-v选项。例如:

[root@localhost uestc]# awk -F: -v 'OFS=**' '{print $1,$NF}' ./passwd
root**/bin/bash

打印行

[root@localhost uestc]# awk -F: '{print "User", $1, "is really", $NF}' /etc/passwd
User root is really /bin/bash

简单明了的print语句,如果没有任何参数,则等同于print $0,即显示整条记录。
当我们使用printf语句时,用法如下:

[root@localhost /]# awk -F: '{printf "User %s is really %s\n",$1,$NF}' /etc/passwd
User root is really /bin/bash
awk的print语句会自动提供最后的换行字符,然后,如果使用printf语句,则用户必须要通过\n转义序列的使用自己提供。
请记住:在print的参数间用都好隔开!,否则,awk将连接相邻的所有值。

起始于清除

BEGIN与END这两个特殊的“模式”,它们提供awk程序起始(startup)与清楚(cleanup)操作。
BEGIN与END的语句块是可选用的。如需设置,习惯上它们应分别位于awk程序的开头与结尾处。你可以有数个BEGIN与END语句块,awk会按照它们出现在程序的顺序来执行:所有的BEGIN语句块都应该放在起始处,所有END语句块也应该放在结尾。例如:

[root@localhost /]# awk 'BEGIN {FS=":" ; OFS="**"} {print $1,$NF}' /etc/passwd
root**/bin/bash

排序文本

未提供命令行选项时,整个记录都会根据当前locale所定义的次序排序。在传统的的C locale中,也就是ASCII顺序。这里我们介绍一个排序命令:sort
sort [options] [files]
-b   忽略开头的空白。
-c   检查输入是否已正确地排序。如果未经过排序,退出码(exit code)非零值,不会有任何输出。
-d   字典顺序,仅处理英文字母、数字、空格。
-f    忽略英文字母的大小写。
-i    忽略无法打印的字符,即只识别
-k   指定排序的键值,即按照哪个域进行排序。
-n   以整数类型排序。
-o outfile   将输出写到指定的文件,而非标准输出。
-r    倒置排序(descending),默认是由小到大(ascending)。
-t    指定分割符,默认空格
-u    uniq即丢弃所有相同键值的记录,即删掉重复行。
例如:如果你想把结果输入到输入文件中,则不同能重定向了,此时我们需要使用-o
例如:sort -r number.txt -o number.txt
例如:sort -t ":" -k 2 /etc/passwd
使用点号字符位置,则比较的开始(一对数字的第一个)或结束(一对数字的第二个)在该字符位置处:
-k2.4,-k5.6指的是从第二个字段的第四个字符开始比较,一直比到第五个字段的第六个字符。
例如:sort -t ":" -k 3.1 /etc/passwd 
例如:sort -t ":" -k3n -k4n /etc/passwd   #按照第一个key值排序,然后再按照第二个key值排序。

sort的稳定性

稳定性(Stable)指的是:相同的记录输入顺序是否在输出时时也可保持原状,这点sort并不稳定,不过我们可以通过--stable选项来使其变得稳定。

删除重复

有时,将数据流里连续重复的记录删除是很有必要的,前面我们介绍了sort -u的用法,不过它的消除操作是依据匹配的键值,而非匹配的记录我们则使用uniq。
-c    可在每个输出行之前加上改行重复的次数。
-d    仅显示重复的行。
-u    仅显示未重复的行。
默认情况下不带任何选项:显示唯一的、排序后的记录,重复则仅取唯一行。

重新格式化段落

fmt通过将所有非空白行的长度设置为几乎相同,来进行简单的文本格式化。
-s    仅仅切割较长的行,但不会将短行结合成长行。
-w n    则设置输出行宽度为n个字符(默认通常为75个)。
例如:fmt -s -w 10 << END_OF_DATA

[root@localhost ~]# fmt -s -w 10 << END
> Welcome to UESTC
> Ali
> Taobao
> Baidu Group
> END
Welcome
to UESTC
Ali
Taobao
Baidu
Group

计算行数、字数以及字符数

wc -c    计算字节数(bytes)
wc -l    计算行数
wc -w   计算字数
wc -m   计算字符数(character)
注意:wc在处理的时候,一定要在文件末尾存在换行符,否则统计行数是不正确的:

[root@localhost ~]# echo "UESTC" | wc -l
1
[root@localhost ~]# echo -n "UESTC" | wc -l
0

提出开头或结尾数行

显示前n条记录:
head -n n
head -n
awk 'FNR<=n'
sed -e nq
sed nq

观察动态观察结尾:tail -n 10 -f /var/log/messages

几个值得加入工具箱的命令

dd 可以以用户指定的块大小与数量拷贝数据,可以进行大小写转换,以及字符转换,备份等。
dd应用实例:
1.将本地的/dev/hdb整盘备份到/dev/hdd
dd if=/dev/hdb of=/dev/hdd
2.备份/dev/hdb全盘数据,并利用gzip工具进行压缩,保存到指定路径
dd if=/dev/hdb | gzip > /root/image.gz
3.将压缩的备份文件恢复到指定盘
gzip -dc /root/image.gz | dd of=/dev/hdb
4.备份磁盘开始的512字节第一扇区文件,即MBR信息到指定文件
dd if/dev/hda of=/root/image count=1 bs=512
count=1指仅拷贝一个块;bs=512指块大小为512个字节。
5.拷贝内存内容到硬盘
dd if=/dev/mem of=/root/mem.bin bs=1024 (指定块大小为1k)
6.增加swap分区文件大小
Step1:创建一个大小为256M的文件
dd if=/dev/zero of=/swapfile bs=1024 count=262144
Step2:把这个文件变成swap文件:
mkswap /swapfile
Step3:启用这个swap文件:
swapon /swapfile
Step4:编辑/etc/fstab文件,使在每次开机使自动加载swap文件:
/swapfile swap swap default 0 0
7.销毁磁盘数据
dd if=/dev/urandom of=/dev/hda1 #注意利用随机的数据填充硬盘,在某些必要的场合可以用来销毁数据
8.测试硬盘的读写速度
dd if=/dev/zero bs=1024 count-1000000 of=/root/1GB.file;
dd if=/root/1GB.file bs=64k | dd of=/dev/null
9.修复硬盘
dd if=/dev/sda of=/dev/sda

file 用于检测文件类型

od 八进制码转存(octal dump)命令,显示ASCII码。此命令主要用来查看保存在二进制文件中的值。

strings 用来查看二进制文件,例如声音、图像文件有时会在开头出包含一些有用的文本数据。
例如:

[root@localhost ~]# strings -a 12.jpg | head -c 256 | fmt -w 65
JFIF rExif Apple iPhone 5s 7.1.1 2014:06:12 23:11:30 0221 0100
2014:06:12 23:11:30 2014:06:12 23:11:30 dApple iOS

tr 它可以非常容易地试下sed许多最基本的功能。它可以用一个字符来替换另一个字符,或者可以完全除去一些字符。同时也可以用它来除去重复字符。
-c 用字符串1中字符集的补集替换此字符集,
-d 从标准输入中删除字符串1中所有字符
-s 删除所有重复出现字符序列,只保留一个。
例如:

[root@localhost ~]# echo Hello,world,root,2015 | tr -c "[0-9]" "*"
*****************2015*
可以看出,我们使用0-9,添加-c选项后,会把0-9替换为其补集,这时补集自然不包含0-9,而包含很多其他的字符,接下来就把其他字符都替换成*号,但不包含数字。

变量与算术

Shell脚本里面经常出现一些简单的算术运算,例如每经过一次循环,变量就会加1,。Shell为内嵌算术提供了一种标记法,称为算术展开(arithmetic expansion)。Shell会对$((...))里面的算术表达式进行计算,再将计算后的结果放回到命令的文本内容。

最常见的命令是export,其用法是将变量放入环境里。环境是一个名称与值的简单列表,可供所有执行中的程序使用。新的进程会从其父进程继承环境。

export命令仅将变量加到环境中,如果你要从程序的环境中删除变量,则要用到env命令,env也可临时改变环境变量的值:
env -i PATH=/usr/bin/local
-i 选项时用来初始化环境变量的,仅传递命令行上指定的变量给程序使用。
unset 命令从执行内中的Shell中删除变量与函数。默认情况下,它会解除变量设置。
-f 删除指定的函数
-v 默认选项,删除指定的变量。

展开运算符

在Shell下,有更复杂的形式可用与更特殊的情况。这些形式都是将变量名括在花括号里${variable},然后再增加额外的语法以告诉Shell该做些什么。

替换运算符:
${varname:-word}  如果varname存在且非null,则返回其值;否则,返回word。
${varname:=word}  如果varname存在且非null,则返回其值;否则,设置它为word,并返回其值。
${varname:?word}  如果varname存在且非null,则返回其值;否则,显示varname:word。
${varname:+word}  如果varname存在且非null,则返回word;否则返回null。为了测试变量的存在。
上述中“:”是可选的,如果没有的话,则将存在且非空改为存在。

模式匹配运算符:
${path#/*/*}  从前匹配最短删除或替换。
${path%/*/*}  从后匹配最短删除或替换。
${#variable}  返回$variable值里的字符长度。

位置参数:

$#  提供传递到Shell脚本或函数的参数总数。
while [ $# != 0 ]
do
  case $1 in
  ...
  esac
  shift
done
$* & $@  将所有命令行参数传递给脚本或函数所执行的程序。
$*  将所有命令行参数视为单个字符串。
$@  将所有命令行参数视为单独的个体。

Shell内置变量

算术展开

“位与”与“逻辑与”的区别:
对于两个整型量,逻辑与只判断两个均不为0,则为True,但是按位与则将两个数转为二进制,对于每个位进行与运算,如果结果不为0,则为True。

退出状态

退出状态及其含义如下图所示:

exit n   目的是从Shell脚本返回一个退出状态给脚本的调用者。

test命令

test中-a的优先级高于-o
注意:-a的使用与&&的适用范围不同,"&&"和"||"只能用在[]之间,而-a与-o能用在[]之内。
例如:test -f file && echo true||echo false 等同于 [ -f file ] && echo true|| echo false

Shell脚本学习指南笔记的更多相关文章

  1. Shell脚本学习指南笔记(一)

    脚本语言通常是解释型的,这类程序的运行.是由解释器读入程序代码,并将其转换成内部的形式, 再执行,解释器本身是一般的编译型程序. 第一行的开头处使用#!这两个字符,当内核扫描到改行的其余部分,看是否存 ...

  2. 【Shell脚本学习指南笔记】重定向文件描述符 2>&1

    如: make > results 2>&1 重定向 > results让文件描述符1(标准输出)作为文件results,接下来的重定向2>&1有两个部分.2& ...

  3. 学习笔记之Shell脚本学习指南 & sed与awk & 正则表达式

    正则表达式_百度百科 http://baike.baidu.com/link?url=ybgDrN2WQQKN64_gu-diCqdeDqL8LQ-jiQ-ftzzPaNUa9CmgBRDNnyx50 ...

  4. 笔记——shell脚本学习指南

    <shell脚本学习指南>机械工业出版 ISBN 987-7-111-25504-8 第2章 2.4 初级陷阱 1.当今的系统,对#!这一行的长度限制从63到1024个字符都有,尽量不要超 ...

  5. 转 shell脚本学习指南

    shell脚本学习指南 以下八点不敢说就能成为你shell脚本学习指南de全部,至少可以让你编写出可靠的shell脚本. 1. 指定bashshell 脚本的第一行,#!之后应该是什么?如果拿这个问题 ...

  6. 《Shell脚本学习指南》学习笔记之自定义函数

    Shell的函数在使用之前必须先定义,定义格式: [ function ] funname [()] { action; [return int;] } 可以带function fun()定义,也可以 ...

  7. 《Shell脚本学习指南》学习笔记之变量、判断和流程控制

    变量 定义变量 可以使用export和readonly来设置变量,export用于修改或打印环境变量,readonly则使得变量不得修改.语法: export name[=word] ... read ...

  8. 《shell脚本学习指南》学习笔记之入门

    为什么要使用shell脚本? shell脚本能够轻易处理文件与目录之类的对象,而且是各UNIX系统之间经过POSIX标准化的通用的功能,因此Shell脚本只要“用心写”一次,即可应用到很多系统上,因此 ...

  9. shell脚本学习积累笔记(第一篇)

    (1)首先,今天在执行shell脚本./test.sh时抛出“/bin/sh^M: bad interpreter: No such file or directory”的异常,百度后,才知道这是由于 ...

随机推荐

  1. 小程序-支持的最小像素px

    经过我手机多次测试,支持的最小px为: 8px;

  2. iOS -- MBProgressHUB

    高级: http://www.jianshu.com/p/485b8d75ccd4 //只有小菊花 - (void)indeterminateExample { // Show the HUD on ...

  3. Android---简单的动画

  4. VS中的 MD/MT设置 【转】

    VS系列工具作为目前微软主打的集成开发环境,在历经了近20多年的发展后,到如今已经可以 说是Windows平台上各种IDE环境中的翘楚了.很多别的开发工具已经难望其项背了,如今VS2010也已经面市很 ...

  5. AngularJS的简单表单验证

    代码下载:https://files.cnblogs.com/files/xiandedanteng/angularjsCheckSimpleForm.rar 代码: <!DOCTYPE HTM ...

  6. C - The C Answer (2nd Edition) - Exercise 1-16

    /* Revise the main routine of the longest-line program so it will correctly print the length of arbi ...

  7. ios You app information could not be saved. Try again. If the problem persists, contact us

    ios You app information could not be saved. Try again. If the problem persists, contact us  大概意思:你的a ...

  8. 解决Eclipse中C++代码显示Symbol &#39;std&#39; could not be resolved的问题

    第一次在Eclipse中写C++代码,写了一个简单的hello world程序,还没有等我编译.就报出了各种错误,但是这么简单的代码.怎么可能这么多错误.于是没有理会.编译执行后,能够正常输出!!!H ...

  9. Cocos2d-x移植安卓的笔记

    一.下载所需软件 Java SDK   http://www.oracle.com/technetwork/java/javase/downloads/index.html  Windows x64 ...

  10. USB通讯协议

    首先要了解USB枚举过程(自己百度) https://blog.csdn.net/MyArrow/article/details/8270029 USB通讯协议 0. 基本概念 一个[传输](控制.批 ...