ed再研究(ed的临时文件与sed -i的临时文件区别)

趁热打铁

 

前边讨论sed -i 与sed的差别时提到:ed实际上是全文拷贝到内存做镜象,通过编辑镜象和回写保存整个文件。

sed是流编辑器,对流有感性理解的人不难理解sed -i的临时文件的作用:做为流的输入

sed -i可以形象的比喻成:把一桶水原来的标签拿掉,换个新标签,通过“管道”将水倒进一个新的桶里

那么ed呢,ed是处理方式则是:把桶里的水“倒(实际上是复制)”到另一个桶里,直接对桶进行处理,再倒回原来的桶。

 

见man ed原文

 

If invoked with a file argument, then a copy of file is read into the editor’s buffer. Changes are made to this copy and not directly to file itself.

 

cu上有版主提示说,ed是有临时文件的,但在man里并没有仔细的提及,细翻源码发现,确定有运行tmpfile()程序

tmpfile程序是stdio里包含的一个标准函数,通过创建一个临时文件返回描述符,

且在较的版本里,ed在创建tmpfile后,直接unlink 该文件

[root@rac0 ed-1.5]# strace ./ed execve("./ed", ["./ed"], [/* 31 vars */]) = 0 ... open("/tmp/tmpfyDwG6P", O_RDWR|O_CREAT|O_EXCL, 0600) = 3 unlink("/tmp/tmpfyDwG6P") = 0

 

还可以通过lsof查看进程打开的临时文件:
[root@rac0 ~]# lsof |grep "^ed"|grep tmp ed 17874 root 3u REG 253,0 0 663197 /tmp/tmpfyDwG6P (deleted)

 

文件虽然被unlink了,只要进程还在,描述符和资源仍在,

 

那么接下来自然会想到:ed的这个临时文件,跟sed -i的临时文件有何区别

这就要了解ed的临时文件的作用了

 

ed的临时文件叫:scrach_file

前文有提到,ed里内存保存的数据的内存结构叫:line_buffer

源码定义结点如下:

 

typedef struct line /* Line node */ { struct line * q_forw; struct line * q_back; long pos; /* position of text in scratch buffer */ int len; /* length of line */ } line_t;

 

这是一个双向链表,整个链表代表着内存中保存的所有行,链表中的一个结点代表着一行记录

每个结点保存着行的起始位置(pos)跟长度,

这里的pos是起始位置,对应的便是scrach_file

ed正是对line_buffer+scrach_file进行编辑

 

我们通过对ed进行strace来看看ed的内部运行

1:准备文件aaa,有六行记录

[root@rac0 tmp]# cat -n aaa 1 1 2 2 3 3 4 4 5 5 6 6

 

2:用ed aaa

[root@rac0 tmp]# ed aaa 12

3:另开一个终端,strace 进程

[root@rac0 tmp]# ps -ef |grep ed root 18133 15769 0 10:20 pts/5 00:00:00 ed aaa ... [root@rac0 tmp]# strace -p 18133 Process 18133 attached - interrupt to quit read(0,

 刚开始执行ed的时候,程序会处理中断输入状态

从临时文件大小可以看出来,此时临时文件大小为0,已经创建,但未写入

似乎并没有将内容写入到临时文件里,因为数据大的话,这种情况不存在,

猜测是由于文件太小了,这里调用的是标准io,数据还在缓冲里,

 

 

4:在ed里执行命令3p,打印第三行,并观察strace

[root@rac0 tmp]# ed aaa 12 3p 3

 

 fstat64(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 5), ...}) = 0 read(0, "3", 1) = 1 read(0, "p", 1) = 1 read(0, "/n", 1) = 1 write(3, "123456", 6) = 6 _llseek(3, 0, [0], SEEK_SET) = 0 read(3, "123456", 4096) = 6 fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 5), ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7c73000 write(1, "3/n", 2) = 2 read(0,

 

文件描述符3指向的是scrach_file,

执行了第一条命令后,内容才被写进了scrach_file,然后又读了出来

 

5:替换第三行第一个字符为“ddd",然后输出

[root@rac0 tmp]# ed aaa 12 ? 3p 3 3s/./ddd ddd 3p ddd

观察strace结果:

read(0, "3", 1) = 1 read(0, "s", 1) = 1 read(0, "/", 1) = 1 read(0, ".", 1) = 1 read(0, "/", 1) = 1 read(0, "d", 1) = 1 read(0, "d", 1) = 1 read(0, "d", 1) = 1 read(0, "/n", 1) = 1 open("/usr/lib/gconv/gconv-modules.cache", O_RDONLY) = 4 fstat64(4, {st_mode=S_IFREG|0644, st_size=25462, ...}) = 0 mmap2(NULL, 25462, PROT_READ, MAP_SHARED, 4, 0) = 0xb7c6c000 close(4) = 0 _llseek(3, 6, [6], SEEK_SET) = 0 fstat64(3, {st_mode=S_IFREG|0600, st_size=6, ...}) = 0 _llseek(3, 0, [0], SEEK_SET) = 0 read(3, "123456", 4096) = 6 write(3, "ddd", 3) = 3 _llseek(3, 0, [0], SEEK_SET) = 0 read(3, "123456ddd", 4096) = 9 write(1, "ddd/n", 4) = 4 read(0, "3", 1) = 1 read(0, "p", 1) = 1 read(0, "/n", 1) = 1 _llseek(3, 9, [9], SEEK_SET) = 0 write(1, "ddd/n", 4) = 4

可以看到,原来第三行的内容“3”,还在scrach_file文件里,

而替换完了的第三行新内容"ddd"实际上追加到了scrach_file文件的后边了

 

 

 

由此可见,ed的临时文件scrach_file的作用,是配合内存中的line_buffer,保存文件镜象内容

 

 

ed在行读写时,都是根据line_buffer里的行起始和行长度,从scratch_file进行读写的

比如指定行输出(一行或多行),对应源码里这个函数:

/* print a range of lines to stdout */ bool display_lines( int from, const int to, const int gflags ) { line_t * const ep = search_line_node( inc_addr( to ) ); line_t * bp = search_line_node( from ); if( !from ) { set_error_msg( "Invalid address" ); return false; } while( bp != ep ) { const char * const s = get_sbuf_line( bp ); if( !s ) return false; set_current_addr( from++ ); put_tty_line( s, bp->len, gflags ); bp = bp->q_forw; } return true; }

 

其中,get_sbuf_line这个函数便是进行scratch_file的读写

/* get a line of text from the scratch file; return pointer to the text */ char * get_sbuf_line( const line_t * const lp )

 

这么频繁的文件读写,效率会高吗?

这其实是一种unix编程的哲学,unix/linux的shell应用程序都有其编写原则

比如:如果处理的是行对象,则没必要在内存中保存所有的数据内容,保需要在内存中保存相关行数据以进行处理

因为这样子提高了程序的应用范围

不会导致程序在处理大数据量文件时候大量占用内存空间。

 

 

你可能感兴趣的:(unix,struct,File,null,search,buffer)