NYC's Blog - Shell 2015-05-07T20:25:00+08:00 Typecho http://niyanchun.com/feed/atom/tag/shell/ <![CDATA[Linux正则表达式]]> http://niyanchun.com/regular-expression.html 2015-05-07T20:25:00+08:00 2015-05-07T20:25:00+08:00 NYC https://niyanchun.com 在Linux中,有两种流行的正则表达式(Regular Expressions)引擎:

  • POSIX基本正则表达式(BRE)引擎
  • POSIX扩展正则表达式(ERE)引擎

大多数Linux工具都支持BRE,但是只有一些工具支持ERE。

1. BRE模式

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的数字与字符

2. ERE模式

RE字符 含义及范例
? 重复前面的字符0次到1次
+ 重复前面的字符1次或多次
{m}、{m, n}(区间) RE字符准确出现m次;RE字符至少出现m次,至多出现n次。gawk中要使用区间,必须指定--re-interval
exp1|exp2 逻辑OR(正则表达式和管道符号之间不能有空格,否则它们也会加到正则表达式模式中)
]]>
<![CDATA[gawk程序用法大全]]> http://niyanchun.com/gawk-usage.html 2015-05-06T21:11:00+08:00 2015-05-06T21:11:00+08:00 NYC https://niyanchun.com 1. gawk概述

在《sed编辑器基本用法》一文中我们已经介绍了Linux世界中使用最广泛的两个命令行编辑器之一——sed,今天我们来介绍另外一个:gawk。gawk是Unix中原始awk程序的GNU版本,比sed功能更加强大,因为它提供了一个类编程环境,允许我们修改和重新组织文件中的数据。gawk的特点如下:

  • 定义变量来保存数据;
  • 使用算术和字符运算符来处理数据;
  • 使用结构化编程的概念,比如if-else语句和循环,来为数据处理增加逻辑;
  • 提取数据文件中的数据元素并将它们按另一顺序或格式重新放置,从而生成格式化的报告。

2. gawk的命令格式及选项

gawk程序的基本格式如下:gawk options program file

其支持的选项如下表:

选项 描述
-F fs 指定分隔行中数据字段的文件分隔符
-f file 指定读取程序的文件名
-v var=value 定义gawk程序中的一个变量及其默认值
-mf N 指定要处理的数据文件中的最大字段数
-mr N 指定数据文件中的最大数据行数
-W keyword 指定gawk的兼容模式或者警告等级。用help选项来列出所有可用的关键字。

3. 使用gawk

我们可以在命令行或者shell脚本中使用gawk。

3.1 从命令行上读取程序脚本

gawk程序脚本是由一对花括号定义的,你必须将脚本命令放在两个括号之间。由于gawk行假定脚本是单个文本字符串,所以还必须用单引号将脚本命令圈起来:

linux-osud:~/temp # echo "just a test" | gawk '{print $1}'
just

这个命令会显示输入流中每行的第一个数据字段。

3.2 在程序脚本中使用多个命令

gawk允许我们将多条命令合成一个普通程序,只要使用分号将多个命令分隔起来就行:

linux-osud:~/temp # echo "My name is Allan ." | gawk '{$4="Alne"; print $0}'
My name is Alne .

3.3 从文件中读取程序

跟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
...

3.4 在处理数据前运行脚本

gawk允许我们指定程序脚本命令何时允许。默认情况下,gawk从输入读入一行文本,然后执行针对文本行中的数据执行脚本。有时,我们可能需要在处理数据之前(比如创建报告的标题)允许一些命令。为了做到这点,我们可以使用BEGIN关键字。它会强制先执行BEGIN关键字后面指定的程序脚本,然后再读取数据:

linux-osud:~/temp # gawk 'BEGIN {print "This is a test report"}'
This is a test report

我们可以在BEGIN块中放置任何类型的gawk命令,比如给变量赋默认值等。

3.5 在处理数据后运行脚本

和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块中的代码。

4. gawk变量

注意:跟shell变量不同,引用gawk变量时,变量名前不加美元符。

4.1 内建变量

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的值则会继续计数,直到处理完整个文件。

4.2 自定义变量

4.2.1 在脚本中给变量赋值

