复制文件,替换字符,然后连接

复制文件,替换字符,然后连接

我有一个如下所示的文件:

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、bashzsh但不包括BSD 等变体yash,或者使用管道代替)ashshdashbusybox 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,然后将其截断为其命名的write 文件 - 这只是它读取其脚本的已删除文件的链接 - 在提取每个输入行之前,它将写入一个其模式空间的副本。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))

相关内容