web-dev-qa-db-fra.com

La commande la plus courte pour calculer la somme d'une sortie colonne sur Unix?

Je suis sûr qu'il existe un moyen rapide et facile de calculer la somme d'une colonne de valeurs sur les systèmes Unix (en utilisant quelque chose comme awk ou xargs peut-être), mais écrire un script Shell pour analyser les lignes ligne par ligne est la seule chose que vient à l'esprit pour le moment.

Par exemple, quel est le moyen le plus simple de modifier la commande ci-dessous pour calculer et afficher le total de la colonne SEGSZ (70300)?

ipcs -mb | head -6
IPC status from /dev/kmem as of Mon Nov 17 08:58:17 2008
T         ID     KEY        MODE        OWNER     GROUP      SEGSZ
Shared Memory:
m          0 0x411c322e --rw-rw-rw-      root      root        348
m          1 0x4e0c0002 --rw-rw-rw-      root      root      61760
m          2 0x412013f5 --rw-rw-rw-      root      root       8192
47
An̲̳̳drew
ipcs -mb | tail +4 | awk '{ sum += $7 } END { print sum }'

Ou sans queue:

ipcs -mb | awk 'NR > 3 { sum += $7 } END { print sum }'

Utiliser awk avec bc pour obtenir des résultats arbitrairement longs (crédits en Jouni K.):

ipcs -mb | awk 'NR > 3 { print $7 }' | paste -sd+ | bc
82

Je voudrais essayer de construire une chaîne de calcul et le nourrir à bc comme suit:

  1. grep les lignes qui contiennent les nombres
  2. sed loin tous les caractères avant (et après) le nombre sur chaque ligne
  3. xargs le résultat (pour obtenir une chaîne de nombres séparés par des blancs)
  4. tr corrige les blancs en caractères '+'
  5. bon appétit bc !

ipcs -mb | grep -w '^m ' | sed 's/^.*\s//' | xargs | tr ' ' + | bc

On dirait que cela est légèrement plus long que la solution awk , mais pour tous ceux qui ne peuvent pas lire (et comprendre) le code impair awk , cela peut être plus facile à saisir ... :-)

Si bc n'est pas installé, vous pouvez utiliser les doubles parenthèses à l'étape 5 ci-dessus pour calculer le résultat:

  • echo $(( $(ipcs -mb | grep -w '^m ' | sed 's/^.*\s//' | xargs | tr ' ' +) )) ou
  • SUM=$(( $(ipcs -mb | grep -w '^m ' | sed 's/^.*\s//' | xargs | tr ' ' +) )) ou
  • (( SUM=$(ipcs -mb | grep -w '^m ' | sed 's/^.*\s//' | xargs | tr ' ' +) ))

L'espacement après et avant les doubles parenthèses est facultatif.

13
Peterino

J'ai un script utilitaire qui ajoute simplement tout colonnes. Il est généralement assez facile de saisir celui que vous voulez dans la sortie sur une ligne. En prime, certains suffixes SI sont reconnus.

#!/usr/bin/awk -f
# Sum up numerical values by column (white-space separated)
#
# Usage:  $0 [file ...]
#
# stern, 1999-2005

{
    for(i = 1; i <= NF; ++i) {
        scale = 1
        if ($i ~ /[kK]$/) { scale = 1000 }
        if ($i ~ /[mM]$/) { scale = 1000*1000 }
        if ($i ~ /[gG]$/) { scale = 1000*1000*1000 }
        col[i] += scale * $i;
    }
    if (NF > maxnf) maxnf = NF;
}

END {
    for(i = 1; i <= maxnf; ++i) { printf " %.10g", col[i] }
    print "";
}

Exemple avec un séparateur de champ personnalisé:

$ head /etc/passwd | addcol -F:
0 0 45 39 0 0 0
4
user61853

Solution Python

#!/usr/bin/env python
text= file("the_file","r")
total= 0
for line in text:
    data = line.split()
    if data[0] in ('T', 'Shared', 'IPC'): continue
    print line
    segsize= int(data[6])
    total += segsize
print total

La plupart des distributions Linux ont Python.

Si vous voulez traiter stdin dans le cadre d’une pipline, utilisez

import sys
total = 0
for line in sys.stdin:
   ...etc...

Si vous voulez supposer qu'il y a toujours 3 lignes d'en-tête:

import sys
total = 0
for line in sys.stdin.readlines()[3:]:
    total += int(line.split()[6])
print total

Bon mot:

import sys; print sum( [int(line.split()[6]) for line in sys.stdin.splitlines()[3:]] )
2
S.Lott

