Como obter todos os números de uma string e adicioná-los?

2

Eu tenho que analisar um arquivo .xml gerado para resumir os resultados de uma execução de um testSuite em algum software. Em uma linha eu tenho, por exemplo:

<Summary failed="10" notExecuted="0" timeout="0" pass="18065" />

Isso indica que o número de testes falhou, não foi executado e foi aprovado. Eu preciso descobrir quantos testes foram no conjunto de testes, então eu preciso adicionar, no caso acima, 10 + 0 + 18065 = 18075.

Como posso fazer isso no Bash?

    
por farid99 10.07.2015 / 18:17

7 respostas

2

Basta eliminar todos os caracteres que não sejam dígitos e não o espaço:

echo '<Summary failed="10" notExecuted="0" timeout="0" pass="18065" />'|\
sed -e 's/[^0-9 ]//g'

10 0 0 18065

.

A soma pode ser feita com dc (com o campo de tempo limite filtrado conforme solicitado)

echo '<Summary failed="10" notExecuted="0" timeout="0" pass="18065" />'|\
sed -e 's/timeout="[0-9]*" //' \
    -e 's/[^0-9 ]//g' \
    -e 's/^ *//' \
    -e 's/ *$//' \
    -e 's/ /+/g' \
    -e 's/^/0 /' \
    -e 's/$/pq/'|dc

.

Descrição

Como um script sed, ficaria assim

s/timeout="[0-9]*" //    #remove the timeout
s/[^0-9 ]//g             #drop anything but numbers and spaces
s/^ *//                  #drop spaces at the beginning of the line
s/ *$//                  #drop spaces at the end of the line
s/ /+/g                  #replace remaining spaces with +
s/^/0 /                  #add a 0 to initialize the sum for dc
s/$/pq/                  #add print and quit command for dc

O script pode ser usado simplesmente com

INPUT|sed -f script.sed

Deixo para você aplicar este script com sed e dc para entrada de múltiplas linhas. O que eu escrevi só funciona em uma linha!

    
por 10.07.2015 / 18:21
3

Você pode usar xmlstarlet para analisar o xml adequado.

Para o seu problema:

total=0; \
for i in failed notExecuted pass; do \
    sum='xmlstarlet sel -t -v "//Summary/@$i" test.xml'; \
    total=$(($sum + $total)); \
done; \
echo "Total=$total"

em que test.xml é seu arquivo contendo os dados xml.

    
por 10.07.2015 / 19:27
3

Usando perl

perl -lne 'my @a=$_=~/(\d+)/g;$sum+=$_ for @a; print $sum' file

Usando awk

tr ' ' '\n' < file | 
    awk '/[0-9]+/ {gsub(/[^0-9]/, "", $0); sum+=$0} END {print sum}'

Exemplo

% perl -lne 'my @a=$_=~/(\d+)/g;$sum+=$_ for @a; print $sum' foo
18075

% tr ' ' '\n' < foo | 
    awk '/[0-9]+/ {gsub(/[^0-9]/, "", $0); sum+=$0} END {print sum}' 
18075

% cat foo
<Summary failed="10" notExecuted="0" timeout="0" pass="18065" />
    
por 10.07.2015 / 19:43
1

Aqui está outro com dc :

{   tr -cs 0-9 \n 
    echo '[pq]sq[z2>q+l+x]s+l+x'
}   <<\IN | dc
<Summary failed="10" notExecuted="0" timeout="0" pass="18065" />
IN

dc primeiro lê todo o infil - depois que tr tiver espremido cada sequência de caracteres não numéricos em um único \n ewline - e depois lê uma pequena sequência de macro de laço echo ed que informa para adicionar todos os valores de sua pilha a cada um até que menos de dois permaneçam, em cujo ponto ele apenas imprime qualquer soma e sai. Neste caso, a soma é ...

18075

Se você tem um GNU dc , você pode escrevê-lo assim:

tr -cs 0-9 \n <in | dc -f- -e'[pq]sq[z2>q+l+x]s+l+x'

