Edições específicas para um arquivo de texto (awk?)

5

Eu tenho um arquivo que parece

TITLE
1.000000000000000
10.0000000000000000    0.0000000000000000    0.0000000000000000 
0.0000000000000000   10.0000000000000000    0.0000000000000000
0.0000000000000000    0.0000000000000000   10.0000000000000000
U   U
X   X
C
0.2000000000000028  0.2000000000000028  0.2000000000000028
0.2967599999999990  0.0641000000000034  0.1551499999999990
0.1033699999999982  0.3361099999999979  0.244990000000001

e eu preciso ter um script que modifique o bloco numérico inferior (abaixo de C a 30 a menos que seus valores originais. Existe alguma maneira de fazer isso?

Até agora, o melhor que eu tenho é

$ awk '{if(NR>1){for(i=2;i<=NF;i++){$(i)=$(i)-10;}}print;}' data.txt | column -t

mas, isso é da internet e não sei como manipulá-lo para o efeito desejado. No entanto, isso não imprime / sobrescreve o que está atualmente em data.txt , que é o que eu estou procurando.

Obrigado pela ajuda!

    
por MadisonCooper 09.06.2015 / 22:36

4 respostas

1

Aqui está a minha versão do awk:

awk '/^C/,0 {for (i=1;i<=NF;i++) { if ( $i != "C" ) printf "%.16f ",$i-30.0000};print"\n" }' data.txt

Aqui, obtemos todas as coisas do caractere C até o final do arquivo, subtraímos 30 em cada coluna, adicionamos nova linha e repetimos o processo. A instrução if foi adicionada para evitar subtrair 30 de C, obviamente.

A saída é esta:

    46)serg@ubuntu[/home/xieerqi]
    >_ awk '/^C/,0 {for (i=1;i<=NF;i++) { if ( $i != "C" )  printf "%.16f ",$i-30.0000};print"\n" }' data.txt                             

-29.7999999999999972 -29.7999999999999972 -29.7999999999999972 

-29.7032400000000010 -29.9358999999999966 -29.8448500000000010 

-29.8966300000000018 -29.6638900000000021 -29.7550099999999986 

Isso pode ser substituído no arquivo original; Alternativamente, poderíamos sempre tentar imprimir coisas antes de C com uma instrução BEGIN {}

    
por Sergiy Kolodyazhnyy 09.06.2015 / 23:23
3

Dado data.awk abaixo:

{
        if (matched) {
                for (i = 1; i <= NF; i++) {
                        $(i) = 30.0 - $(i)
                }
        }
        print
}
/^C/ { matched = 1 }
BEGIN { CONVFMT = "%.20f" }

Você recebe:

$ awk -f data.awk data.txt
TITLE
1.000000000000000
10.0000000000000000    0.0000000000000000    0.0000000000000000 
0.0000000000000000   10.0000000000000000    0.0000000000000000
0.0000000000000000    0.0000000000000000   10.0000000000000000
U   U
X   X
C
29.79999999999999715783 29.79999999999999715783 29.79999999999999715783
29.70324000000000097543 29.93589999999999662350 29.84485000000000098908
29.89663000000000181444 29.66389000000000208956 29.75500999999999862666

Obviamente, tem um problema de precisão para a sua entrada. Então, você pode querer chamar o comando bc para cálculos reais (ele suporta precisão arbitrária):

{
        if (matched) {
                for (i = 1; i <= NF; i++) {
                        cmd = "echo 30.0 - " $(i) " | bc"
                        cmd | getline $(i)
                        close(cmd)
                }
        }
        print
}
/^C/ { matched = 1 }

Resultado:

TITLE
1.000000000000000
10.0000000000000000    0.0000000000000000    0.0000000000000000 
0.0000000000000000   10.0000000000000000    0.0000000000000000
0.0000000000000000    0.0000000000000000   10.0000000000000000
U   U
X   X
C
29.7999999999999972 29.7999999999999972 29.7999999999999972
29.7032400000000010 29.9358999999999966 29.8448500000000010
29.8966300000000018 29.6638900000000021 29.755009999999999