Je sais que cette question est un peu dépassée, mais je ne vois pas "ma" réponse ici, alors j'ai décidé de poster néanmoins. J'irais avec une combinaison de

  • tail (pour obtenir les lignes dont vous avez besoin)
  • tr (pour réduire plusieurs espaces consécutifs à un seul)
  • couper (pour obtenir uniquement la colonne nécessaire)
  • coller (pour concaténer chaque ligne avec un signe +)
  • bc (pour faire le calcul réel)

ipcs ne donne pas de sortie sur mon système, je vais donc le démo avec df:

# df
Filesystem     1K-blocks    Used Available Use% Mounted on
rootfs          33027952 4037420  27312812  13% /
udev               10240       0     10240   0% /dev
tmpfs             102108     108    102000   1% /run
/dev/xvda1      33027952 4037420  27312812  13% /
tmpfs               5120       0      5120   0% /run/lock
tmpfs             204200       0    204200   0% /run/shm
/dev/xvda1      33027952 4037420  27312812  13% /var/www/clients/client1/web1/log
/dev/xvda1      33027952 4037420  27312812  13% /var/www/clients/client1/web2/log
/dev/xvda1      33027952 4037420  27312812  13% /var/www/clients/client1/web3/log
/dev/xvda1      33027952 4037420  27312812  13% /var/www/clients/client1/web4/log
/dev/xvda1      33027952 4037420  27312812  13% /var/www/clients/client2/web5/log
/dev/xvda1      33027952 4037420  27312812  13% /var/www/clients/client2/web6/log
# df | tail -n +2 | tr -s ' ' | cut -d ' ' -f 2 | paste -s -d+ | bc
264545284

Je sais que faire ce calcul particulier sur mon système n'a pas vraiment de sens, mais cela montre le concept.

Toutes les pièces de cette solution ont été montrées dans les autres réponses, mais jamais dans cette combinaison.

2
Alexander Stumpf

Vous pouvez commencer par exécuter les données via cut - ce qui réduirait au moins les colonnes. 

Vous devriez alors pouvoir canaliser cela dans grep, en éliminant les non-numériques.

Alors ... eh bien, alors je ne suis pas sûr. Il serait peut-être possible de diriger cela vers bc. Sinon, il pourrait certainement être confié à un script Shell pour ajouter chaque élément.

Si vous avez utilisé tr pour modifier les nouvelles lignes (\n) en espaces () et que vous avez transmis cela par le biais de xargs dans votre script qui boucle jusqu'à ce qu'il n'y ait plus d'entrées, en ajoutant chacune, vous pouvez obtenir une réponse.

Donc, quelque chose qui ressemble à ce qui suit:

cat <whatever> | cut -d'\t` -f7 | grep -v <appropriate-character-class> | tr '\n' ' ' | xargs script-that-adds-arguments

Les drapeaux cut sont peut-être un peu incorrects - mais man est votre ami :)

1
warren

Vous pouvez le rechercher dans n'importe quelle référence awk en ligne:

ipcs | awk '
BEGIN { sum = 0 }
/0x000000/ { sum = sum + $2 }
END {print sum}'
1
florin

Merci pour le one-liner Python ci-dessus!. Cela m'a aidé à vérifier facilement l'espace utilisé sur mon disque ..__ Voici un one-liner mixte Shell/Python, qui effectue cette opération - compte l'espace utilisé sur le périphérique/dev/sda en mégaoctets. Il m'a fallu un certain temps avant de le découvrir, alors peut-être que quelqu'un trouve cela utile aussi.

df -h -B 1M | grep dev/sda | tr -s ' '| cut -d' ' -f3 |python -c "import sys; print sum([int(num) for num in sys.stdin.readlines()])"

ou plus Python/moins Shell:

 df -h -B 1M | python -c "import sys; print sum([int(l.split()[2]) for l in sys.stdin.readlines() if '/dev/sda' in l])"

Merci encore!

0
Andrzej

Pour additionner des valeurs dans une colonne, vous pouvez utiliser GNU datamash. Comme les quatre premières lignes ne contiennent pas de valeurs que vous souhaitez résumer, nous les supprimons avec tail +4.

ipcs -mb  | tail +4 | datamash -W sum 7

L'option -W définit le délimiteur de champ sur (éventuellement plusieurs) espaces.

0
Socowi

Si vous souhaitez additionner plusieurs colonnes spécifiques, vous pouvez utiliser:

input_command | awk '{s1+=$1;s2+=$2;s3+=$3;s4+=$4;s5+=$5}END{print s1,s2,s3,s4,s5}'

ce qui fonctionnera si vous voulez additionner les colonnes 1 à 5.

0
gerrit