Duplicar arquivo, substituir caractere e concatenar

1

Eu tenho um arquivo assim:

i36aasf5i7538i123
i47982i16537i1256
i1647i6458i3457
i1856i8456i43865

Eu quero fazer uma cópia do arquivo, no qual o primeiro i em cada linha é substituído por um o. Eu então quero concatenar o arquivo editado de volta para o arquivo original (de preferência sem precisar especificar um arquivo de saída).

Assim, a saída ficaria assim:

i36aasf5i7538i123
i47982i16537i1256
i1647i6458i3457
i1856i8456i43865
o36aasf5i7538i123
o47982i16537i1256
o1647i6458i3457
o1856i8456i43865

Eu conheço algumas frases que podem fazer isso. No entanto, ao usar sed, houve problemas com a codificação (o arquivo contém alguns caracteres incomuns). Usando perl, eu não tive esse problema, mas estava procurando uma maneira de encaixar isso em um script perl o mais "elegantemente" possível.

Estou usando um sistema operacional Unix.

    
por user146854 08.01.2016 / 15:55

3 respostas

3

Quando você anexa dados ao arquivo que está lendo, corre o risco de entrar em um loop infinito e aumentar o arquivo para sempre à medida que processa os dados que escreveu anteriormente.

Você pode se proteger contra isso com coisas como:

perl -pe '
  BEGIN{seek(STDOUT,0,2);$end = tell STDOUT}
  last if tell(ARGV) > $end;
  s/i/o/' < file >> file

Dentro de um script 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;
    
por 08.01.2016 / 16:34
1
sed 's/^i/o/;H;1h;$!d;x;q' <infile >>infile

Se o arquivo for pequeno o suficiente para caber na memória, o acima deve funcionar. Não consigo pensar em nenhum motivo pelo qual você possa ter um problema de codificação, a menos que seu sed esteja com bugs. Um sane sed deve manipular qualquer codificação de caracteres válida que você queira lançar nele.

Se não for pequeno o suficiente para caber na memória, então em um sistema que entende os /dev/fd/[num] links (que é praticamente qualquer sistema similar ao Unix) , e dado um shell que usa arquivos tmp para here-documents e não pipes (que é a maioria deles, para incluir o shell Bourne, bash e zsh mas não yash ou ash variantes como como BSD sh , dash ou busybox sh que usam pipes em vez disso), e espaço livre ${TMPDIR:-/tmp} suficiente para armazenar o buffer enquanto ele está sendo editado, então o seguinte deve funcionar:

sed -nf- file <<"" >>file
s/^i/o/
w /dev/fd/0
$r /dev/fd/0

Isso funcionará porque o shell obterá um arquivo temporário e um descritor de arquivo para o here-document, gravará o script sed nele, unlink() o arquivo temporário (e, portanto, removerá seu um e somente link no sistema de arquivos) , bifurque sed como um filho para herdá-lo, e restaure seu próprio estado para o estado em que estava antes de chamar sed - e então elimine seu próprio descritor para temp Arquivo. Nesse ponto, o arquivo existe apenas como descritor de stdin de sed , e o kernel é obrigado a manter o arquivo apenas enquanto existir algum identificador para ele, mas assim que todos os descritores forem liberados, ele removerá um arquivo com 0 links do sistema de arquivos.

Então, sed lerá seu script a partir do temp -f ile excluído e truncá-lo-á como seu arquivo w rite denominado - que é apenas um link para o arquivo excluído a partir do qual leu seu script - e antes de puxar cada linha de entrada, ele irá escrever uma cópia de seu espaço padrão. sed irá autoprint -n othing, mas na sua última linha de entrada $ , r levará ao seu stdout o arquivo para o qual ele tem w riting o tempo todo - e isso será >> acrescentado à sua edição nomeada file .

Quando sed terminar e seu processo terminar, o último descritor restante para a fonte <<"" here-doc será fechado, e o kernel limpará posteriormente o arquivo. Enquanto isso, nenhum outro processo terá qualquer meio de acessar o arquivo e, portanto, está imune a qualquer possibilidade de outro processo afetar de alguma forma o buffer de trabalho de sed .

Se -nf- não funcionar, provavelmente é porque o seu sed não interpreta - para significar stdin (embora a maioria faça) e você deve usar -nf/dev/fd/0 .

    
por 08.01.2016 / 18:48
1

Você pode usar o mapeamento de memória para "trapacear" criando um mapa de memória restrito sobre o arquivo limitado ao tamanho inicial do arquivo. Separadamente, abra outro identificador para esse arquivo e procure esse identificador até o final. Inicie a iteração no mapa de memória, escrevendo cada linha que é lida no outro identificador de arquivo posicionado no final do arquivo. Representante python code

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))
    
por 08.01.2016 / 19:21