Faz o loop de linhas no arquivo e subtrai a linha anterior da linha atual

3

Eu tenho um arquivo que contém alguns números

$ cat file.dat
0.092593
0.048631
0.027957
0.030699
0.026250
0.038156
0.011823
0.013284
0.024529
0.022498
0.013217
0.007105
0.018916
0.014079

Eu quero fazer um novo arquivo que contenha a diferença da linha atual com a linha anterior. A saída esperada deve ser

$ cat newfile.dat
-0.043962
-0.020674
0.002742
-0.004449
0.011906
-0.026333
0.001461
0.011245
-0.002031
-0.009281
-0.006112
0.011811
-0.004837

Pensando que isso foi trivial, eu comecei com este pedaço de código

f="myfile.dat"    
while read line; do
    curr=$line
    prev=

    bc <<< "$line - $prev" >> newfile.dat
done < $f

mas percebi rapidamente que não tenho ideia de como acessar a linha anterior no arquivo. Eu acho que também preciso explicar que nenhuma subtração deve ocorrer ao ler a primeira linha. Qualquer orientação sobre como proceder é apreciada!

    
por Yoda 19.09.2018 / 16:26

6 respostas

7
$ awk 'NR > 1 { print $0 - prev } { prev = $0 }' <file.dat
-0.043962
-0.020674
0.002742
-0.004449
0.011906
-0.026333
0.001461
0.011245
-0.002031
-0.009281
-0.006112
0.011811
-0.004837

Fazer isso em um loop de shell chamando bc é complicado. O acima usa um script simples de awk que lê os valores do arquivo um por um e para qualquer linha após o primeiro, ele imprime a diferença conforme você descreve.

O primeiro bloco, NR > 1 { print $0 - prev } , imprime condicionalmente a diferença entre esta e a linha anterior se tivermos atingido a linha dois ou mais ( NR é o número de registros lidos até o momento e um "registro" é de padrão uma linha).

O segundo bloco, { prev = $0 } , define incondicionalmente prev para o valor na linha atual.

Redirecione a saída para newfile.dat para salvar o resultado:

$ awk 'NR > 1 { print $0 - prev } { prev = $0 }' <file.dat >newfile.dat

Relacionados:

Houve algumas menções sobre a lentidão de chamar bc em um loop. O seguinte é uma maneira de usar uma única invocação de bc para fazer a aritmética enquanto ainda lemos os dados em um loop de shell (eu não recomendaria realmente resolver esse problema dessa forma, e só estou mostrando aqui para pessoas interessado em coprocessos em bash ):

#!/bin/bash

coproc bc

{
    read prev

    while read number; do
        printf '%f - %f\n' "$number" "$prev" >&"${COPROC[1]}"
        prev=$number

        read -u "${COPROC[0]}" result
        printf '%f\n' "$result"
    done
} <file.dat >newfile.dat

kill "$COPROC_PID"

O valor em ${COPROC[1]} é o descritor de arquivo de entrada padrão de bc , enquanto ${COPROC[0]} é o descritor de arquivo de saída padrão de bc .

    
por 19.09.2018 / 16:31
3

Usando alguns utilitários GNU simples e sem loops de shell:

paste -d- <(head -n-1 file.dat) <(tail -n+2 file.dat) | bc

A ideia aqui é duplicar o arquivo de entrada em duas colunas; compense a segunda coluna por 1 linha e cole as colunas junto com - como separador. head e tail são usados para compensar a última linha da primeira e segunda linhas da segunda coluna, respectivamente, para obter a compensação necessária. A lista resultante é a lista obrigatória de diferenças aritméticas canalizada para bc para avaliação.

Experimente online.

Como alternativa, se gostar de sed , você pode fazer isso:

sed '1{s/$/-\/;p;d};${p;d};s/.*/&\n&-\/' file.dat | bc

Isso duplica cada linha e insere -\ no final da segunda versão de cada linha. As primeiras e últimas linhas são tratadas de maneira diferente para gerar as expressões necessárias. A saída sed termina assim:

a-\
b
b-\
c
c-\
d

Estas são novamente diferenças aritméticas válidas que bc pode avaliar. Não que bc entenda as barras invertidas de continuação de linha nas extremidades de todas as outras linhas.

Experimente online.

    
por 19.09.2018 / 18:57
1

Se você quisesse tentar forçar o script de shell a funcionar, estava faltando alguma inicialização:

f=myfile.dat
prev=0
while read line; do
    bc <<< "$line - $prev"
    prev=$line
done < $f > newfile.dat

... onde também movi o redirecionamento para fora do loop, apenas para salvar algumas E / S.

A solução bc não imprime zeros à esquerda, enquanto a solução awk funciona.

    
por 19.09.2018 / 16:40
1

Você poderia usar um redirecionamento exec para ler linhas sucessivas do arquivo de entrada de vários pontos no script - uma vez antes do loop (para configurar o valor inicial) e repetidamente durante o mesmo (para cada novo valor para subtrair ):

exec 3<file.dat
read prev<&3
while read curr ; do
        bc <<< "$curr - $prev" >> newfile.dat
        prev=$curr
done <&3
    
por 19.09.2018 / 16:44
0

Eu uso matrizes. Eu uso eles para tudo. Não consigo me lembrar de como o awk e o sed funcionam sem um estudo extensivo das páginas do manual. Aqui está o jeito que eu faria.

f=( $(< file.dat) )
for ((num=1;num<=${#f[@]};num++))
do
    echo $(bc <<< ${f[$num]}-${f[(($num-1))]})>>differences.dat
done

É assim que eu entendo. Ele tem os recursos questionáveis de algumas das outras respostas: loop e chamar bc repetidamente. No entanto, ele só lê o arquivo uma vez, como as respostas usando sed e awk.

    
por 19.09.2018 / 20:40
-1

Você poderia tentar isso

num <- as.data.frame(num)
num$sub_num <- num[c(2:14, c("0")), ]
num$diff <- num$num - num$sub_num
    
por 20.09.2018 / 07:16