# Bash Scripting

## koma

Ciao ragazzi  :Smile:  mi rivolgo a voi perchè so che siete i migliori.

Vi riporto un prblema che devo risolvere entro stasera e sono 3 giorni che mi ci spacco la testa.

su un file di log di nmila righe, io devo recuperare solo le righe riguardanti gli ultimi 60 minuti.

esempio

Se lancio lo script di grep "orario", alle 17:53, devo avere tutte le righe create dalle 16:53 ad ora.

Tenendo presente che,

```
38752 07/02/09 11:23:58  nsrd rd=bostn03qd:/dev/rmt/STK0.4.10.4 Verify label operation in progress

38752 07/02/09 11:24:01  nsrd rd=bostn03qd:/dev/rmt/STK0.4.10.4 Mount operation in progress

42504 07/02/09 11:24:01  nsrd media event cleared: confirmed mount of Q06894 on rd=bostn03qd:/dev/rmt/STK0.4.10.4

12361 07/02/09 11:24:06  nsrd [Jukebox `CRMF01', operation # 2322]. Finished with status: succeeded

38730 07/02/09 11:24:15  nsrd cloning session:20 of 38 save set(s) reading from Q06894 766 KB of 67 GB

```

per esempio non è detto che abbia  una riga con l'intestazione "16:53" e magari  in quell'ora non è successo niente.

avevo pensato che la cosa  migliore è generare una lista con tutti i minuti  da quel momento ad ora

e andare a fare il grep dentro la lista di tutti quei minuti e inserirli dentro un altro file di appoggio

```
16:53

16:54

16:55

16:56

16:57

...

17:52

17:53
```

aggiungendo anche la data odierna come grep.

Ma il problema è che se lo script lo lancio alle 00:12

dovrò andare a cercare dalle 23:12 e se la data è a fine mese diventa ancora peggio  :Neutral: 

che fare?

mi appoggio a voi :°

----------

## Kernel78

io quando devo fare cose simili mi scrivo degli script in ruby ma il tuo post mi ha fatto venire la curiosità di scoprire come si potesse fare la stessa cosa in bash e, difficile a crederlo, andando su google e cercando bash time difference trovi anche degli esempi  :Wink: 

----------

## koma

a dire il vero non torov proprio nulla e sono 3 giorni che cerco attivamente  :Neutral: 

----------

## lucapost

potrebbe esserti utile ordinare tutto in secondi con il comdando date:

```
 date --date '7/02/09 12:00:00' '+%s'; date '+%s'
```

in particolare il primo ti restituisce come output a che secondo ti trovi, alle ore 12:00:00 del 7 luglio 2009, a partire dal 01/01/1970.

mentre il secondo la stessa informazione al momento attuale.

un paio di sottrazioni ed il gioco e fatto!

----------

## koma

questo lo so e infatti lo faccio, il problema è che non è detto che se faccio

```
let ORADACUIPARTIRE=$ORAATTUALE - $SESSANTAMINUTI
```

(ovviamente le variabili sono a valore indicativo)

l' $ORADACUIPARTIRE esista nel log

potrebbe benissimo non essere successo nulla in quel minuto

percui dovrei controllare ogni minututo fino al momento attuale.

ma diventa un pochino troppo oneroso in termini di tempo e peso-macchina

questo "script" dovrà girare all'incirca ogni 30 secondi perchè sarà parte di una funzione usata da almenoa ltri 25 scripts.

----------

## lucapost

potresti fare il controllo a ritroso, cioè consideri l'ultima riga di log, verifichi se è entro l'ora, se affermativo la salvi. Prendi la penultima, verifichi ed eventualmente la salvi. ecc...

Precedi via così fino a quando incontri la prima riga antecedente l'ora. 

Non dovrebbe essere un problema...

----------

## Kernel78

 *koma wrote:*   

> a dire il vero non torov proprio nulla e sono 3 giorni che cerco attivamente 

 

o non ho capito nulla di cosa stai cercando

o google ti da risultati diversi dai miei

a me come primo risultato porta qui

*ATTENZIONE STO PER LANCIARMI IN VOLI PINDARICI*

Domanda: cosa ne penseresti, se possibile, di usare inotify e sqlite ?

Controlli il file con inotail (adoro inotify) e butti le righe nuove in un db sqlite e poi interroghi questo db con un where che tiri fuori solo l'ultima ora (a te decidere se cancellare il resto o meno).

----------

## djinnZ

Se non ho capito male il problema è senza soluzione o quanto meno non puoi permetterti con bash di gestire efficacemente la situazione quindi o cambi formato ai log o usi un programma compilato (o shc + preload in disco ram dei binari e delle librerie) per fare il tail del log.

Il problema principale credo che siano le prestazioni e l'idea di chiamare date migliaia di volte mi pare un tantino tragica; mettiamo che il file sia di 6000 righe e che date sia eseguito in 3 millisecondi (diciamo che è un valore plausibile) ogni volta che la funzione è richiamata rientrerebbe dopo non meno di 26 secondi.

Per me l'alternativa migliore è costruire un filtro (anche in bash) che cambi il formato della data in Unix Time Counter ed a quel punto ti vai a prendere tutti i log con timestamp >= della data attuale meno 3600.

mi permetto di sottolineare che date +%s (e basta) secondo le versioni e le impostazioni dell'ambiente tende a riportare il numero di secondi trascorsi dal 1/01/1970 sul fuso orario di greenwich mentre date --date '...' '+%s' dovrebbe riportare il numero di secondi sempre dall'1/01/1970 ma sul fuso orario attuale od anche viceversa, per questo nell'esempio riportato  da kernel78 si usa il parametro --utc, manco a farlo apposta tu provi a fare i raffronti con giusto un'ora e suppongo sia nella capitale di questa repubblica delle banane...  :Wink: 

Purtroppo sono fuori studio e non ho più altri linux a disposizione per provare o consultare la documentazione ma l'idea di gestire in alternativa il grep per giorni non è malvagia devi solo inserire le 13 (c'è anche l'anno bisestile) eccezioni per il giorno a fine mese e lavorare direttamente sugli ultimi due giorni, almeno riduci la mole dei dati da processare.

Infine potresti pensare di fare qualcosa usando logrotate (direttiva copy mi raccomando) ma non mi sono mai applicato a problemi del genere quindi non saprei dirti.

oppure potresti pensare di usare sqlite ma non tramite inotify bensì direttamente come filtro a syslog, a questo pu8nto estrapolarti tutti i log con data superiore a è banale.

----------

## oRDeX

Forse ho tralasciato mille cose...ma questo scriptino quì?

```
#!/bin/bash

