d基本⽤法
朋友用英语怎么说d基本⽤法
d⾯试题:
1、d介绍
d是⼀个流式编辑器程序,它读取输⼊流(可以是⽂件、标准输⼊)的每⼀⾏放进模式空间(pattern space),同时将此⾏⾏号通过d ⾏号计数器记录在内存中,然后对模式空间中的⾏进⾏模式匹配,如果能匹配上则使⽤d程序内部的命令进⾏处理,处理结束后,从模式空间中输出(默认)出去,并清空模式空间,随后再从输⼊流中读取下⼀⾏到模式空间中进⾏相同的操作,直到输⼊流中的所有⾏都处理完成。由此可见,d是⼀个循环⼀个循环处理内容的。
这是d的⼀个循环的过程:
英语面试介绍
1. 读取输⼊流的⼀⾏到模式空间。
squash2. 对模式空间中的内容进⾏匹配和处理。
3. ⾃动输出模式空间内容。
4. 清空模式空间内容。
5. 读取输⼊流的下⼀⾏到模式空间。
(注:(如看不懂,请跳过)如果是读取⽂件数据,则会每次需要的时候⼀次性加载⼀定量(⽐如多⾏)的数据到os buffer,然后d从os buffer中⼀⾏⼀⾏读取,并不是要读⼀⾏就从磁盘⽂件中加载⼀⾏。另外,如果是管道或其它输⼊流,则直接从对应的缓存中⼀⾏⼀⾏读取。验证命令:d ‘p;s/.*/:>filename/e;d’ filename)
上述整个循环过程中,第2步是我们写d命令所修改的地⽅,其余的⼏个步骤,通过命令⾏⽆法改变。但是,d有⼏个命令和选项能改变第3、4步的⾏为,使其输出总是输出空内容或⽆法清空模式空间。
george如果使⽤编程结构来描述,则⼤致过程如下:
for ((line=1;line<=last_line_num;++line))
do
read $line to pattern_space;
while pattern_space is not null
do
execute cmd1 in SCRIPT;
execute cmd2 in SCRIPT;
execute cmd3 in SCRIPT;
……
auto_print;
remove_pattern_space;
done
done
其中while循环执⾏的正是SCRIPT中的所有命令,只不过⼀般情况下,while循环只执⾏⼀轮就退出并
进⼊外层的for循环。于是,外层的for循环称之为"d循环",内层的while循环称之为"SCRIPT"循环。所以,for循环只包含了两个动作:读取下⼀⾏和执⾏SCRIPT循环。
其实while循环中是有continue、break甚⾄是exit的,分别表⽰回到SCRIPT的顶端(即进⼊下⼀个SCRIPT循环)、退出当前SCRIPT循环回到外层d循环以及退出整个d循环。显然,这不是"花拳绣腿"的内容。
最后,说明下d命令⾏如何书写,其实就是写SCRIPT部分,这部分的写法⽐较灵活,⼤致有以下⼏种:
# ⼀⾏式。多个命令使⽤分号分隔
d Address{cmd1;}
# 多个表达式时,可以使⽤"-e"选项,也可以不⽤,但使⽤分号分隔
d Address1{cmd1;cmd2;cmd3};Address2{cmd1;cmd2;cmd3}...
d -e 'Address1{cmd1;cmd2;cmd3}' -e 'Address2{cmd1;cmd2;cmd3}' ...
# 分⾏写时
d Address1{
cmd1
cmd2
cmd3
}
Address2{
cmd1
cmd2
cmd3
}
如果是写在⽂件中,即d脚本,以⽂件名为a.d为例。
#!/usr/bin/d -f
#注释⾏
Address1{}
Address2{}
......
其中cmd部分还可以进⾏模式匹配,也即类似于"Address{{pattern1}cmd1;{pattern2}cmd2}"的写法。例如, /^abc/{2d;p} 。
有了以上基本的⼤纲性知识,理解和深⼊d机制就简单多了。
2、d选项
d选项不算多,能⽤到的更没⼏个。
d OPTIONS SCRIPT INPUT_STREAM
可能⽤到的⼏个选项:
'-n'
默认情况下,d将在每轮script循环结束时⾃动输出模式空间中的内容。使⽤该选项后可以使得这次⾃动输出动作输出空内容,⽽不是当前模式空间中的内容。注意,"-n"是输出空内容⽽不是禁⽤输出动作,虽然两者的结果都是不输出任何内容,但在有些依赖于输出动作和输出流的地⽅,它们的区别是很⼤的,前者有输出流,只是输出空流,后者则没有输出流。
'-e SCRIPT'
前⽂说了,SCRIPT中包含的是命令的集合,"-e"选项就是向SCRIPT中添加命令的。可以省略"-e"选项,但如果命令⾏容易产⽣歧义,则使⽤"-e"选项可明确说明这部分是SCRIPT中的命令。另外,如果⼀个"-e"选项不⽅便描述所需命令集合时,可以指定多个"-e"选项。
'-f SCRIPT-FILE'
指定包含命令集合的SCRIPT⽂件,让d根据SCRIPT⽂件中的命令集处理输⼊流。
'-i[SUFFIX]'
该选项指定要将d的输出结果保存(覆盖的⽅式)到当前编辑的⽂件中。GNU d是通过创建⼀个临时⽂件并将输⼊写⼊到该临时⽂件,然后重命名为源⽂件来实现的。
当当前输⼊流处理结束后,临时⽂件被重命名为源⽂件的名称。如果还提供了SUFFIX,则在重命名临时⽂件之前,先使⽤该SUFFIX修改源⽂件名,从⽽⽣成⼀个源⽂件的备份⽂件。
临时⽂件总是会被重命名为源⽂件名称,也就是说输⼊流处理结束后,仍使⽤源⽂件名的⽂件是d修改后的⽂件。⽂件名中包含了SUFFIX的⽂件则是最原始⽂件的备份。例如源⽂件为a.txt,d -i'.log' 将⽣成两个⽂件:a.txt和a.txt.log,前者是d修改后的⽂件,a.txt.log是源a.txt的备份⽂件。
重命名的规则如下:如果扩展名不包含符号"",将SUFFIX添加到原⽂件名的后⾯当作⽂件后缀;如果SUFFIX中包含了⼀个或多个字符"",则每个"*"都替换为原⽂件名。这使得你可以为备份⽂件添加⼀个前缀,⽽不是后缀。如果没有提供SUFFIX,源⽂件被覆盖,且不会⽣成备份⽂件。
该选项隐含了"-s"选项。
'-r'
使⽤扩展正则表达式,⽽不是使⽤默认的基础正则表达式。d所⽀持的扩展正则表达式和egrep⼀样。使⽤扩展正则表达式显得更简洁,因为有些元字符不⽤再使⽤反斜线"\"。正则表达式见。
'-s'
默认情况下,如果为d指定了多个输⼊⽂件,如d OPTIONS SCRIPT file1 file2 file3,则多个⽂件会被d当作⼀个长的输⼊流,也就是说所有⽂件被当成⼀个⼤⽂件。指定该选项后,d将认为命令⾏中给定的每个⽂件都是独⽴的输⼊流。
既然是独⽴的输⼊流,范围定址(如/abc/,/def/)就⽆法跨越多个⽂件进⾏匹配,⾏号也会在处理每个⽂件时重置,"$"代表的也将是每个⽂件的最后⼀⾏。这也意味着,如果不使⽤该选项,则这⼏个⾏为都是可以完成的。
⽰例:以d命令"p"和"="为例,其中"p"命令⽤于强制输出当前模式空间中的内容,"="命令⽤于输出d⾏号计数器当前的值,即刚被读⼊到模式空间中的⾏是输⼊流中的第⼏⾏。
(1).只输出a.txt中的第5⾏。
d -n
这⾥使⽤了"-n"选项,使得读取到模式空间的每⼀⾏都⽆法被输出,只有明确使⽤了"p"选项才能被"p"动作输出。由于只有读⼊的第5⾏内容能匹配"5",才能被"p"输出。
其实上⾯的命令和d -n -e '5p' a.txt是完全⼀样的,因为"5p"在d解析命令⾏时不会产⽣歧义,所以可以省略"-e"选项。
(2).输出a.txt,并输出每⾏的⾏号。
d '=' a.txt
由于要输出a.txt的内容,所以不使⽤"-n"选项,同时"="命令会输出每⾏⾏号。
(3).分别输出a.txt和b.txt的第5⾏,并分别保存到".bak"后缀的⽂件中。
d -i'*.bak' -n '5p' a.
此处必须使⽤"-s"选项,否则将只会输出"a."结合后的第5⾏。但"-i"隐含了"-s"选项。这会⽣成4个⽂件:a.txt、b.txt和
(4).使⽤扩展正则表达式,输出a.txt和b.txt中能包含3个以上字母"a"的⾏。
d -r -n '/aaa+/p' a.
3、定址表达式
当d将输⼊流中的⾏读取到模式空间后,就需要对模式空间中的内容进⾏匹配,如果能匹配就能执⾏对应的命令,如果不能匹配就直接输出、清空模式空间并进⼊下⼀个d循环读取下⼀⾏。
匹配的过程称为定址。定址表达式有多种,但总的来说,其格式为[ADDR1][,ADDR2]。这可以分为3种⽅式:
1. ADDR1和ADDR2都省略时,表⽰所有⾏都能被匹配上。
2. 省略ADDR2时,表⽰只有被ADDR1表达式匹配上的⾏才符合条件。
3. 不省略ADDR2时,是范围地址。表⽰从ADDR1匹配成功的⾏开始,到ADDR2匹配成功的⾏结束。
⽆论是ADDR1还是ADDR2,都可以使⽤两种⽅式进⾏匹配:⾏号和正则表达式。如下:
'N'绯闻女孩第一季插曲
指定⼀个⾏号,d将只匹配该⾏。(需要注意,除⾮使⽤了"-s"或"-i"选项,d将对所有输⼊⽂件的⾏连续计数。)
做月饼小游戏'FIRST~STEP'
表⽰从第FIRST⾏开始,每隔STEP⾏就再取⼀次。也就是取⾏号满⾜FIRST+(N*STEP) (其中N>=0)的⾏。因此,要选择所有奇数⾏,使⽤"1~2";要从第2⾏开始每隔3⾏取⼀次,使⽤"2~3";要从第10⾏开始每隔5⾏取⼀次,使⽤"10~5";⽽"50~0"则表⽰只取第50⾏。
'$'
默认该符号匹配的是最后⼀个⽂件的最后⼀⾏,如果指定了"-i"或"-s",则匹配的是每个⽂件的最后⼀⾏。总之,"$"匹配的是每个输⼊流的最后⼀⾏。
需要注意的是,d采⽤⾏号计数器来临时记录当前⾏的⾏号,因此d在读取到最后⼀⾏前即使是倒数第⼆⾏的时候,完全不知道最后⼀⾏是第⼏⾏,所以代表最后⼀⾏的"$"⽆法进⾏任何数学运算,例如倒数第⼆⾏使⽤"$-1"表⽰是错误的。⽽且,"$"只是⼀个额外的标记符号,当d读取到输⼊流的最后⼀⾏时,发现这就是最后⼀⾏,于是为此⾏打上"$"记号,并读取到模式空间中。门的英文单词
'/REGEXP/'
将选择能被正则表达式REGEXP匹配的所有⾏。如果REGEXP中⾃⾝包含了字符"/",则必须使⽤反斜线转义,即"\/"。
'/REGEXP/I'
和"/REGEXP/"是⼀样的,只不过匹配的时候不区分⼤⼩写。
'\%REGEXP%'
('%'可以使⽤其他任意单个字符替换。) 这和上⼀个定址表达式的作⽤是⼀样的,只不过是使⽤符号"%"替换了符号"/"。当REGEXP中包含"/"符号时,使⽤该定址表达式就⽆需对"/"使⽤反斜线"\"转义。但如果此时REGEXP中包含了"%"符号时,该符号需要使⽤"\"转义。
总之,定址表达式中使⽤的分隔符在REGEXP中出现时,都需要使⽤反斜线转义。
'ADDR1,+N'
匹配ADDR1和其后的N⾏。
'ADDR1,~N'
匹配ADDR1和其后的⾏直到出现N的倍数⾏。倍数可为随意整数倍,只要N的倍数是最接近且⼤于ADDR1的即可。 如ADDR1=1,N=3匹配1-3⾏,ADDR1=5,N=4匹配5-8⾏。⽽"1,+3"匹配的是第⼀⾏和其后的3⾏即1-4⾏。
另外,在定址表达式的后⾯加"!"符号表⽰反转匹配的含义。也就是说那些匹配的⾏将不被选择,⽽是不匹配的⾏被选择。
例如,以下⼏个定址的⽰例:
1
2
3
4
5
d -n '3p' INPUTFILE
d -n '3,5!p' INPUTFILE
d -n '3,/^# .*/! p' INPUTFILE
d -n '/abc/,/xyz/p' INPUTFILE
d -n '!p' INPUTFILE # 这个有悖常理,但确实是允许的
4.d常⽤命令
d命令很多,本⽂的只简单介绍⼏个最常见的。
此处不以命令的⽤法为重,⽽是通过这⼏个命令,引出d最重要的原理和执⾏机制(还包括本⽂的第⼀节内容),并为阅读下⼀篇⽂章打下基础。⽽且理解了这些原理,再使⽤d做任何操作都有理可循,遇到疑难之处也知道如何进⾏分析。⽽这,是任何书籍(包括⼴为推崇的d && awk)、教学视频和⽹络⽂章中都没有的内容(⾄少我从未见过,这些内容也是我花费⼤量精⼒经过⼤量实验推演出来)。
(1).强制输出命令"p"。
该命令能强制输出当前模式空间的内容。即使使⽤了"-n"选项。
事实上,它们本就不冲突,因为循环过程如下:
for ((line=1;line<=last_line_num;++line))
do
read $line to pattern_space;
while pattern_space is not null
do
execute cmd1 in SCRIPT;
execute cmd2 in SCRIPT;
scrollADDR1,ADDR2{print}; # "p" command
……
auto_print;佐治亚理工
remove_pattern_space;
done
done
在d处理的过程中,"p"和"auto_print"是两个输出动作,都是输出当前模式空间的内容,只不过auto_print是隐含动作。使⽤了"-n"选项,其所影响的动作仅是"auto_print",使其输出空内容。也因此,当没有使⽤"-n"选项时,模式空间的内容会被输出两次。
例如,仅输出标准输⼊的第2⾏内容。prometheus
[root@xuexi ~]# echo -e 'abc\nxyz' | d -n 2p
xyz
不加"-n"选项,在"p"输出之后,SCRIPT循环的结尾处还会被auto_print输出⼀次。
[root@xuexi ~]# echo -e 'abc\nxyz' | d 2p
abc
xyz # 这是p命令输出的结果
xyz # 这是⾃动输出的结果
(2).删除命令"d"。
命令"d"⽤于删除整个模式空间中的内容,并⽴即退出当前SCRIPT循环,进⼊下⼀个d循环,即读取下⼀⾏。
循环⼤致格式如下:
for ((line=1;line<=last_line_num;++line))
do
read $line to pattern_space;
while pattern_space is not null
do
execute cmd1 in SCRIPT;
execute cmd2 in SCRIPT;
ADDR1,ADDR2{delete;break}; # "d" command
……
auto_print;
remove_pattern_space;
done
done