Comando più breve per calcolare la somma di una colonna di output su Unix?
Domanda
Sono sicuro che esiste un modo semplice e veloce per calcolare la somma di una colonna di valori sui sistemi Unix (usando qualcosa come awk
o xargs
forse), ma scrivere uno script di shell per analizzare le righe riga per riga è l'unica cosa che viene in mente al momento.
Ad esempio, qual è il modo più semplice per modificare il comando seguente per calcolare e visualizzare il totale per la colonna 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
Soluzione
ipcs -mb | tail +4 | awk '{ sum += $7 } END { print sum }'
O senza coda:
ipcs -mb | awk 'NR > 3 { sum += $7 } END { print sum }'
Usare awk con bc per ottenere risultati lunghi arbitrari (crediti per Jouni K.
):
ipcs -mb | awk 'NR > 3 { print $7 }' | paste -sd+ | bc
Altri suggerimenti
Vorrei provare a costruire una stringa di calcolo e inviarla a bc come segue:
- grep le righe che contengono i numeri
- sed elimina tutti i caratteri prima (e dopo) il numero su ciascuna riga
- xargs il risultato (per ottenere una stringa di numeri separati da spazi)
- tr rispondi agli spazi vuoti con caratteri "+"
- buon appetito bc !
ipcs -mb | grep -w '^ m' | sed 's /^.* \ s //' | xargs | tr '' + | bc
Sembra che questo sia leggermente più lungo della soluzione awk , ma per tutti coloro che non sono in grado di leggere (e capire) lo strano codice awk questo potrebbe essere più facile da capire ... :-)
Se bc non è installato, è possibile utilizzare le doppie parentesi nel passaggio 5 sopra per calcolare il risultato:
-
echo $ (($ (ipcs -mb | grep -w '^ m' | sed 's /^.* \ s //' | xargs | tr '' +)))
o -
SUM = $ (($ (ipcs -mb | grep -w '^ m' | sed 's /^.* \ s //' | xargs | tr '' +)))
o -
((SUM = $ (ipcs -mb | grep -w '^ m' | sed 's /^.* \ s //' | xargs | tr '' +)))
La spaziatura dopo e prima delle doppie parentesi è facoltativa.
Ho uno script di utilità che aggiunge semplicemente tutte colonne. Di solito è abbastanza facile prendere quello che vuoi dall'output di una riga. Come bonus, vengono riconosciuti alcuni suffissi SI.
#!/usr/bin/awk -f
# Sum up numerical values by column (white-space separated)
#
# Usage: $ head /etc/passwd | addcol -F:
0 0 45 39 0 0 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 "";
}
Esempio con separatore di campo personalizzato:
<*>Soluzione 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 maggior parte delle distribuzioni Linux hanno Python.
Se si desidera elaborare stdin come parte di una pipeline, utilizzare
import sys
total = 0
for line in sys.stdin:
...etc...
Se vuoi assumere che ci siano sempre 3 righe di intestazione:
import sys
total = 0
for line in sys.stdin.readlines()[3:]:
total += int(line.split()[6])
print total
One-liner:
import sys; print sum( [int(line.split()[6]) for line in sys.stdin.splitlines()[3:]] )
So che questa domanda è in qualche modo datata, ma non riesco a vedere " my " rispondo qui, così ho deciso di pubblicare comunque. Vorrei andare con una combinazione di
- tail (per ottenere le linee di cui hai bisogno)
- tr (per restringere più spazi consequenziali a uno)
- taglia (per ottenere solo la colonna necessaria)
- incolla (per concatenare ogni riga con un segno
+
) - bc (per eseguire il calcolo effettivo)
ipcs
non fornisce un output sul mio sistema, quindi lo dimostrerò semplicemente con 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
So che fare questo particolare calcolo sul mio sistema non ha davvero senso, ma mostra il concetto.
Tutti i pezzi di questa soluzione sono stati mostrati nelle altre risposte, ma mai in quella combinazione.
Puoi iniziare eseguendo i dati tramite cut
, che almeno taglierebbe le colonne verso il basso.
Dovresti quindi essere in grado di reindirizzarlo in grep
, eliminando i caratteri non numerici.
Allora ... beh, allora non sono sicuro. Potrebbe essere possibile reindirizzarlo a bc
. In caso contrario, potrebbe sicuramente essere passato a uno script di shell per aggiungere ogni elemento.
Se hai usato tr
per cambiare le nuove righe ( \ n
) in spazi (
), e lo hai instradato attraverso xargs nel tuo script che loop fino a quando non ci sono più input, aggiungendo ognuno, potresti avere una risposta.
Quindi, qualcosa di simile al seguente:
cat <whatever> | cut -d'\t` -f7 | grep -v <appropriate-character-class> | tr '\n' ' ' | xargs script-that-adds-arguments
Potrei avere le bandiere cut
leggermente sbagliate, ma man
è tuo amico :)
Puoi cercarlo in qualsiasi riferimento awk online:
ipcs | awk '
BEGIN { sum = 0 }
/0x000000/ { sum = sum + $2 }
END {print sum}'
Grazie per il one-liner Python sopra !. Mi ha aiutato a controllare facilmente lo spazio utilizzato sul mio disco. Ecco una shell mista / Python one-liner, che fa questo - conta lo spazio utilizzato sul dispositivo / dev / sda in megabyte. Mi ci è voluto del tempo prima che lo scoprissi, quindi forse qualcuno lo trova utile anche.
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()])"
o più shell Python / less:
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])"
Grazie ancora!
Per sommare i valori in una colonna puoi usare il datamash GNU. Dato che le prime quattro righe non contengono valori che desideri riassumere, le rimuoviamo con tail +4
.
ipcs -mb | tail +4 | datamash -W sum 7
L'opzione -W
imposta il delimitatore di campo su (eventualmente più) spazi bianchi.
Se hai specifiche, più colonne che vuoi sommare, puoi usare:
input_command | awk '{s1+=$1;s2+=$2;s3+=$3;s4+=$4;s5+=$5}END{print s1,s2,s3,s4,s5}'
che funzionerà se vuoi sommare le colonne 1–5.