T=$(date --date "$1" "+%s")

while read line; do

        SEC=$(date --date "$(echo $line | cut -d ' ' -f2,3)" "+%s")

        if [ $SEC -ge $T ]; then

                echo $line

        fi

done < $2

```

lo lanci come

```
# ./script "02/07/09 10:00" file_di_log
```

Ovviamente il primo argomento tra apici non è altro che l'istante di partenza della ricerca, il secondo è il file da ispezionare

----------

## Kernel78

 *djinnZ wrote:*   

> oppure potresti pensare di usare sqlite ma non tramite inotify bensì direttamente come filtro a syslog, a questo pu8nto estrapolarti tutti i log con data superiore a è banale.

 

non avevo nemmeno preso in considerazione l'idea che il log potesse essere generato da syslog, in tal caso questa proposta mi pare vincente

----------

## xdarma

Non credo di poterti essere d'aiuto ma per le date "antecedenti" puoi usare:

```
xdarma@skariolante ~ $ date --date="1 hour ago" "+%x %X"

07/02/09 21:28:57
```

oppure, circa, in secondi dall' 1/01/1970:

```
xdarma@skariolante ~ $ date --date="1 hour ago" +%s

1246562939
```

Se poi i log fossero direttamente in secondi dalla creazione del mondo (unix) credo ti basterebbe qualcosa tipo:

awk { if ( $1 >=  'date --date="...." ') print }

...buona fortuna  :-(

----------

## koma

```
#!/usr/local/bin/bash

export PATH="$PATH:/usr/local/coreutils/bin"

#07/02/09 12:35:57

ADESSO=$(cdate "+%m/%d/%y %H:%M")

UNORAFA=$(cdate -d "1 hour ago" "+%m/%d/%y %H:%M")

UNORAFA_S=$(cdate -d "1 hour ago" +%s)

echo $ADESSO

echo $UNORAFA

echo $UNORAFA_S

A="0"

while [ "$A" -lt "61" ] ; do

        echo $A

        QUACK=$(grep -n "$UNORAFA" daemon.log|tail -1)

        if [ "$QUACK" = "" ]; then

                let A=$A+1

                let UNORAFA_S=$UNORAFA_S+60

                UNORAFA=$(cdate -d "@$UNORAFA_S" "+%m/%d/%y %H:%M")

                echo devo provare $UNORAFA

        else

                echo TROVATO $UNORAFA

                echo $QUACK

                TOTAIL=$(echo $QUACK |awk -F\: '{print $1}')

                NUMLINES=$(wc -l daemon.log|awk '{print $1}')

                echo $TOTAIL

                echo $NUMLINES

                let TOTAIL=$NUMLINES-$TOTAIL

                tail -$TOTAIL daemon.log > ESTRATTO.txt

                A=61

        fi

