大多数Linux工具都支持BRE,但是只有一些工具支持ERE。
RE字符 | 含义及范例 |
^word | 含义:待查找的字符串(word)在行首 范例:查找行首为#开始的那一行,并列出行号 grep -n '^#' re.txt |
word$ | 含义:待查找的字符串(word)在行尾 范例:查找行尾为!开始的那一行,并列出行号 grep -n '!$' re.txt |
. | 含义:匹配任意单个字符,除了换行符。点符必须匹配一个字符。 |
含义:转义字符,将特殊符号的特殊含义去到 | |
* | 含义:重复零个到无穷多个的前一个字符 |
[list] | 含义:从字符集合的RE字符里面找出想要的字符 范例:查找含有(gl)或(gd)的那一行,需要特别留意的是,在[]当中代表一个待查找的字符,例如“a[afl]y”代表查找的字符串可以是aay,afy,aly,即[afl]代表a或f或l的意思 |
[n1-n2] | 含义:从字符集合的RE字符里面找出想要选取的字符范围 范例:查找含有任意数字的一行。需要特别留意,在字符集合[]中的减号-是有特殊含义的,它代表两个字符之间的所有连续字符。但这个连续与否与ASCII编码有关,因此,你的编码要设置正确。例如,所以大写字符为[A-Z]。 |
[^list] | 含义:从字符集合的RE字符里面找出不要的字符串或范围 |
{n,m} | 含义:连续n到m个前一个RE字符。若为{n}则是连续n个的前一个RE字符,若为{n,}则是连续n个以上的前一个RE字符 范例:在g与g之间有2个到3个的o存在的字符串,即(goog)(gooog): grep -n 'go{2, 3}g' re.txt |
注意:正则表达式的特殊字符与一般在命令行输入命令的通配符并不相同。例如,在通配符中,*代表零到无限多个字符的意思,但是在正则表达式中,*则是重复0到无穷多个的前一个RE字符的意思。
BRE特殊字符组
特殊符号 | 含义 |
[:alnum:] | 代表英文大小写字符及数字,即0-9,A-Z,a-z |
[:alpha:] | 任何英文大小写字符,即A-Z,a-z |
[:blank:] | 代表空格键与[Tab]键 |
[:cntrl:] | 代表键盘上面的控制按键,即包括CR,LF,Tab,Del等 |
[:digit:] | 代表数字,即0-9 |
[:graph:] | 除了空格符(空格键与[Tab]键)外的其他所有按键 |
[:lower:] | 代表小写字符,即a-z |
[:print:] | 代表任何可以被打印出来的字符 |
[:punct:] | 代表标点符号,即" ' ? ! ; : # $ |
[:upper:] | 代表大写字符,即A-Z |
[:space:] | 任何产生空格的字符,包括空格键[Tab]CR等 |
[:xdigit:] | 代表十六进制的数字类型,因此包括0-9,A-F,a-f的数字与字符 |
RE字符 | 含义及范例 |
? | 重复前面的字符0次到1次 |
+ | 重复前面的字符1次或多次 |
{m}、{m, n}(区间) | RE字符准确出现m次;RE字符至少出现m次,至多出现n次。gawk中要使用区间,必须指定--re-interval |
exp1|exp2 | 逻辑OR(正则表达式和管道符号之间不能有空格,否则它们也会加到正则表达式模式中) |
在《sed编辑器基本用法》一文中我们已经介绍了Linux世界中使用最广泛的两个命令行编辑器之一——sed,今天我们来介绍另外一个:gawk。gawk是Unix中原始awk程序的GNU版本,比sed功能更加强大,因为它提供了一个类编程环境,允许我们修改和重新组织文件中的数据。gawk的特点如下:
gawk程序的基本格式如下:gawk options program file
其支持的选项如下表:
选项 | 描述 |
-F fs | 指定分隔行中数据字段的文件分隔符 |
-f file | 指定读取程序的文件名 |
-v var=value | 定义gawk程序中的一个变量及其默认值 |
-mf N | 指定要处理的数据文件中的最大字段数 |
-mr N | 指定数据文件中的最大数据行数 |
-W keyword | 指定gawk的兼容模式或者警告等级。用help选项来列出所有可用的关键字。 |
我们可以在命令行或者shell脚本中使用gawk。
gawk程序脚本是由一对花括号定义的,你必须将脚本命令放在两个括号之间。由于gawk行假定脚本是单个文本字符串,所以还必须用单引号将脚本命令圈起来:
linux-osud:~/temp # echo "just a test" | gawk '{print $1}'
just
这个命令会显示输入流中每行的第一个数据字段。
gawk允许我们将多条命令合成一个普通程序,只要使用分号将多个命令分隔起来就行:
linux-osud:~/temp # echo "My name is Allan ." | gawk '{$4="Alne"; print $0}'
My name is Alne .
跟sed编辑器一样,gawk允许我们将程序存储在文件中,然后在命令行上引用它们:
linux-osud:~/temp # gawk -F: -f script2 /etc/passwd
Batch jobs daemon's userid is 25
User for Avahi's userid is 486
User for Avahi IPv4LL's userid is 499
bin's userid is 1
user for colord's userid is 490
Daemon's userid is 2
dnsmasq's userid is 494
...
gawk允许我们指定程序脚本命令何时允许。默认情况下,gawk从输入读入一行文本,然后执行针对文本行中的数据执行脚本。有时,我们可能需要在处理数据之前(比如创建报告的标题)允许一些命令。为了做到这点,我们可以使用BEGIN关键字。它会强制先执行BEGIN关键字后面指定的程序脚本,然后再读取数据:
linux-osud:~/temp # gawk 'BEGIN {print "This is a test report"}'
This is a test report
我们可以在BEGIN块中放置任何类型的gawk命令,比如给变量赋默认值等。
和BEGIN关键字相反,END关键字允许我们指定一个程序脚本,在gawk读取数据之后执行:
linux-osud:~/temp # gawk 'BEGIN {print "Hello World !"} {print $0} END {print "Bye bye"}'
Hello World !
This is a test
This is a test
This is another test
This is another test
Bye bye
gawk会先执行BEGIN块中的代码,然后处理输入流中的数据(使用Ctrl+D可以结束输入),最后执行END块中的代码。
注意:跟shell变量不同,引用gawk变量时,变量名前不加美元符。
gawk使用内建变量来在程序数据中引用特定功能。gawk将数据定义成数据行和数据字段:数据行是一行数据(默认用换行符分隔),而数据字段是行中一个单独的数据元素(默认用空白符比如空格或制表符分隔)。下面介绍gawk的数据字段和数据行变量:
变量 | 说明 |
$0 | 整个数据行 |
$1 | 数据行中第一个数据字段 |
$2 | 数据行中第二个数据字段 |
$n | 数据行中第n个字段 |
FIELDWIDTHS | 由空格分隔开的定义了每个字段确切宽度的一列数字,一旦设置该变量,gawk会忽略FS变量 |
FS | 输入字段分隔符 |
RS | 输入数据行分隔符,默认为换行符 |
OFS | 输出字段分隔符,默认为一个空格 |
ORS | 输出数据行分隔符,默认为换行符 |
看下面有些例子:
# 默认OFS为一个空格
linux-osud:~/temp # gawk 'BEGIN{FS=","} {print $1,$2,$3}' data1
data11 data12 data13
data21 data22 data23
data31 data32 data33
# 更改OFS
linux-osud:~/temp # gawk 'BEGIN{FS=","; OFS="-"} {print $1,$2,$3}' data1
data11-data12-data13
data21-data22-data23
data31-data32-data33
# 使用FIELDWIDTHS变量来读取数据行,而不是用FS来划分字段
linux-osud:~/temp # gawk 'BEGIN{FIELDWIDTHS="3 5 2 5"} {print $1,$2,$3,$4}' data1b
100 5.324 75 96.37
115 -2.34 91 94.00
058 10.12 98 100.1
除了字段和数据行分隔符变量,gawk还提供了一些其他内建变量来帮助我们了解数据中正在做什么以及从shell环境中提取信息,这些变量列举如下:
变量 | 说明 |
ARGC | 当前命令行参数个数 |
ARGIND | 当前文件在ARGV中的位置 |
ARGV | 包含命令行参数的数组 |
CONVFMT | 数字的转换格式(参加printf语句),默认为%.6g |
ENVIRON | 当前shell环境变量及其值组成的关联数组 |
ERRNO | 当读取或关闭输入文件发生错误时的系统错误号 |
FILENAME | 用作gawk输入数据的数据文件的文件名 |
FNR | 当前数据文件中的数据行数 |
IGNORECASE | 设成非零时,忽略gawk命令中出现的字符串的字符大小写 |
NF | 数据文件中的字段总数 |
NR | 已处理的输入数据行数目 |
OFMT | 数字的输出格式,默认值是%.6g |
RLENGTH | 由match函数所匹配的子字符串的长度 |
RSTART | 由match函数所匹配的子字符串的起始位置 |
看下面几个例子:
# ARGC和ARGV
linux-osud:~ # gawk 'BEGIN{print ARGC,ARGV[0],ARGV[1]}' data1
2 gawk data1
可以看到gawk不把单引号中的程序脚本认为是命令行参数。
linux-osud:~ # gawk '
> BEGIN {
> print ENVIRON["HOME"]
> print ENVIRON["PATH"]
> }'
/root
/sbin:/usr/sbin:/usr/local/sbin:/root/bin:/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games
ENVIRON使用关联数组来提取shell变量,关联数组用文本作为数组的索引值,而不是shell中数组那样使用数值。
linux-osud:~ # gawk 'BEGIN {FS=":"; OFS=":"} {print $1, $NF}' /etc/passwd
at:/bin/bash
avahi:/bin/false
avahi-autoipd:/bin/false
bin:/bin/bash
colord:/sbin/nologin
daemon:/bin/bash
dnsmasq:/bin/false
ftp:/bin/bash
利用NF定位到最后一个数据字段。
linux-osud:~/temp # cat data1
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
# FNR
linux-osud:~/temp # gawk 'BEGIN {FS=","} {print $1,"FNR="FNR}' data1 data1
data11 FNR=1
data21 FNR=2
data31 FNR=3
data11 FNR=1
data21 FNR=2
data31 FNR=3
# NR
linux-osud:~/temp # gawk 'BEGIN{FS=","} {print $1,"FNR="FNR,"NR="NR} END{print "There were",NR,"records processed"}' data1 data1
data11 FNR=1 NR=1
data21 FNR=2 NR=2
data31 FNR=3 NR=3
data11 FNR=1 NR=4
data21 FNR=2 NR=5
data31 FNR=3 NR=6
There were 6 records processed
虽然FNR和NR类似,但从例子可以看出区别:如果只使用一个数据文件作为输入,那么FNR和NR的值将会相同。如果使用多个数据文件作为输入,那么FNR的值会在处理每个数据文件时被重置,而NR的值则会继续计数,直到处理完整个文件。
在gawk中给变量赋值类似于在shell脚本中给变量赋值——使用赋值语句:
linux-osud:~/temp # gawk '
> BEGIN {
> testing="This is a test"
> print testing
> }'
This is a test
给变量赋值后,就可以在gawk脚本中任何地方使用该变量了(未赋值的变量默认为空字符串)。
我们也可以用gawk命令行为gawk程序变量赋值。这允许我们在普通代码外设置值,即使修改这个值。比如:
linux-osud:~/temp # echo "One,Two,Three,Four,Five" | gawk -f script1 n=2
Two
linux-osud:~/temp # echo "One,Two,Three,Four,Five" | gawk -f script1 n=3
Three
gawk的数组为关联数组。关联数组与数字数组不同之处在于它的索引值可以是任意文本字符串,每个索引字符串都必须是唯一的,并且唯一的标识赋给它的数据元素。这有点类似于其他编程语言的哈希表的概念。
看下面的例子:
# 定义数组变量
linux-osud:~/temp # gawk 'BEGIN{capital["China"] = "Beijing"; print capital["China"]}'
Beijing
linux-osud:~/temp # gawk 'BEGIN{
> var[1] = 34
> var[2] = 3
> total = var[1] + var[2]
> print total
> }'
37
# 遍历数组变量
linux-osud:~/temp # gawk 'BEGIN{
> var["a"] = 1
> var["b"] = 2
> var["g"] = 3
> var["m"] = 4
> for (test in var)
> {
> print "Index:"test," - Value:",var[test]
> }
> }'
Index:m - Value: 4
Index:a - Value: 1
Index:b - Value: 2
Index:g - Value: 3
# 删除数组变量
linux-osud:~/temp # gawk 'BEGIN{
> var["a"] = 1
> var["g"] = 2
> for (test in var)
> {
> print "Index:",test," - Value:",var[test]
> }
> delete var["g"]
> for (test in var)
> {
> print "Index:",test," - Value:",var[test]
> }
> }'
Index: a - Value: 1
Index: g - Value: 2
Index: a - Value: 1
gawk支持几种类型的匹配模式来过滤数据行,与sed编辑器大同小异。BEGIN和END关键字就是两个特殊的模式,下面再介绍几种模式。
我们可以使用基本正则表达式(BRE)或扩展正则表达式(ERE)来过滤程序脚本要作用在数据流的哪些行上。使用正则表达式时,正则表达式必须出现在它控制的程序代码的左花括号之前:
linux-osud:~/temp # cat data1
This is a test.
This is a trial.
This is an example.
linux-osud:~/temp # gawk 'BEGIN{FS=","} /test/{print $1}' data1
This is a test.
关于BRE和ERE两种表达式会在后面的博客中介绍。
匹配操作符(matching operator)允许我们将正则表达式限定在数据行中的特定数据字段上。匹配操作符是波浪线(~)。我们要一起指定匹配操作符、数据字段变量以及要匹配的正则表达式,比如:$1 ~ /^data/
$1代表数据行中的第一个数据字段。上面这个表达式会过滤出第一个字段以文本data开头的所有数据行。
linux-osud:~/temp # cat data1
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
linux-osud:~/temp # gawk 'BEGIN{FS=","} $2 ~ /^data2/ {print $0}' data1
data21,data22,data23,data24,data25
linux-osud:~/temp # gawk -F: '$1 ~ /allan/ {print $1,$NF}' /etc/passwd
allan /bin/bash
linux-osud:~/temp
也可以使用!符号来排除正则表达式的匹配:$1 !~ /expression/
linux-osud:~/temp # gawk -F: '$1 !~ /allan/ {print $1,$NF}' /etc/passwd
at /bin/bash
avahi /bin/false
avahi-autoipd /bin/false
bin /bin/bash
colord /sbin/nologin
daemon /bin/bash
dnsmasq /bin/false
ftp /bin/bash
games /bin/bash
gdm /bin/false
lp /bin/bash
mail /bin/false
man /bin/bash
messagebus /bin/false
news /bin/bash
nm-openconnect /sbin/nologin
nobody /bin/bash
nscd /sbin/nologin
ntp /bin/false
polkitd /sbin/nologin
postfix /bin/false
pulse /sbin/nologin
root /bin/bash
rpc /sbin/nologin
rtkit /bin/false
srvGeoClue /sbin/nologin
sshd /bin/false
statd /sbin/nologin
tftp /bin/false
uucp /bin/bash
wwwrun /bin/false
除了正则表达式,我们还可以在模式匹配中使用数学表达式:
linux-osud:~/temp # gawk -F: '$4 == 0 {print $1}' /etc/passwd
root
比如我们可以用上面的语句找出所有属于root用户组的用户。
我们可以使用任意的普通数学比较表达式:
注意:也可以对文本数据使用表达式,但必须小心:
linux-osud:~/temp # gawk -F, '$1=="data" {print $1}' data1
linux-osud:~/temp # gawk -F, '$1=="data11" {print $1}' data1
data11
linux-osud:~/temp # gawk -F, '$1>="data11" {print $1}' data1
data11
data21
data31
linux-osud:~/temp # gawk -F, '$1>="aata11" {print $1}' data1
data11
data21
data31
linux-osud:~/temp # gawk -F, '$1>="eata11" {print $1}' data1
可以看到,对于文本,比较的时候是使用ASCII值进行比较的。
gawk程序支持如下结构化命令:
if-then-else语句:
if (condition) statement1; else statement2
while语句:
while (condition)
{
statements
}
do-while语句:
do {
statements
} while (condition)
for语句:
for(variable assignment; condition; iteration process)
下面看一些例子:
linux-osud:~/temp # cat data4
10
5
13
50
34
# if
linux-osud:~/temp # gawk '{if ($1 > 20) print $1}' data4
50
34
linux-osud:~/temp # gawk '{
> if ($1 > 20)
> {
> x = $1 * 2
> print x
> }
> }' data4
100
68
# if else
linux-osud:~/temp # gawk '{
> if ($1 > 20)
> {
> x = $1 * 2
> print x
> } else
> {
> x = $1 / 2
> print x
> }}' data4
5
2.5
6.5
100
68
# 放在一行时,需要使用分号
linux-osud:~/temp # gawk '{if ($1 > 20) print $1*2; else print $1/2}' data4
5
2.5
6.5
100
68
linux-osud:~/temp # cat data5
130 120 135
160 113 140
145 170 215
# while
linux-osud:~/temp # gawk '{
> total = 0
> i = 1
> while (i < 4)
> {
> total += $i
> i++
> }
> avg = total / 3
> print "Average:", avg
> }' data5
Average: 128.333
Average: 137.667
Average: 176.667
# break、continue
linux-osud:~/temp # gawk '{
> total = 0
> i = 1
> while (i < 4)
> {
> total += $i
> if (i == 2)
> break
> i++
> }
> avg = total / 2
> print "The average of the first two data elements is:",avg
> }' data5
The average of the first two data elements is: 125
The average of the first two data elements is: 136.5
The average of the first two data elements is: 157.5
# do while
linux-osud:~/temp # gawk '{
> total = 0
> i = 1
> do
> {
> total += $i
> i++
> } while (total < 150)
> print total }' data5
250
160
315
# for
linux-osud:~/temp # gawk '{
> total = 0
> for (i=1; i<4; i++)
> {
> total += $i
> }
> avg = total / 3
> print "Average:",avg
> }' data5
Average: 128.333
Average: 137.667
Average: 176.667
虽然我们可以使用print打印,但是print的输出格式比较单一。如果需要比较精细的控制输出格式,可以使用printf。printf的使用与C语言中的使用基本相同,所以此处不详细介绍,仅列举几个例子:
linux-osud:~/temp # gawk 'BEGIN{
> x = 10 * 100
> printf "The answer is:%en", x
> }'
The answer is:1.000000e+03
linux-osud:~/temp # gawk 'BEGIN{FS=","} {printf "%s ", $1} END {printf "n"}' data1
data11 data21 data31
linux-osud:~/temp # gawk '{
> total = 0
> for ( i = 1; i < 4; i++ )
> total += $i
> avg = total / 3
> printf "Average: %5.1fn", avg
> }' data5
Average: 128.3
Average: 137.7
Average: 176.7
linux-osud:~/tem
控制符说明:
控制字母 | 说明 |
c | 将一个整数作为ASCII字符显示 |
d | 显示一个整数值 |
i | 显示一个整数值 |
e | 科学计数法显示 |
f | 显示一个浮点数 |
g | 用科学计数法或者浮点数中的较短者显示 |
o | 显示八进制值 |
s | 显示文本字符串 |
x | 显示十六进制值 |
X | 显示十六进制值,但是用大写字母 |
除了控制字母,还有三个修饰符:
函数 | 说明 |
atan2(x, y) | x/y的反正切,以弧度为单位 |
cos(x) | x的余弦 |
exp(x) | x的指数函数 |
int(x) | x的整数部分,同C的floor函数 |
log(x) | x的自然对数 |
rand( ) | 0到1之间的随机浮点值 |
sin(x) | x的正弦 |
sqrt(x) | x的平方根 |
srand(x) | 随机数种子 |
位操作函数 | |
and(v1, v2) | v1与v2与运算 |
compl(val) | val的补运算 |
lshift(val, count) | 左移 |
rshift(val, count) | 右移 |
or(v1, v2) | 位或 |
xor(v1, v2) | 异或 |
函数 | 说明 |
asort(s [, d]) | 将数组s按数据元素数值排序。索引值会被替换成表示新的排序顺序的连续数字。另外,如果指定了d,则排序后的数组会存储在数组d中 |
asorti(s [, d]) | 将数组s按索引值paixu.shengcheng的数组会将索引值作为数组元素值,用连续数字索引来表明排序顺序。另外,如果指定了d,排序后的数组会存储在数组d中 |
gensub(r, s, h [, t]) | 查找变量$0或目标字符串t(如果提供了的话)来匹配正则表达式r。如果h是一个以g或G开头的字符串,就用s替换掉匹配的文本。如果h是一个数字,它就表示要替换掉第几处r匹配的地方。 |
gsub(r, s [, t]) | 查找变量$0或目标字符串t来匹配正则表达式r。如果找到了,就全部替换成字符串s |
index(s, t) | 返回字符串t在字符串s中的索引值;如果没找到的话就返回0 |
length([s]) | 返回字符串s的长度;如果没有指定的话,就返回$0的长度 |
match(s, r [, a]) | 返回字符串s中的正则表达式r出现位置的索引。如果指定了数组a,它会存储s中匹配正则表达式的那部分 |
split(s, a [, r]) | 将s用FS字符或正则表达式r(如果指定了的话)分开放到数组a中。返回字段的总数 |
sprintf(format, variables) | 用提供的format和variables返回一个类似与printf输出的字符串 |
sub(r, s [, t]) | 在变量$0或目标字符串t中查找正则表达式r的匹配。如果找到了,就用字符串s替换掉第一处匹配 |
substr(s, i [, n]) | 返回s中从索引i开始的n个字符组成的子字符串。如果未提供n,则返回s剩下的部分 |
tolower(s) | 将s中的所有字符转换成小写 |
toupper(s) | 将s中所有的字符转换成大写 |
有的字符串函数使用比较复杂,有的则比较简单。看几个例子:
linux-osud:~ # gawk 'BEGIN{x = "testing"; print toupper(x); print length(x) }'
TESTING
7
linux-osud:~ # gawk 'BEGIN{
> var["a"] = 1
> var["g"] = 2
> var["m"] = 3
> var["u"] = 4
> asort(var, test)
> for ( i in test )
> print "Index:", i, " - valu:", test[i]
> }'
Index: 1 - valu: 1
Index: 2 - valu: 2
Index: 3 - valu: 3
Index: 4 - valu: 4
linux-osud:~/temp # gawk 'BEGIN{ FS=","} {
> split($0, var)
> print var[1], var[5]
> }' data1
data11 data15
data21 data25
data31 data35
函数 | 说明 |
mktime(dataspec) | 将一个按YYYY MM DD HH MM SS [DST]格式指定的日期转换成从UTC到现在经历的秒数(称为时间戳) |
strftime(format [, timestamp]) | 将当前时间的时间戳或timestamp转化成用shell函数格式date( )的格式化日期 |
systime( ) | 返回当前时间时间戳 |
看一个例子:
linux-osud:~/temp # gawk 'BEGIN {
> date = systime()
> day = strftime("%A, %B %d, %Y", date)
> print day
> }'
Wednesday, May 06, 2015
自定义函数使用function关键字,格式如下:
function name([variables])
{
statements
}
函数名必须能够唯一标识函数,可以在调用gawk的程序中传给这个函数一个或多个变量。
在定义函数时,它必须出现在所有代码块之前(包括BEGIN代码块):
linux-osud:~/temp # cat data2
Riley Mullen
123 Main Street
Chicago, IL 60601
(321)555-1234
Frank Williams
456 Oak Street
Indianapolis In 46201
(317)555-9875
Haley Snell
4231 Elm Street
Detroit. MI 48201
(313)555-4938
linux-osud:~/temp # gawk '
> function myprint()
> {
> printf "%-16s - %sn", $1, $4
> }
> BEGIN{FS="n"; RS=""}
> {
> myprint()
> }' data2
Riley Mullen - (321)555-1234
Frank Williams - (317)555-9875
Haley Snell - (313)555-4938
创建函数库:
linux-osud:~/temp # cat funclib
function myprint()
{
printf "%-16s - %sn", $1, $4
}
function myrand(limit)
{
return int(limit * rand())
}
function printthird()
{
print $3
}
linux-osud:~/temp # cat script4
BEGIN{FS="n"; RS=""}
{
myprint()
}
linux-osud:~/temp # gawk -f funclib -f script4 data2
Riley Mullen - (321)555-1234
Frank Williams - (317)555-9875
Haley Snell - (313)555-4938
]]>一般我们更习惯说sed为linux命令,但更准确的说法应该是sed流编辑器。和普通的交互式文本编辑器不同,流编辑器是在编辑器处理数据之前基于预先提供的一组规则来编辑数据流;而交互式文本编辑器(比如vim)则是通过键盘命令来交互式的插入、删除或者替换数据中的文本。sed编辑器可以基于输入到命令行的或是存储在命令文本文件中的命令来处理数据流中的数据。它每次从输入读入一行,用提供的编辑器命令匹配数据、按照命令中指定的方式修改流中的数据,然后将生成的数据输出到STDOUT。在流编辑器将所有的命令与一行数据进行匹配后,它会读取下一行数据并重复这个过程。在流编辑器处理完流中所有的数据行后,它就会终止。
sed命令的格式为:sed options script file .
选项参数(options)允许你修改sed命令的行为,sed命令常用的选项如下:
选项 |
描述 |
-e script | 在处理输入时,将script中指定的命令添加到运行的命令中;如果需要多个命令,也可用-e选项 |
-f file | 在处理输入时,将file中指定的命令添加到运行的命令中 |
-n | 不要为每个命令生成输出,等待print命令来输出 |
替换(substitute,s)命令的格式为:s/from/to/ .
(1)普通替换
linux-osud:~/temp # echo "This is a test" | sed 's/test/big test/'
This is a big test
sed编辑器自身不会修改文本文件中的数据,它只会将修改后的数据发送到STDOUT:
linux-osud:~/temp # cat data1
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
linux-osud:~/temp # sed 's/dog/cat/' data1
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
linux-osud:~/temp # cat data1
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
使用-e选项,我们可以在命令行上面使用多个编辑器命令:
linux-osud:~/temp # sed -e 's/brown/green/; s/dog/cat/' data1
The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.
linux-osud:~/temp # sed -e '
> s/brown/green/
> s/fox/elephant/
> s/dog/cat/' data1
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
注意:
如果有大量要处理的sed命令,可以将他们放进一个文件中(此时,不用在每个命令后面加分号,sed知道每一行都是一条单独的命令),然后使用-f选项来指定文件:
linux-osud:~/temp # cat script1
s/brown/green/
s/fox/elephant/
s/dog/cat/
linux-osud:~/temp # sed -f script1 data1
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
(2)替换标记
看下面一个例子:
linux-osud:~/temp # echo "This test is just a test." | sed 's/test/example/'
This example is just a test.
可以看到替换命令只替换了第一次出现的test。默认情况下,替换命令会遍历所有的行,但只会替换每行第一处匹配的地方,要让替换命令对一行中不同地方出现的文本都起作用,必须使用替换标记(substitution flag):s/from/to/flags .
有四种可用的替换标记:
linux-osud:~/temp # sed 's/test/trial/' data5
This is a trial of the test script.
This is the second trial of the test script.
linux-osud:~/temp # sed 's/test/trial/2' data5
This is a test of the trial script.
This is the second test of the trial script.
linux-osud:~/temp # sed 's/test/trial/g' data5
This is a trial of the trial script.
This is the second trial of the trial script.
linux-osud:~/temp # cat data6
This is a test line.
This is a different line.
linux-osud:~/temp # sed -n 's/test/trial/p' data6
This is a trial line.
linux-osud:~/temp # sed 's/test/trial/w test' data6
This is a trial line.
This is a different line.
linux-osud:~/temp # cat test
This is a trial line.
(3)替换字符
有的时候,我们要替换的文本中含有正斜线(/),而sed默认的字符串分隔符也是正斜线,这样我们就需要使用反斜线()来转义:
linux-osud:~/temp # sed 's//bin/bash//bin/csh/' /etc/password
上面使用/bin/csh来替换/bin/bash,由于使用了很多转义字符,看起来非常不直观。为了解决这个问题,sed编辑器允许选择其他字符来作为替换命令中的字符串分隔符,比如使用感叹号(!):
linux-osud:~/temp # sed 's!/bin/bash!/bin/csh!' /etc/passwd
(4)使用地址
默认情况下,在sed编辑器中使用的命令会作用于文本数据中的所有行。如果想要命令作用于特定某行或某几行,你必须用行寻址(line addressing)。在sed编辑器中有两种形式的行寻址:
两种形式都使用相同的格式来指定地址:[address]command
也可以为特定地址将多个命令放在一起:
address { command1 command2 command3 }
sed编辑器会将指定的每条命令只作用到匹配指定地址的行上。
使用数字方式的行寻址:
linux-osud:~/temp # sed '2s/dog/cat/' data1
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
linux-osud:~/temp # sed '2,3s/dog/cat/' data1
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy dog.
linux-osud:~/temp # sed '2,$s/dog/cat/' data1
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
使用文本模式过滤器:
linux-osud:~/temp # cat data0
This is just a test.
This is just a trial.
linux-osud:~/temp # sed '/trial/s/This/That/' data0
This is just a test.
That is just a trial.
组合命令:
linux-osud:~/temp # sed '2{
> s/fox/elephant/
> s/dog/cat/
> }' data1
The quick brown fox jumps over the lazy dog.
The quick brown elephant jumps over the lazy cat.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
linux-osud:~/temp # sed '3,${
> s/brown/green/
> s/lazy/active/
> }' data1
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick green fox jumps over the active dog.
The quick green fox jumps over the active dog.
当然,行寻址与文本过滤器不光可以用在替换命令里面,在sed的其他命令里面也可以使用。见后面的介绍。
删除命令(delete,d)会删除匹配指定寻址模式的所有行或文本过滤器匹配的所有行。如果什么也没加,就会删除流中所有的文本行。
linux-osud:~/temp # cat data1
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
linux-osud:~/temp # sed 'd' data1
linux-osud:~/temp #
linux-osud:~/temp # cat data7
This is line 1.
This is line 2.
This is line 3.
This is line 4.
linux-osud:~/temp # sed '3d' data7
This is line 1.
This is line 2.
This is line 4.
linux-osud:~/temp # sed '2,3d' data7
This is line 1.
This is line 4.
linux-osud:~/temp # sed '3,$d' data7
This is line 1.
This is line 2.
linux-osud:~/temp # sed '/line 1/d' data7
This is line 2.
This is line 3.
This is line 4.
注意:sed编辑器不会修改原始文件,所以我们删除的只是从sed编辑器的输出中消失了,原始文件中的那些行依旧是存在的。
我们也可以删除用两个文本模式匹配的范围的行,两个模式之间用都好隔开。但这么做要非常小心:你指定的第一个模式会“打开”行删除功能,第二个模式会“关闭”行删除功能。sed编辑器会删除两个指定行之间的所有行,包括指定的行:
linux-osud:~/temp # cat data7 This is line 1. This is line 2. This is line 3. This is line 4. linux-osud:~/temp # sed '/1/,/3/d' data7 This is line 4. # 有问题的例子 linux-osud:~/temp # cat data8 This is line number 1. This is line number 2. This is line number 3. This is line number 4. This is line number 1 again. This is text you want to keep. This is the last line in the file. linux-osud:~/temp # sed '/1/,/3/d' data8 This is line number 4.
可以看到第二个例子中,后面再次匹配到‘/1/’,导致打开了删除功能,但是直到结束也没有匹配到‘/3/’,所以删除模式打开后没有关闭,导致后面的内容全部被删掉了。
插入(insert,i)和追加(append,a)命令允许我们向数据流中插入或者附加文本行:
命令格式为:
sed ‘[address]command new line’
linux-osud:~/temp # echo "Test Line 2" | sed 'iTest Line 1'
Test Line 1
Test Line 2
linux-osud:~/temp # echo "Test Line 2" | sed 'aTest Line 1'
Test Line 2
Test Line 1
linux-osud:~/temp # echo "Test Line 2" | sed 'i
> Test Line 1'
Test Line 1
Test Line 2
linux-osud:~/temp # sed '3i
> This is an inserted line.' data7
This is line 1.
This is line 2.
This is an inserted line.
This is line 3.
This is line 4.
linux-osud:~/temp # sed '3a
This is an inserted line.' data7
This is line 1.
This is line 2.
This is line 3.
This is an inserted line.
This is line 4.
linux-osud:~/temp # sed '$a
This is an inserted line.' data7
This is line 1.
This is line 2.
This is line 3.
This is line 4.
This is an inserted line.
linux-osud:~/temp # sed '1i
> This is one line of new text.
> This is another line of new text.' data7
This is one line of new text.
This is another line of new text.
This is line 1.
This is line 2.
This is line 3.
This is line 4.
修改(change,c)命令允许修改数据流中整行的文本内容。它跟插入和追加命令的工作机制一样:
linux-osud:~/temp # cat data7
This is line 1.
This is line 2.
This is line 3.
This is line 4.
linux-osud:~/temp # sed '3c
> This is a changed line of test' data7
This is line 1.
This is line 2.
This is a changed line of test
This is line 4.
linux-osud:~/temp # sed '/number 1/c
This is a changed line of test' data8
This is a changed line of test
This is line number 2.
This is line number 3.
This is line number 4.
This is a changed line of test
This is text you want to keep.
This is the last line in the file.
linux-osud:~/temp # sed '2,3c
> This is a new line of text.' data7
This is line 1.
This is a new line of text.
This is line 4.
转换命令(transform,y)是唯一可以处理单个字符的sed编辑器命令,命令格式如下:
[address]y/inchars/outchars/
转换命令会进行inchars和outchars值的一对一映射。inchars中的第一个字符会被转换为outchars中的第一个字符,第二个字符会被转换成outchars中的第二个字符。这个映射关系会一直持续到处理完指定字符。如果inchars和outchars的长度不同,则sed编辑器会产生一条错误信息。
linux-osud:~/temp # cat data8
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
This is line number 1 again.
This is text you want to keep.
This is the last line in the file.
linux-osud:~/temp # sed 'y/123/789/' data8
This is line number 7.
This is line number 8.
This is line number 9.
This is line number 4.
This is line number 7 again.
This is text you want to keep.
This is the last line in the file.
linux-osud:~/temp # sed '3y/123/789/' data8
This is line number 1.
This is line number 2.
This is line number 9.
This is line number 4.
This is line number 1 again.
This is text you want to keep.
This is the last line in the file.
linux-osud:~/temp # sed '3,$y/123/789/' data8
This is line number 1.
This is line number 2.
This is line number 9.
This is line number 4.
This is line number 7 again.
This is text you want to keep.
This is the last line in the file.
linux-osud:~/temp # echo "This 1 is a test of 1 try." | sed 'y/123/456/'
This 4 is a test of 4 try.
除了替换命令外,sed编辑器还有一些命令也可以和文件配合使用:
[address]w filename [address]r filename
linux-osud:~/temp # sed '1,2w test' data7
This is line 1.
This is line 2.
This is line 3.
This is line 4.
linux-osud:~/temp # cat test
This is line 1.
This is line 2.
linux-osud:~/temp # cat data11
Blum. Katie Chicago. IL
Mullen. Riley West Lafayette. IN
Snell. Haley Ft. IN
Jim. Green Grant. IL
linux-osud:~/temp # sed -n '/IN/w INcustomers' data11
linux-osud:~/temp # cat INcustomers
Mullen. Riley West Lafayette. IN
Snell. Haley Ft. IN
linux-osud:~/temp # vi data12
linux-osud:~/temp # cat data12
This is an added line.
This is the second added line.
linux-osud:~/temp # sed '3r data12' data7
This is line 1.
This is line 2.
This is line 3.
This is an added line.
This is the second added line.
This is line 4.
linux-osud:~/temp # cat letter
Would you following people:
LIST
please report to the office.
linux-osud:~/temp # sed '/LIST/{
r data11
d
}' letter
Would you following people:
Blum. Katie Chicago. IL
Mullen. Riley West Lafayette. IN
Snell. Haley Ft. IN
Jim. Green Grant. IL
please report to the office.
一般这些命令单独使用没有意义,都是和其他命令配合使用:
linux-osud:~/temp # echo "this is a test" | sed 'p'
this is a test
this is a test
linux-osud:~/temp # cat data7
This is line 1.
This is line 2.
This is line 3.
This is line 4.
linux-osud:~/temp # sed -n '/line 3/p' data7
This is line 3.
linux-osud:~/temp # sed -n '2,3p' data7
This is line 2.
This is line 3.
# 显示原来的行和替换后的行
linux-osud:~/temp # sed -n '/3/{
> p
> s/line/test/p
> }' data7
This is line 3.
This is test 3.
linux-osud:~/temp # sed '=' data1
1
The quick brown fox jumps over the lazy dog.
2
The quick brown fox jumps over the lazy dog.
3
The quick brown fox jumps over the lazy dog.
4
The quick brown fox jumps over the lazy dog.
linux-osud:~/temp # sed -n '/line 4/{
> =
> p
> }' data7
4
This is line 4.
linux-osud:~/temp # cat data9
This line contains tabs.
linux-osud:~/temp # sed -n 'l' data9
Thistlinetcontainsttabs.$ # $代表换行符
至此,sed编辑器的基本用法就介绍完了。当然,sed还有一些高级用法,但平时我们使用的比较少,这里也先不介绍了。
本文总结自《Linux命令行与shell脚本编程大全》第二版。
]]>expr命令我们可以进行一些简单的数学运算或者字符串运算,下面是expr命令支持的操作符(参见man文档):
操作符 | 说明 |
ARG1 | ARG2 | 如果两个参数都非null或者0,返回ARG1;否则返回ARG2。需转义 |
ARG1 & ARG2 | 如果两个参数都非null或者0,返回ARG1;否则返回0。需转义 |
ARG1 < ARG2 | 如果ARG1小于ARG2,返回1;否则返回0。需转义 |
ARG1 <= ARG2 | 如果ARG1小于等于ARG2,返回1;否则返回0。需转义 |
ARG1 = ARG2 | 如果ARG1等于ARG2,返回1;否则返回2 |
ARG1 != ARG2 | 如果ARG1不等于ARG2,返回1;否则返回2 |
ARG1 >= ARG2 | 如果ARG1大于等于ARG2,返回1;否则返回2。需转义 |
ARG1 > ARG2 | 如果ARG1大于ARG2,返回1;否则返回2。只能用于数学运算。需转义 |
ARG1 + ARG2 | 返回ARG1与ARG2的算术运算和。只能用于数学运算 |
ARG1 - ARG2 | 返回ARG1与ARG2的算术运算差。只能用于数学运算 |
ARG1 * ARG2 | 返回ARG1与ARG2的算术运算积。需转义 |
ARG1 / ARG2 | 返回ARG1与ARG2的算术运算商。 |
ARG1 % ARG2 | 返回ARG1与ARG2的算术运算余数。 |
STRING : REGEXP | 如果REGEXP匹配到了STRING中的某个模式,返回该模式匹配 |
match STRING REGEXP | 和STRING : REGEXP相同 |
substr STRING POS LENGTH | 范围起始位置为POS(从1开始计数),长度为LENGTH个字符的子字符串 |
index STRING CHARS | 返回在STRING中找到CHARS字符串的位置(以完整的单词计数);否则返回0 |
length STRING | 返回字符串STRING的数值长度 |
+ TOKEN | 将TOKEN解释成字符串,即使是个关键字(比如match或者'/') |
( EXPRESSION ) | 返回EXPRESSION的值。需转义 |
使用expr命令的注意事项:
表达式非null或者非0,返回0;表达式为null或者0,返回1;表达式有语法错误,返回2;其他错误返回3
可以看到expr进行算术运算时很多运算符都需要转义,使用非常不方便,所以bash shell提供了一个简单的方法:在bash中,在将一个数学运算结果赋给某个变量时,可以使用美元符合方括号将数学表达式圈起来($[ operation ])相比expr,使用方括号有如下好处:
在bash shell中进行浮点数运算最常见的方法就是使用内建的bash计算器bc。默认情况下,bc只支持整数运算,要想支持浮点数运算,需要设置bc内建的scale变量,该变量的值表示小数点后的位数,默认为0.我们在shell脚本中,可以使用管道来进行浮点数运算,基本格式如下:
variable=`echo "options; expression" | bc`
比如下面的例子:
#!/bin/bash
# simple one
var=`echo "scale=4; 3.44 / 5" | bc`
echo "var=$var"
# complex one
var1=10.46
var2=43.67
var3=33.2
var4=71
var5=`bc << EOF
scale = 4
a1 = ( $var1 * $var2 )
b1 = ( $var3 * $var4 )
a1 + b1
EOF
`
echo "var5=$var5"
运行结果:
allan@ubuntu:temp$ sh test.sh
var=.6880
var5=2813.9882
]]>可以使用大于号(>)或者双大于号(>>)将命令的输出重定向,最常见的是重定向到文件中。大于号会在用新的文件数据覆盖旧的已经存在的文件,双大于号则是将新数据追加到已有的文件数据后面。
输入重定向与输出重定向相反,是将文件的内容重定向到命令,输入重定向有两种:
(1)普通的输入重定向,符号是小于号(<),格式为command < inputfile。比如:
allan@ubuntu:temp$ cat test
this is just a test.
the end.
allan@ubuntu:temp$ wc < test
2 7 30
注:wc命令提供了对数据中文本的计数。默认情况下,它会输出3个值:
(2)内联输入重定向(inline input redirection),这种方法允许你在命令行而不是在文件指定输入重定向数据。符号是双小于号(<<),除了这个符号,还必须指定一个文本标记来划分要输入数据的开始与结尾。可以用任何字符串的值来作为文本标记,但在数据的开始和结尾必须一致,格式为:
command << marker
data
marker
比如下面的例子:
allan@ubuntu:temp$ wc << EOF
> test string 1
> test string 2
> test string 3
> EOF
3 9 42
使用管道我们可以将某个命令的输出作为另一个命令的输入,虽然我们可以通过增加中间临时文件来使用重定向实现管道的功能,但显然没有管道方便。管道的格式如下:
command1 | command2 | ...
需要注意的是,
管道链接的命令并不是一个一个运行的,Linux系统实际上会同时运行多个命令,在系统内部将他们拼接起来。在第一个命令产生输出的同时,输出会被立即送给第二个命令。传输数据不会用到任何中间文件或者缓冲区域。 ]]>注:下面说明中提到的环境变量的默认值都是我在我的系统(Ubuntu 14.04 x86_64)上面测的,不一定在所有的Linux发行版上面都一样
变量 |
说明 |
CDPATH | 冒号分隔的目录列表,作为cd命令的搜索路径 |
HOME | 当前用户主目录 |
IFS | shell用来分隔文本字符串的一列字符 |
当前用户收件箱的文件名;shell会检查这个文件来查看有没有新邮件,我的系统上为/var/mail/allan | |
MAILPATH | 冒号分隔的当前用户收件箱的文件名列表;shell检查列表中的每个文件确认是否有新邮件 |
OPTARG | getopts命令处理的最后一个选项参数值 |
OPTIND | getopts命令处理的最后一个选项参数的索引号,默认值为1 |
PATH | 冒号分隔的shell查找命令的目录列表 |
PS1 | shell命令行界面的主提示符,推荐一个不错的提示符:PS1=${debian_chroot:+($debian_chroot)}[33[01;32m]u@h[33[00m]:[33[01;34m]W[33[00m]$ |
PS2 | shell命令行界面的次提示符 |
PS3 | select命令的提示符 |
PS4 | 如果使用了bash的-x参数,在命令行显示之前显示的提示符 |
BASH | 运行当前shell实例的全路径名,我的系统值为/bin/bash |
BASH_ALIASES | 当前已设置别名的关联数组 |
BASH_ARGC | 含有传给子函数或shell脚本的参数总数的可变数组 |
BASH_ARGV | 含有传给子函数或shell脚本的参数的可变数组 |
BASH_CMDS | shell执行过的命令的所在位置的关联数组 |
BASH_COMMAND | shell正在执行的命令或者马上就执行的命令 |
BASH_ENV | 设置了的话,每个bash脚本会在运行前先尝试运行一下这个变量定义的启动文件 |
BASH_EXECUTION_STRING | 通过bash –c选项传递过来的命令 |
BASH_LINENO | 含有当前执行的shell函数的在源代码中行号的可变数组 |
BASH_REMATCH | 含有模式和它们通过正则表达式比较运算符=~匹配到的子模式的只读可变数组 |
BASH_SOURCE | 含有当前正在执行的shell函数的源码文件名的可变数组 |
BASH_SUBSHELL | 当前shell环境的嵌套级别,初始值是0 |
BASH_VERSION | 当前运行的bash shell的版本号 |
BASH_VERSINFO | 含有当前运行的bash shell的主版本号和次版本号的可变数组 |
BASH_XTRACEFD | 若设置成了有效的文件描述符(0,1,2),则‘set -x’调试选项声明的跟踪输出可被重定向;通常用来跟踪输出分出到一个文件中 |
BASHOPTS | 当前使能的bash shell选项列表 |
BASHPID | 当前bash进程的pid |
COLUMNS | 当前bash shell实例使用的终端的宽度 |
COMP_CWORD | 当前含光标位置的COMP_WORDS变量的索引值 |
COMP_LINE | 当前命令行 |
COMP_POINT | 当前光标位置相对于当前命令起始位置的索引 |
COMP_KEY | 用来调用shell函数补全功能的最后一个键值 |
COMP_TYPE | 代表尝试调用补全shell函数的补全类型的整数值 |
COMP_WORDBREAKS | Readline库里做单词补全的词分隔字符 |
COMP_WORDS | 含有当前命令行所有词的可变数组 |
COMPREPLY | 含有由shell函数生成的可能的填充字的可变数组 |
DIRSTACK | 含有目录栈当前内容的可变数组 |
EMACS | 设置为't'是,表明emacs shell缓冲区正在工作而行编辑不能工作 |
EUID | 当前用户的有效用户ID |
FCEDIT | 供fc命令用的默认编辑器 |
FIGNORE | 冒号分隔的做文件名补全时要忽略的后缀名列表 |
FUNCNAME | 当前执行的shell函数的名称 |
GLOBIGNORE | 定义了文件名展开时忽略的文件名集合的冒号分隔的模式列表 |
GROUPS | 含有当前用户属组列表的可变数组 |
histchars | 控制历史记录展开的字符,最多可有三个字符 |
HISTCMD | 当前命令在历史记录中的位置 |
HISTCONTROL | 控制哪些命令留在历史记录列表中 |
HISTFILE | 保存shell历史记录列表的文件名,默认是.bash_history |
HISTFILESIZE | 最多在历史文件中存在多少行 |
HISTIGNORE | 冒号分隔的用来决定哪些命令不存仅历史文件的模式列表 |
HISTSIZE | 最多在历史文件中存多少条命令 |
HOSTFILE | shell在补全主机名时读取的文件 |
HOSTNAME | 当前主机的名称 |
HOSTTYPE | 当前运行bash shell的机器 |
IGNOREEOF | shell在退出前必须受到连续的EOF字符的数量。如果这个值不存在,默认是1 |
INPUTRC | readline初始化文件名,默认是.inputrc |
LANG | shell的语言环境分类 |
LC_ALL | 定义一个语言环境,覆盖LANG变量 |
LC_COLLATE | 设置对字符串排序时用的对照表顺序 |
LC_CTYPE | 决定着在文件名展开和模式匹配时用字符如何解释 |
LC_MESSAGES | 决定解释前置美元符的双引号字符串的语言环境设置 |
LC_NUMERIC | 决定着格式化数字时的语言环境设置 |
LINENO | 当前执行的脚步的行号 |
LINES | 定义了终端上可见的行数 |
MACHYPE | 用“cpu-公司-系统”格式定义的系统类型 |
MAILCHECK | shell查看邮件的频率,以秒为单位,默认 |
OLDPWD | shell之前的工作目录 |
OPTERR | 设置为1时,bash shell会显示getopts命令产生的错误 |
OSTYPE | 定义了shell运行的操作系统 |
PIPESTATUS | 含有前端进程的退出状态列表的可变数组 |
POSIXLY_CORRECT | 设置了的话,bash 会以POSIX模式启动 |
PPID | bash shell父进程的pid |
PROMPT_COMMAND | 设置了的话,在命令行主提示符显示之后会执行这条命令 |
PROMPT_DIRTRIM | 用来定义当启动了w或W提示符字符串转义时显示的尾部目录名数。删除的目录名会用一组英文句点替换 |
PWD | 当前工作目录 |
RANDOM | 返回一个0~32767的随机数;对其赋值可作为随机数生成器的种子 |
REPLY | read命令的默认变量 |
SECONDS | 自从shell启动到现在的秒数;对其赋值将会重新计数 |
SHELL | bash shell的全路径名 |
SHELLOPTS | 冒号分隔的打开的bash shell选项列表 |
SHLVL | shell的级别;每次启动一个新的bash shell,值增加1 |
TIMEFORMAT | 指定了shell显示时间值的格式 |
TMOUT | select和read命令在没有输入的情况下等待多久(以秒为单位)。默认值是0,表示无线长 |
TMPDIR | bash shell创建临时文件的目录名 |
UID | 当前用户的真实用户ID |
可以看到,bash shell有许多的内置环境变量,而且不是所有的默认环境变量都会在运行set命令时列出。我们只要记住一些常用的比较重要的就可以了。
当我们登录Linux系统启动一个bash shell时,默认情况下bash在几个文件中查找命令,这些文件称作启动文件。bash 检查的启动文件取决于启动bash shell的方式。而启动bash shell有三种方式:
(1)登录时当做默认登录shell。当我们登录Linux系统时,bash shell会作为登录shell启动。登录shell会从四个不同的启动文件中读取命令。下面是bash shell处理这些文件的次序:
(2)作为非登录shell的交互式shell。比如我们敲入bash命令启动一个shell,这样启动的交互式shell不会去访问/etc/profile文件,而会去用户的HOME目录下检查.bashrc文件是否存在。
(3)非交互式shell。比如系统执行脚本时用的就是这种shell。当shell启动一个非交互式的shell进程时,它会检查BASH_ENV这个环境变量,如果这个环境变量有设置,shell会执行文件里面的命令。
之前介绍的很多环境变量都使用了可变数组,从名字可以看出就是一个长度可变的数组。要把某个环境变量设置成可变数组(即设置多个值),可以把值房子括号里,值与值之间用空格分隔:
allan@ubuntu:~$ mytest=(one two three four five)
不能通过echo数组名来查看整个数组(那样只会显示第一个元素),而必须要用代表它在数组中位置的数值索引值来访问(索引值从0开始),数值用方括号括起来;可用通配符显示整个数组:
allan@ubuntu:~$ echo ${mytest[2]}
three
allan@ubuntu:~$ echo ${mytest[*]}
one two three four five
可以用unset来删除数组中的某个值或整个数组:
allan@ubuntu:~$ unset mytest[2]
allan@ubuntu:~$ echo ${mytest[*]}
one two four five
allan@ubuntu:~$ echo ${mytest[2]}
allan@ubuntu:~$ echo ${mytest[3]}
four
allan@ubuntu:~$ mytest[2]=seven
allan@ubuntu:~$ echo ${mytest[*]}
one two seven four five
]]>allan@ubuntu:~$ var1="Linux Shell" allan@ubuntu:~$ echo "$var1" Linux Shell # var1变量被解析 allan@ubuntu:~$ echo '$var1' $var1 # var1变量没有被解析
当然,这是Shell中单引号与双引号的常规用法,大家都知道。但是今天我却发现一个小技巧,分享一下。
看例子:
allan@ubuntu:~$ echo "I love '$var1'" I love 'Linux Shell'
可见,双引号内嵌单引号时,单引号内的变量不仅会正常解析,而且会保留单引号。我个人理解是双引号内的单引号已经退化为普通的符号,并不再具有Shell中单引号的特殊含义。这个在实际中是有非常大的用途的。
考虑这样一种场景:我们在Shell脚本中需要将log_path = '/var/log/xxx' 这行配置(配置实用单引号括起来的)写到某个配置文件(假设为example.conf),而且xxx 是一个变量,需要动态获取。那利用这种嵌套就非常好写了:
test.sh内容:
#!/bin/bash xxx=process/log_dir # 这里直接赋值,实际中可能需要动态获取 echo "log_path= '/var/log/$xxx'" >> example.conf
test.sh执行结果:
allan@ubuntu:temp$ sh test.sh allan@ubuntu:temp$ ls example.conf test.sh allan@ubuntu:temp$ cat example.conf log_path= '/var/log/process/log_dir'
在配置文件中,xxx变量既被解析,也保留了单引号。这种场景在实际中也挺常见的。
当然,单引号内嵌双引号时,变量并不会被解析,所有的东西都是原样输出。并没有特殊之处。
]]>#!/bin/bash param="cat test.txt | grep allan"; if [ $param = "allan" ]; then echo "allan exists in test.txt" fi
这一个脚本执行的时候,如果param的值为空就会出现“test.sh: 5: [: cat: unexpected operator”,因为当param为空时,第五行就变为“if [ = "allan" ]”,这显然是不对的。
这种情况在我们写shell脚本时出现的几率很大,但这个很容易造成隐藏的bug。当变量不为空的时候程序可以正常运行,但是当变量为空的时候,就会出现错误。解决的方法也很简单,就是在表达式中,将变量都用双引号括起来,这样即使变量为空的时候,程序也可以正常运行,不会出现语法错误。比如改成下面这个样子:
#!/bin/bash param="cat test.txt | grep allan"; if [ "$param" = "allan" ]; then echo "allan exists in test.txt" fi
所以,推荐大家在写Shell脚本的时候,尽量多用双引号将变量引起来。虽然大多数情况下,加不加双引号都是一样的,但是在一些情况下,双引号可以帮助我们避免很多bug。比如下面也是一种情况:
#!/bin/bash param="123" echo "1st line: $param456" echo "2nd line: $param 456" echo "3rd line: $param"456 echo "4th line: ${param}456"
程序输出为:
1st line: 2nd line: 123 456 3rd line: 123456 4th line: 123456
可以看到,
第一行输出为空,因为系统认为我们要打印的变量是$param456,但这个变量不存在,也就是值为空。
第二行,我们在$param后面加了一个空格,打印正常。
第三行,我们用双引号将变量引起来,也可正常打印。
第四行,我们用大括号将变量括起来,也可正常打印。
结论:在Shell脚本中,表达式中的变量尽量用双引号/大括号引起来。
]]>命令格式:
我们以第一条命令做说明,可以看到,删除命令共分为四部分:
下面我们用几个例子来看这个命令的具体使用:
首先我们将环境变量PATH赋给临时变量path,然后用path来进行练习:
注:下面的"allan@ubuntu:~$"均为终端提示符。
allan@ubuntu:~$ echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games allan@ubuntu:~$ path=$PATH allan@ubuntu:~$ echo $path /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
Exp 1:假如我们要删除“/usr/local/sbin:”这个路径,可以如下操作:
allan@ubuntu:~$ echo ${path#/*sbin:} /usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
这里我们使用了通配符*,代表0个或任意多个任意字符。上面那句命令的意思就是从变量最前面开始往右匹配最短的“/*sbin:”这个字符串, 很明显,就是“/usr/local/sbin:”这个字符串匹配成功。所以被删除。
Exp 2:现在我们将Exp1中的一个#换成两个#,看一下结果:
allan@ubuntu:~$ echo ${path##/*sbin:} /bin:/usr/games:/usr/local/games
可以看到两个#的时候,删除的部分是“/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:”,也即从字符串前面往右匹配“/*sbin:”最长的字符串。这便是#和##的区别,一个是匹配最短的,一个是匹配最长的。
%、%%与#、##对应含义类似,不过是从变量最后面开始往左匹配,%代表最短匹配,%%代表最长匹配。看下面这个例子:
Exp 3:使用%和%%的例子
allan@ubuntu:~$ echo ${path%:/usr*} /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games allan@ubuntu:~$ echo ${path%%:/usr*} /usr/local/sbin
从上面的三个例子可以看出这个命令还是蛮好用的,现在大家可能会疑问这个命令用在哪里呢?其实这个命令的应用范围应该蛮广的,不过在目录分离方面用的最多,下面就介绍一个应用特别多的场景:
比如我的邮件目录是/var/spool/mail/allan,现在我想得到最后的那个用户名allan,可以如下操作:
allan@ubuntu:~$ echo $MAIL /var/spool/mail/allan allan@ubuntu:~$ echo ${MAIL##*/} allan
相反,现在我们不想要用户名,只想得到邮件目录/var/spool/mail,可以如下操作:
allan@ubuntu:~$ echo $MAIL /var/spool/mail/allan allan@ubuntu:~$ echo ${MAIL%/*} /var/spool/mail
这两个操作在平时我们写shell脚本的时候还是经常会用到的。关于字符串的删除就介绍这些。
]]>Shell这个东西比较抽象,我们可以理解为是操作系统留给我们和内核交互的一种方法或者途径。我们都知道Linux内核是非常重要的,我们往往是不能直接操作内核的,但是我们的计算机要工作,就要依赖于操作系统,而操作系统要工作就要依赖于内核,Shell就是Linux操作系统提供给我们和和内核交互的方法,我认为有点类似于编程语言中的API的概念。
其实我们平时使用的Terminal就是一个Shell,它可以解析我们输入的命令。而Shell脚本就是将我们输入的命令放在一个文件里面,简单理解就是这个样子的。需要说明的是,Shell脚本语言不同于Python、JS等脚本语言,可以运行在很多操作系统上面,Shell脚本语言只存在于Unix-like操作系统中。
Shell也有很多种,我们可以查看/etc/shells这个文件,里面有我们使用的操作系统支持的shell。主要有以下几个(你的shells文件里面一般只有下面中的一部分,因为下面列的比较全):
虽然各种Shell的功能大同小异,但是在语法方面还是会有一些不同。我们每个用户默认使用的shell类型是在/etc/passwd中的最后一个字段规定的。目前Linux默认使用的shell是/bin/bash,我们后续shell脚本学习使用的都是这个shell。
Bash是GNU一个重要的工具包,全称“ Bourne Again SHell”。他的强大之处就不赘述了,我们的重点是shell script,有兴趣的可以看man文档(man bash)超级多多,超级详细。熟练掌握Shell对于我们使用Unix-like操作系统至关重要。
本系列文章参考自《鸟哥的Linux私房菜——基础篇》等书籍以及网上的一些知识。
]]>