在gawk中给变量赋值类似于在shell脚本中给变量赋值——使用赋值语句:

linux-osud:~/temp # gawk  '
> BEGIN {
> testing="This is a test"
> print testing
> }'
This is a test

给变量赋值后,就可以在gawk脚本中任何地方使用该变量了(未赋值的变量默认为空字符串)。

4.2.2  在命令行上给变量赋值

我们也可以用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

5. gawk数组

gawk的数组为关联数组。关联数组与数字数组不同之处在于它的索引值可以是任意文本字符串,每个索引字符串都必须是唯一的,并且唯一的标识赋给它的数据元素。这有点类似于其他编程语言的哈希表的概念。

  • 定义数组变量:var[index] = element
  • 遍历数组变量:for (var in array) { statements }
  • 删除数组变量:delete array[index]

看下面的例子:

# 定义数组变量
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

6. 使用模式

gawk支持几种类型的匹配模式来过滤数据行,与sed编辑器大同小异。BEGIN和END关键字就是两个特殊的模式,下面再介绍几种模式。

6.1 正则表达式

我们可以使用基本正则表达式(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两种表达式会在后面的博客中介绍。

6.2 匹配操作符

匹配操作符(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

6.3 数学表达式

除了正则表达式,我们还可以在模式匹配中使用数学表达式:

linux-osud:~/temp # gawk -F: '$4 == 0 {print $1}' /etc/passwd
root

比如我们可以用上面的语句找出所有属于root用户组的用户。

我们可以使用任意的普通数学比较表达式:

  • x == y
  • x <= y
  • x < y
  • x >= y
  • x > y

注意:也可以对文本数据使用表达式,但必须小心:

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值进行比较的。

7. 结构化命令

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

8. 格式化打印printf

虽然我们可以使用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 显示十六进制值,但是用大写字母

除了控制字母,还有三个修饰符:

  • width:指定输出字段最小宽度的数字值,如果输出短于这个值,则会右对齐,并使用空格填充。如果输出长度超过这个值,则覆盖width
  • prec:指定浮点数中小数点后面位数的个数,或者文本字符串中显示的最大字符数
  • -:采用左对齐而不是右对齐

9. 内建函数

9.1 数学函数

函数 说明
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) 异或

9.2 字符串函数

函数 说明
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

9.3 时间函数

函数 说明
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

10. 自定义函数

自定义函数使用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
]]>
<![CDATA[sed编辑器基本用法]]> http://niyanchun.com/sed-editor.html 2015-04-29T19:34:00+08:00 2015-04-29T19:34:00+08:00 NYC https://niyanchun.com 1. sed流编辑器原理概述

一般我们更习惯说sed为linux命令,但更准确的说法应该是sed流编辑器。和普通的交互式文本编辑器不同,流编辑器是在编辑器处理数据之前基于预先提供的一组规则来编辑数据流;而交互式文本编辑器(比如vim)则是通过键盘命令来交互式的插入、删除或者替换数据中的文本。sed编辑器可以基于输入到命令行的或是存储在命令文本文件中的命令来处理数据流中的数据。它每次从输入读入一行,用提供的编辑器命令匹配数据、按照命令中指定的方式修改流中的数据,然后将生成的数据输出到STDOUT。在流编辑器将所有的命令与一行数据进行匹配后,它会读取下一行数据并重复这个过程。在流编辑器处理完流中所有的数据行后,它就会终止。

2. sed的选项和命令

sed命令的格式为:sed options script file .

2.1 sed的选项

选项参数(options)允许你修改sed命令的行为,sed命令常用的选项如下:

选项

描述

-e script 在处理输入时,将script中指定的命令添加到运行的命令中;如果需要多个命令,也可用-e选项
-f file 在处理输入时,将file中指定的命令添加到运行的命令中
-n 不要为每个命令生成输出,等待print命令来输出

2.2 sed的命令

2.2.1 替换命令