done
```

 (cdate è il date delle coreutils cioè il date standard di linux)

----------

## Kernel78

a occhio dovrebbe funzionare ma gli fai fare un sacco di lavoro inutile rigreppando a ogni esecuzione tutto il log (e con log di nmila righe ...  :Confused:  )

usare syslog e sqlite è proprio fuori discussione ?

----------

## koma

lui cicla solo se no trova la sstringa cercata quindi se l'orario non compare passa all'orario successivo.

per 60 volte. (un'ora) avanzando di minuto in minuto

la possibilità che cicli per 60 volte è remota di media cicla al massimo 4 o 5 volte.

----------

## oRDeX

ma lo script che avevo fatto io era così brutto?   :Embarassed:  Con una singola passata del log ti sputa fuori le righe con "istante" maggiore o uguale a quello passato come parametro

----------

## Kernel78

 *koma wrote:*   

> lui cicla solo se no trova la sstringa cercata quindi se l'orario non compare passa all'orario successivo.
> 
> per 60 volte. (un'ora) avanzando di minuto in minuto
> 
> la possibilità che cicli per 60 volte è remota di media cicla al massimo 4 o 5 volte.

 

io non ho mai detto che faccia molti cicli.

È comprensibili che con un file di nmila righe la probabilità di trovare almeno una riga ogni 5 minuti sia decisamente alta.

Quello a cui mi riferivo io era il fatto che ogni volta che viene lanciato si fa il grep di TUTTO il file ALMENO una volta e come dici tu in media ogni esecuzione comporta 4 o 5 grep.

Ripeto: se possibile sarebbe più efficiente usare syslog e sqlite.

Nel caso in cui non fosse possibile mi viene in mente al volo una modifica: salvarsi in un file la riga corrispondente all'inizio dell'ora precedente così alla prossima esecuzione si recupererebbe da tale file la riga da cui iniziare a cercare scartando automaticamente tutto il resto del log.

----------

## MajinJoko

se il file di log è scritto da syslog potresti farglielo salvare direttamente in un database..

http://www.rsyslog.com/module-Static_Docs-view-f-rsyslog_mysql.html.phtml

----------

## Kernel78

 *MajinJoko wrote:*   

> se il file di log è scritto da syslog potresti farglielo salvare direttamente in un database..
> 
> http://www.rsyslog.com/module-Static_Docs-view-f-rsyslog_mysql.html.phtml

 

beh, quel link riporta le istruzioni per rsyslog (non so bene quanto vari da syslog o da syslog-ng) ma il concetto rimane valido, usare un db come backend per il demone dei log  :Wink: 

----------

## MajinJoko

 *Kernel78 wrote:*   

> beh, quel link riporta le istruzioni per rsyslog (non so bene quanto vari da syslog o da syslog-ng) ma il concetto rimane valido, usare un db come backend per il demone dei log 

 

Oops.. ecco cosa succede a fare le cose di fretta.

Il concetto è più che valido. Mi limitavo a proporre un metodo alternativo per salvare i log nel db. E se il logger di sistema permette di farlo, hai il lavoro già pronto. A patto che il log da consultare sia gestito dal logger di cui sopra..

----------

## Kernel78

 *MajinJoko wrote:*   

>  *Kernel78 wrote:*   beh, quel link riporta le istruzioni per rsyslog (non so bene quanto vari da syslog o da syslog-ng) ma il concetto rimane valido, usare un db come backend per il demone dei log  
> 
> Oops.. ecco cosa succede a fare le cose di fretta.
> 
> Il concetto è più che valido. Mi limitavo a proporre un metodo alternativo per salvare i log nel db. E se il logger di sistema permette di farlo, hai il lavoro già pronto. A patto che il log da consultare sia gestito dal logger di cui sopra..

 

Nel caso non fosse gestito dal logger si potrebbe sempre fare come suggerivo io prima, appoggiarsi su inotify (magari proprio con inotail) per rilevare i vari append e redirigerli verso un db ...

In ogni caso ho trovato una guida per gentoo per usare sqlite come backend per syslog-ng e mi sa che adesso la metto in pratica  :Very Happy: 

----------

## djinnZ

syslog-ng rispetto ad rsyslog (che è pensato proprio per riportare su una macchina remota) ha qualche problemino a gestire i tempi morti del database (ovvero usarlo per gestire un database con tutti i log su una macchina che lavora sul serio del sistema è improponibile) e quindi va bene solo per usare dei filtri o comunque andare a gestire una parte limitata dei log su database.

L'alternativa migliore dal punto di vista del carico resta cambiare formato a log tramite un filtro ed andare a gestire le date in formato uutc di modo che ti basti un grep ed un tail per risolvere il problema.

Quanto tempo macchina possa essere richiesto per il problema specifico lo sa solo koma e questo è un altro discorso.

Le prestazioni di uno script non sono determinate solo dalla sua complessità algoritmica ma anche e soprattutto dal numero di invocazioni a programmi esterni, ovvero è meglio uno script di 100 righe di codice che uno di 100 che invoca dieci volte un programma esterno (ram permettendo, ma su un server moderno non è un problema).

----------

## Kernel78

 *djinnZ wrote:*   

> syslog-ng rispetto ad rsyslog (che è pensato proprio per riportare su una macchina remota) ha qualche problemino a gestire i tempi morti del database (ovvero usarlo per gestire un database con tutti i log su una macchina che lavora sul serio del sistema è improponibile) e quindi va bene solo per usare dei filtri o comunque andare a gestire una parte limitata dei log su database.

 

ammetto di non essere esperto delle meccaniche interne dei vari logger ma da quanto mi pare di capire stai dicendo che in un sistema molto carico usare un db come backend per syslog-ng sarebbe sconsigliabile ma non capisco perchè ne cosa si rischia ...

in ogni caso io pensavo proprio di indirizzare verso il db solo questo log specifico che non dovrebbe comportare troppo carico (da quanto ci è stato detto si può intuire che viene scritta una riga di log ogni 4-5 minuti)

 *Quote:*   

> L'alternativa migliore dal punto di vista del carico resta cambiare formato a log tramite un filtro ed andare a gestire le date in formato uutc di modo che ti basti un grep ed un tail per risolvere il problema.
> 
> 

 

una volta che il log si trova in un db (magari con un indice sulla data) la ricerca è indubbiamente più efficiente li rispetto all'accesso sequenziale al file, purtroppo non ho le conoscenze per ipotizzare il carico aggiuntivo dato dalla ridirezione del log verso il db ma mi sembra strano che sia così gravoso ...

----------

## djinnZ

 *Kernel78 wrote:*   

> non capisco perchè ne cosa si rischia

 non rischi nulla a parte prestazioni indecenti, syslog-ng ha qualche problemino a gestire code troppo lunghe (se non mi sbaglio era tra i todo molto tempo fa) e si impunta bloccando l'output di tutti programmi collegati quindi, dato che l'aggiunta di un record al database è sicuramente più gravosa che un "append" su file di testo, è meglio evitare se hai un computer sotto forte carico di elaborazione e che produce molti log (la congiunzione implica che entrambe le condizioni non una sola delle due).

Per non dire dell'attività supplementare generata dal database locale in risposta all'inserimento dei log stessi che potrebbe creare dei loop se non correttamente gestita (ovvero mettere in modalità super verbose mysql in locale e poi ci spara sopra tutti i log, compresi quelli di mysql stesso).

Non è certo il caso in questione e, tanto per cambiare, hai confuso un commento teso a scongiurare l'uso improprio di un suggerimento con un appunto nei confronti dello stesso.

"la tua soluzione va bene ma attenzione a non cercare di andare oltre mettendo tutto su database o tentando di fare il logging di qualcosa che potrebbe sparare milioni di righe di errore in un sol colpo su una macchia lenta" è più comprensibile?

----------

## xdarma

A scopo "didattico" ho provare a fare uno script riciclando quello di koma e quello di oRDeX

```