Ou, se o arquivo for muito grande, você também desejará bloqueá-lo para manter dc do buffer em excesso ao mesmo tempo.

(tr -cs 0-9 \n|xargs -n128|tr \  +)<in |
 dc -e'[pq]sq' -e'0[?z2>q+l+x]s+l+x'

... que armazenará 128 números de cada vez.

Assim:

seq -skfkridmdk 100000 |
(tr -cs 0-9 \n|xargs -n128|tr \  +)|
 dc -e'[pq]sq' -e'0[?z2>q+l+x]s+l+x'
5000050000

Se você tem certeza dos quatro por linha, e você quer largar *timeout=* , então você pode fazer:

<in  grep '^<Summary'   |
     cut -d\" -f2,4,8   |
     tr \" \n          |
     xargs -n512        |
     tr \  +            |
     dc -e'[?z2>q+l+x]s+'\
        -e\[pq]sq -e0l+x

Que somente falhará, não executado, aprovado , desde que suas posições relativas " sejam constantes e sejam os únicos tipos de linhas de entrada que podem corresponder a ^<Summary . Eu tentei como:

for x in 512 4096 16384; do time \
yes $'kdkeifndjei\n<Summary failed="10" notExecuted="0" timeout="0" pass="18065" />'|
     grep '^<Summary'   |
     cut  -d\" -f2,4,8  |
     head -n1000000     |
     tr \" \n          |
     xargs -n"$x"       |
     tr \  +            |
     dc -e'[?z2>q+l+x]s+'\
        -e\[pq]sq -e0l+x
done

... para 3 contagens de 3mil valores de uma peça, e os resultados foram:

18075000000
4.00s user 0.04s system 72% cpu 5.549 total
18075000000
2.82s user 0.01s system 99% cpu 2.831 total
18075000000
2.67s user 0.01s system 99% cpu 2.680 total
    
por 10.07.2015 / 20:41
1

Usando um analisador de XML, como XMLStarlet , com o arquivo fornecido na pergunta:

$ xml sel -t -m '//Summary' -v '@failed+@notExecuted+@timeout+@pass' -nl file.xml
18075

Se o nó Summary for encontrado em vários locais, haverá uma linha de saída para cada nó.

Em alguns sistemas, o XMLStarlet é instalado como xmlstarlet em vez de xml .

    
por 30.03.2018 / 12:22
0

Uma variante da de @ikrabbe. Pense que isso deve funcionar: echo '<Summary failed="10" notExecuted="0" timeout="0" pass="18065" />'| sed -e 's/[^0-9 ]//g' | cut -d" " -f2,3,5 | tr " " "+" | bc Os campos começam com 2, pois há um espaço na primeira posição.

    
por 10.07.2015 / 19:38
0

Com base na resposta do ikrabbe.

echo $(($(echo '<Summary failed="10" notExecuted="0" timeout="0" pass="18065" />' | sed -e 's/[^0-9 ]//g' -e 's/ /+/g' -e 's/[+]*$//')))

dá "18075"

Explicação do código:

  • Eco o texto e, em seguida, canalize-o para sed para analisar usando 3 sed declarações.
  • 1º sed-statement - Remove todos, exceto números e espaços (" 10 0 0 18065 ")
  • 2nd sed-statement - espaços de troca com adição sinais ("+ 10 + 0 + 0 + 18065 +")
  • 3a declaração sed - Remover trailing sinais de adição ("+ 10 + 0 + 0 + 18065"). A calculadora bash embutida fica com raiva se você der um cálculo que termine com um operador)

Tudo é então agrupado nessa função bash:

$(( ))

Que calcula o que está dentro dele e, portanto, fornece a resposta em uma única linha de código.

Esta é uma versão de duas linhas. Basta filmar a string "input" com as linhas que você deseja processar.

input="<Summary failed="10" notExecuted="0" timeout="0" pass="18065" />"
echo $(($(echo $input | sed -e 's/[^0-9 ]//g' -e 's/ /+/g' -e 's/[+]*$//')))
    
por 30.03.2018 / 12:11