替换(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.

注意:

  • 使用多个命令时,命令之间必须用分号分隔,并且在命令末尾和分号之间不能有空格。
  • 也可以使用bash shell的次提示符来分隔命令,而不用分号。只要输入第一个单引号来开始编写,bash会继续提示你输入更多的命令,知道你输入了封尾的单引号。必须记住,要在封尾单引号所在行结束命令。

如果有大量要处理的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 .

有四种可用的替换标记:

  • 数字,表明新文本将替换第几处模式匹配的地方;
  • g,表明新文本将会替换所有匹配的地方;
  • p,表明原来行的内容要打印出来,一般和-n配合使用;-n选项禁止sed编辑器输出,但p替换标记会输出修改过的行,二者配合使用将只会输出替换命令修改过的行;
  • w file,将替换的结果(指包含匹配模式的行)写出到文件。
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编辑器中有两种形式的行寻址:

  • 行的数字范围;
  • 用文本模式来过滤出某行,格式为/pattern/command 。

两种形式都使用相同的格式来指定地址:[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的其他命令里面也可以使用见后面的介绍。

2.2.2 删除命令

删除命令(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/’,所以删除模式打开后没有关闭,导致后面的内容全部被删掉了。

2.2.3 插入和追加命令

插入(insert,i)和追加(append,a)命令允许我们向数据流中插入或者附加文本行:

  • 插入命令i会在指定行前增加一个或多个新行;
  • 追加命令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.

2.2.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.

2.2.5 转换命令

转换命令(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.

2.2.6 读写文件命令

除了替换命令外,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.

2.2.7 打印命令

  • 小写p命令用来打印文本行;
  • 等号(=)命令用来打印行号;
  • l(小写L)命令用来列出行,与p不同的是,l可以打印数据流中不可打印的ASCII字符。任何不可打印字符都用它们的八进制值前加一个反斜线或标准C风格的命名法,比如t用来代表制表符。

一般这些命令单独使用没有意义,都是和其他命令配合使用:

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脚本编程大全》第二版。

]]>
<![CDATA[Shell中的数学计算]]> http://niyanchun.com/calculation-in-shell.html 2015-04-19T14:31:00+08:00 2015-04-19T14:31:00+08:00 NYC https://niyanchun.com shell对于数值计算支持的不是特别好,而且bash shell的数学操作符只支持整数运算(z shell提供了完整的浮点数运算),如果想要进行浮点数运算,需要采用一些其他方法,下面我们总结一下shell中如何进行数学计算。

1. expr命令

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命令的注意事项:

  • expr的许多运算符都需要转义()或者用引号括起来,否则就会出错。需要转义的运算符已经在上面的表格中用红字标识出来
  • 涉及到比较的运算符使用如下规则进行比较:如果都是数字,则使用算术比较;如果有非数字,则使用字典序比较(ASCII序)
  • 变量与运算符之间必须有至少一个空格
    expr返回值:

表达式非null或者非0,返回0;表达式为null或者0,返回1;表达式有语法错误,返回2;其他错误返回3

2. 使用方括号

可以看到expr进行算术运算时很多运算符都需要转义,使用非常不方便,所以bash shell提供了一个简单的方法:在bash中,在将一个数学运算结果赋给某个变量时,可以使用美元符合方括号将数学表达式圈起来($[ operation ])相比expr,使用方括号有如下好处:

  • 在方括号内的所 有运算符不需要转义,shell会将他们解释为运算符,而非shell里面具有特殊含义的符号。
  • 方括号里面的操作符和运算符之间无需加空格,比如echo $[ 5 * 2 ]</span> 和<span class="lang:default decode:true crayon-inline ">echo $[5*2] 均可。
    注:shell中有一个test命令可以用来测试一些条件,而test也可以用方括号替代,但是那里的方括号与此处用于计算的方括号含义不同(要求也不同,表示test的方括号需要加空格),注意区分。

3. 浮点数运算

在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
]]>
<![CDATA[Shell中的重定向与管道]]> http://niyanchun.com/redirection-and-pipe-in-shell.html 2015-04-18T22:13:00+08:00 2015-04-18T22:13:00+08:00 NYC https://niyanchun.com 1. 重定向

1.1 输出重定向

可以使用大于号(>)或者双大于号(>>)将命令的输出重定向,最常见的是重定向到文件中。大于号会在用新的文件数据覆盖旧的已经存在的文件,双大于号则是将新数据追加到已有的文件数据后面。

1.2 输入重定向