Para sobrescrever data.txt com o resultado, normalmente é necessário gravá-lo em outro arquivo e renomeá-lo para o arquivo original.

$ awk -f data.awk data.txt > data.txt.out
$ mv data.txt.out data.txt

Ou use sponge em moreutils .

$ sudo apt-get install moreutils
$ awk -f data.awk data.txt | sponge data.txt
    
por yaegashi 09.06.2015 / 23:08
1

Usando python :

#!/usr/bin/env python2
import decimal
with open('/path/to/data.txt') as f:
    for line in f:
        if line.rstrip() == 'C':
            print line.rstrip()
            break
        else:
            print line.rstrip()
    for line in f:
        print '\t'.join(['{0:.16f}'.format(decimal.Decimal(30 - float(part))) for part in line.rstrip().split()])

Saída:

TITLE
1.000000000000000
10.0000000000000000    0.0000000000000000    0.0000000000000000
0.0000000000000000   10.0000000000000000    0.0000000000000000
0.0000000000000000    0.0000000000000000   10.0000000000000000
U   U
X   X
C
29.7999999999999972 29.7999999999999972 29.7999999999999972
29.7032400000000010 29.9358999999999966 29.8448500000000010
29.8966300000000018 29.6638900000000021 29.7550099999999986
  • Sempre que python lê uma linha do arquivo, o ponteiro é aumentado em um para apontar para a próxima linha, estamos utilizando isso para ler e imprimir até a linha que contém apenas C .

  • Para as linhas após C , dividimos a linha em partes por line.rstrip().split() e, em seguida, subtraímos cada parte de 30 para obter o resultado desejado.

  • Para obter precisão no número de ponto flutuante resultante, usamos o módulo decimal .

por heemayl 09.06.2015 / 22:56
1

Não posso fornecer uma boa solução para o problema, mas tentarei descrever em profundidade qual é o problema e fornecerei uma solução parcial.

O problema :

Os números de pontos flutuantes nas máquinas sofrem de precisão limitada: em suma, apenas um subconjunto limitado de números de ponto flutuante [por cada ordem de magnitude] é representável.

Os números de pontos flutuantes nas máquinas são representados de perto seguindo a notação normalizada ± significand * base ^ exponent (onde base = base de representação, significand = qualquer número real > 0 e < = a base de representação e onde exponent = ordem de magnitude): por exemplo, em uma máquina de 32 bits seguindo o padrão IEEE 754 , números de ponto flutuante de precisão simples são representados usando o primeiro bit para representar o sinal, os 8 bits a seguir para representar a ordem de magnitude e os últimos 23 bits para representar o significando, enquanto números de ponto flutuante de precisão dupla são representados usando o primeiro bit para representar o sinal, os 11 bits seguintes para representar a ordem de grandeza e os últimos 52 bits para representar o significando (a base , sendo sempre 2, não está representado). Para isso, o significando de um número deve ser representado sempre usando 23 bits (usando precisão simples) ou usando 52 bits (usando precisão dupla).

Uma propriedade dessa forma de representar números de ponto flutuante em um número fixo de bits é o número de significados representáveis por ordem de magnitude sempre igual, a "distância" média entre números de ponto flutuante representáveis com a mesma ordem de magnitude aumenta conforme a ordem de grandeza dos dois aumenta.

Para o acima, o primeiro problema é que, se o significante da notação normalizada de um número de ponto flutuante não estiver no conjunto limitado de significados representáveis, ele é arredondado para o significante mais próximo (maior ou menor) representável.

Falando de números representados com a mesma ordem de magnitude, um segundo problema é que, mesmo quando um número de ponto flutuante é representável com precisão, adicionar / subtrair outro número de ponto flutuante [precisamente representável] pode resultar em uma flutuação não representável. número do ponto, cujo significand será arredondado para o mais próximo (maior ou menor) significand representable.

