我有一个如下所示的文件:
i36aasf5i7538i123
i47982i16537i1256
i1647i6458i3457
i1856i8456i43865
我想复制该文件,其中每行中的第一个 i 被替换为 o。然后,我想将编辑后的文件连接回原始文件(最好不需要指定输出文件)。
所以输出看起来像这样:
i36aasf5i7538i123
i47982i16537i1256
i1647i6458i3457
i1856i8456i43865
o36aasf5i7538i123
o47982i16537i1256
o1647i6458i3457
o1856i8456i43865
我知道一些单行话可以做到这一点。然而,在使用sed时,编码出现了问题(文件包含一些不寻常的字符)。使用 perl,我没有遇到这个问题,但我正在寻找一种方法将其尽可能“优雅”地融入到 perl 脚本中。
我使用的是 Unix 操作系统。
答案1
当您将数据附加到正在读取的文件时,您将面临进入无限循环并永远增长文件的风险,因为您最终会处理之前写入的数据。
你可以通过以下方法来防范这种情况:
perl -pe '
BEGIN{seek(STDOUT,0,2);$end = tell STDOUT}
last if tell(ARGV) > $end;
s/i/o/' < file >> file
在 Perl 脚本中:
open OUT, ">>", "file" or die "open file: $!";
open IN, "<", "file" or die "open file: $!";
seek(OUT,0,2) or die "seek: $!";
$end = tell OUT;
while (tell IN < $end && <IN>) {
s/i/o/;
print OUT $_;
}
close IN;
close OUT;
答案2
sed 's/^i/o/;H;1h;$!d;x;q' <infile >>infile
如果文件足够小以适合内存,那么上面的方法应该可以工作。我想不出任何可能出现编码问题的原因,除非你sed
有问题。理智的人sed
应该处理你可能想扔给它的任何有效的字符编码。
如果它不够小,无法放入内存,那么在一个能够理解的系统上/dev/fd/[num]
链接(实际上是任何类 Unix 系统),并给出一个使用 tmp 文件作为此处文档而不是管道的 shell(其中大多数,包括 Bourne shell、bash
,zsh
但不包括BSD 等变体yash
,或者使用管道代替)ash
sh
dash
busybox sh
,并且有足够的可用${TMPDIR:-/tmp}
空间来容纳正在编辑的缓冲区,那么以下应该可以工作:
sed -nf- file <<"" >>file
s/^i/o/
w /dev/fd/0
$r /dev/fd/0
这将起作用,因为 shell 将获取一个临时文件和此处文档的文件描述符,将脚本写入sed
其中,unlink()
即临时文件(因此删除其在文件系统中唯一的链接),然后分叉 sed
为子级来继承它,并将其自己的状态恢复到调用之前的状态sed
- 因此将其自己的描述符放入临时文件中。此时,该文件仅作为sed
的 stdin 描述符存在,并且只要该文件的任何句柄存在,内核就必须维护该文件,但一旦释放所有描述符,它将删除文件系统为 0 的文件链接。
因此sed
将从已删除的临时文件中读取其脚本-f
,然后将其截断为其命名的w
rite 文件 - 这只是它读取其脚本的已删除文件的链接 - 在提取每个输入行之前,它将写入一个其模式空间的副本。sed
会自动打印-n
任何东西,但在它的$
最后一个输入行上,它会将它r
一直在编写的文件写入到它的标准输出中w
- 并且该文件将被>>
附加到它的命名编辑中file
。
当sed
完成并且其进程终止时<<""
,here-doc 源的最后一个剩余描述符将被关闭,内核随后将清理该文件。同时,没有其他进程可以通过任何方式访问该文件,因此它不会受到其他进程以某种方式影响sed
工作缓冲区的任何可能性。
如果-nf-
不起作用,可能只是因为您sed
没有将其解释-
为 stdin(虽然大多数都是)你应该使用-nf/dev/fd/0
.
答案3
您可以使用内存映射来“作弊”,方法是在文件上创建限制为文件初始大小的受限内存映射。分别打开该文件的另一个句柄并查找该句柄到底。开始迭代内存映射,将读取的每一行写入位于文件末尾的另一个文件句柄。代表python
代码
import mmap
with open('file', 'r+') as f1, open('file', 'r+b') as f2:
mm = mmap.mmap(f2.fileno(), 0) #memory map restricted to current file length
f1.seek(0, 2) #seek to end of file
for line in mm:
f1.write(line.replace('i', 'o', 1))