输入重定向与输出重定向相反,是将文件的内容重定向到命令,输入重定向有两种:

(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

2. 管道

使用管道我们可以将某个命令的输出作为另一个命令的输入,虽然我们可以通过增加中间临时文件来使用重定向实现管道的功能,但显然没有管道方便。管道的格式如下:

command1 | command2 | ...

需要注意的是,

管道链接的命令并不是一个一个运行的,Linux系统实际上会同时运行多个命令,在系统内部将他们拼接起来。在第一个命令产生输出的同时,输出会被立即送给第二个命令。传输数据不会用到任何中间文件或者缓冲区域。

]]>
<![CDATA[Shell中的环境变量]]> http://niyanchun.com/environment-variables-in-shell.html 2015-04-18T21:38:00+08:00 2015-04-18T21:38:00+08:00 NYC https://niyanchun.com 1. Shell环境变量介绍
  • shell的环境变量分为全局变量和局部变量。
  • 全局变量对所有的shell会话及其子进程都有效,局部变量只在定义它们的进程中可见。
  • 可以使用printenv查看全局变量,使用set查看全局变量和局部变量。
  • 使用export可以将局部变量导出为全局变量。
  • 使用unset可以删除一个已定义的环境变量。
  • shell有许多默认的环境变量,但不是每一个变量都必须有一个值

2. 默认的shell环境变量

注:下面说明中提到的环境变量的默认值都是我在我的系统(Ubuntu 14.04 x86_64)上面测的,不一定在所有的Linux发行版上面都一样

变量

说明

CDPATH 冒号分隔的目录列表,作为cd命令的搜索路径
HOME 当前用户主目录
IFS shell用来分隔文本字符串的一列字符
MAIL 当前用户收件箱的文件名;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命令时列出。我们只要记住一些常用的比较重要的就可以了。

3. 启动bash shell的三种方式

当我们登录Linux系统启动一个bash shell时,默认情况下bash在几个文件中查找命令,这些文件称作启动文件。bash 检查的启动文件取决于启动bash shell的方式。而启动bash shell有三种方式:

(1)登录时当做默认登录shell。当我们登录Linux系统时,bash shell会作为登录shell启动。登录shell会从四个不同的启动文件中读取命令。下面是bash shell处理这些文件的次序:

  • /etc/profile
  • $HOME/.bash_profile
  • $HOME/.bash_login
  • $HOME/.profile

(2)作为非登录shell的交互式shell。比如我们敲入bash命令启动一个shell,这样启动的交互式shell不会去访问/etc/profile文件,而会去用户的HOME目录下检查.bashrc文件是否存在。

(3)非交互式shell。比如系统执行脚本时用的就是这种shell。当shell启动一个非交互式的shell进程时,它会检查BASH_ENV这个环境变量,如果这个环境变量有设置,shell会执行文件里面的命令。

4. 可变数组

之前介绍的很多环境变量都使用了可变数组,从名字可以看出就是一个长度可变的数组。要把某个环境变量设置成可变数组(即设置多个值),可以把值房子括号里,值与值之间用空格分隔:

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
]]>
<![CDATA[Shell中的单引号与双引号]]> http://niyanchun.com/quotes-in-shell.html 2014-11-06T23:04:00+08:00 2014-11-06T23:04:00+08:00 NYC https://niyanchun.com Linux下的Shell中,单引号和双引号常用来括住包含有空格的变量值,比如var1="Linux Shell" 。而两者的区别是:单引号中的变量不会被解析,而双引号中的变量会被解析。看下面的例子:

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变量既被解析,也保留了单引号。这种场景在实际中也挺常见的。

当然,单引号内嵌双引号时,变量并不会被解析,所有的东西都是原样输出。并没有特殊之处。

]]>
<![CDATA[shell脚本里面的“xxx: unexpected operator”错误及解决方案]]> http://niyanchun.com/unexpected-operator-in-shell.html 2014-08-20T22:29:00+08:00 2014-08-20T22:29:00+08:00 NYC https://niyanchun.com 在Shell脚步执行的时候我们经常会遇到“xxx: unexpected operator”这个错误,其实这个错误一般都是因为我们的表达式有问题,这里我们举一个出错率非常高的例子:

#!/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脚本中,表达式中的变量尽量用双引号/大括号引起来。

]]>
<![CDATA[Shell脚本学习——字符串的删除]]> http://niyanchun.com/delete-string-in-shell.html 2014-08-11T23:15:00+08:00 2014-08-11T23:15:00+08:00 NYC https://niyanchun.com 利用正则表达式,我们可以对字符串进行一些高级操作,今天我们就介绍一个在shell脚本中听常用的一种处理字符串的方法——字符串的删除。

命令格式:

  • ${variable#delete_part}
  • ${variable##delete_part}
  • ${variable%delete_part}
  • ${variable%%delete_part}

我们以第一条命令做说明,可以看到,删除命令共分为四部分:

  1. "$":是这种删除模式的关键字,必不可少。
  2. "variable":是我们要操作的变量。需要注意的是,这种删除并不会改变变量本身的值。
  3. "#": 这个是删除的模式,一个#代表“从变量内容的最前面开始向右删除,且仅删除最短的那个”;两个#(##),代表“从变量内容的最前面开始向右删除,且删除最长的那个”。一个%代表“从变量内容的最后面开始向左删除,且仅删除最短的那个”;两个%(%%)代表:“从变量内容的最后面开始向右删除,且删除最长的那个”。具体含义见后面的例子。
  4. "delete_part":代表要删除的部分,这部分会在要操作的变量中进行匹配,匹配成功的就会被删除。这里可以使用正则表达式。

下面我们用几个例子来看这个命令的具体使用:

首先我们将环境变量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脚本的时候还是经常会用到的。关于字符串的删除就介绍这些。

]]>
<![CDATA[Shell脚本学习——Shell简介]]> http://niyanchun.com/shell-introduction.html 2014-08-02T20:12:00+08:00 2014-08-02T20:12:00+08:00 NYC https://niyanchun.com 最近更换工作后,第一件事情就是做一些Shell脚本编程,不过已经好久没用过了,而且当时也没有学的很扎实。现在就重新梳理一遍,整理一下知识点,供自己复习,也希望可以给别人带来帮助。

Shell简介

Shell这个东西比较抽象,我们可以理解为是操作系统留给我们和内核交互的一种方法或者途径。我们都知道Linux内核是非常重要的,我们往往是不能直接操作内核的,但是我们的计算机要工作,就要依赖于操作系统,而操作系统要工作就要依赖于内核,Shell就是Linux操作系统提供给我们和和内核交互的方法,我认为有点类似于编程语言中的API的概念。

其实我们平时使用的Terminal就是一个Shell,它可以解析我们输入的命令。而Shell脚本就是将我们输入的命令放在一个文件里面,简单理解就是这个样子的。需要说明的是,Shell脚本语言不同于Python、JS等脚本语言,可以运行在很多操作系统上面,Shell脚本语言只存在于Unix-like操作系统中。

Shell也有很多种,我们可以查看/etc/shells这个文件,里面有我们使用的操作系统支持的shell。主要有以下几个(你的shells文件里面一般只有下面中的一部分,因为下面列的比较全):

  • /bin/sh (已经被 /bin/bash 所取代)
  • /bin/bash (就是 Linux 默认的 shell)
  • /bin/ksh (Kornshell 由 AT&T Bell lab. 发展出来的,兼容于 bash)
  • /bin/tcsh (整合 C Shell ,提供更多的功能)
  • /bin/csh (已经被 /bin/tcsh 所取代)
  • /bin/zsh (基于 ksh 发展出来的,功能更强大的 shell)

虽然各种Shell的功能大同小异,但是在语法方面还是会有一些不同。我们每个用户默认使用的shell类型是在/etc/passwd中的最后一个字段规定的。目前Linux默认使用的shell是/bin/bash,我们后续shell脚本学习使用的都是这个shell。

Bash是GNU一个重要的工具包,全称“ Bourne Again SHell”。他的强大之处就不赘述了,我们的重点是shell script,有兴趣的可以看man文档(man bash)超级多多,超级详细。熟练掌握Shell对于我们使用Unix-like操作系统至关重要。

本系列文章参考自《鸟哥的Linux私房菜——基础篇》等书籍以及网上的一些知识。

]]>