Finalmente, falando de números representados com uma ordem de grandeza diferente, o terceiro problema (principalmente devido à arquitetura da CPU) é que, para poder realizar adições / subtrações entre números de ponto flutuante representados com uma ordem diferente de magnitude , os números precisam ser representados pela primeira vez usando a mesma ordem de grandeza; isto implica que a menor ordem de magnitude precisa ser aumentada, e que (para equilibrar isso) seu significado precisa ser deslocado para a direita, com a consequente perda do número de bits excedendo o 23/52 disponível; se isso não for suficiente, números de ponto flutuante com uma diferença significativa em sua ordem de grandeza podem resultar, uma vez adicionados / subtraídos, exatamente no número com o maior valor absoluto, isso para o problema já declarado (diferença insuficiente para pisar o não significados representáveis para cima / baixo para um significante representável superior / inferior diferente) e cada vez pior, à medida que a ordem de grandeza de dois números diverge mais.

As implicações de tudo isso são: você nunca terá certeza de obter um resultado preciso usando matemática de ponto flutuante, no entanto, isso pode ser mitigado usando uma representação de maior precisão.

A solução parcial :

Pelo acima, os resultados dessas awk one-liners não são precisos; isso poderia ter sido mitigado pelo uso de precisão dupla em seus comandos printf , mas isso não é suportado.

Isso diminuirá em 30 o valor dos três primeiros números separados por espaço em cada linha após a primeira linha corresponder a C , mantendo o formato dos números. Como a versão awk incluída no Ubuntu não suporta edições in-loco, você terá que usar awk e redirecionar seu stdout para um arquivo usando o operador bash > ou usar gawk (GNU awk ) > = 4.10.0 ;

Usando awk :

awk 'NR==1, $0=="C"; $0=="C", 0 {if ($0!="C") printf "%.16f  %.16f  %.16f\n", $1-30, $2-30, $3-30}' data.txt > data_processed.txt

Usando gawk (% GNUawk) > = 4.10.0

gawk -i inplace 'NR==1, $0=="C"; $0=="C", 0 {if ($0!="C") printf "%.16f  %.16f  %.16f\n", $1-30, $2-30, $3-30}' data.txt
  • NR==1, $0=="C"; : seleciona e imprime todos os registros entre a primeira e a primeira correspondência C inclusive;
  • $0=="C", 0 {if ($0!="C") printf "%.16f %.16f %.16f\n", $1-30, $2-30, $3-30} : seleciona todos os registros entre o primeiro C correspondente e o último inclusivo e imprime o primeiro, o segundo e o terceiro campo de cada registro selecionado não coincidentes C do espaço duplo separado e diminuído por 30 mantendo o original formato do número;

Exemplo de saída:

~/tmp$ cat data.txt
TITLE
1.000000000000000
10.0000000000000000    0.0000000000000000    0.0000000000000000 
0.0000000000000000   10.0000000000000000    0.0000000000000000
0.0000000000000000    0.0000000000000000   10.0000000000000000
U   U
X   X
C
0.2000000000000028  0.2000000000000028  0.2000000000000028
0.2967599999999990  0.0641000000000034  0.1551499999999990
0.1033699999999982  0.3361099999999979  0.244990000000001
~/tmp$ awk 'NR==1, $0=="C"; $0=="C", 0 {if ($0!="C") printf "%.16f  %.16f  %.16f\n", $1-30, $2-30, $3-30}' data.txt
TITLE
1.000000000000000
10.0000000000000000    0.0000000000000000    0.0000000000000000 
0.0000000000000000   10.0000000000000000    0.0000000000000000
0.0000000000000000    0.0000000000000000   10.0000000000000000
U   U
X   X
C
-29.7999999999999972  -29.7999999999999972  -29.7999999999999972
-29.7032400000000010  -29.9358999999999966  -29.8448500000000010
-29.8966300000000018  -29.6638900000000021  -29.7550099999999986
    
por kos 09.06.2015 / 23:48