#!/usr/local/bin/bash

export PATH="$PATH:/usr/local/coreutils/bin"

UNORAFA_S=$(cdate -d "1 hour ago" +%s)

NUMLINES=$(wc -l daemon.log | awk '{print $1}' )

CUR_LINE=$(sed -n "$NUMLINES"p daemon.log)

CUR_TIME_S=$(cdate -d "($CUR_LINE | awk '{print $2 $3}' )" +%s)

while [ "$CUR_TIME_S" -ge "$UNORAFA_S" ]

do

      echo $CUR_LINE >> lastlog.txt

      let NUMLINES=$NUMLINES - 1

      CUR_LINE=$(sed -n "$NUMLINES"p daemon.log)

      CUR_TIME_S=$(cdate -d "($CUR_LINE | awk '{print $2 $3}' )" +%s)

done

```

Chiaramente è migliorabile ma soprattutto: a me NON funziona.

Non voglio responsabilità per ore di lavoro perse o, peggio, dati persi  ;-)

Ciao.

----------

## Kernel78

Se si usasse sqlite come backend per syslog-ng (come spiegato nel link che ho postato sopra) allora per ottenere i messaggi dell'ultima ora basterebbe

```
sqlite3 system.db "select * from logs where time>=time('now','-1 hour','localtime') and date=date('now','localtime');"
```

----------

