Archivi tag: query

Script bash per automatizzare le query su MySQL

Scenario

Supponiamo che il vostro DBMS (MySQL) stia gestendo più database e che sia necessario effettuare delle query  “a tappeto”, ovvero su tutte le tabelle relative a ciascuno di essi (cercando, ad esempio, un determinato record oppure un pattern specifico).

bashEseguire tale operazione “a mano” può essere tedioso e soprattutto controproducente in termini di tempo. Come fare quindi? Semplice, realizzare uno scrip bash in grado di automatizzare tale attività.

Ecco il codice:

#!/bin/bash

mysql -u utente -ppassword -e 'show databases' | grep -v Database | grep -v information_schema | grep -v mysql > databases

while read line
do

        command="mysql -u utente -ppassword -e 'use $line; show tables'"
        eval $command | grep -v "Tables_in_*" > tables_$line

done < databases

num_databases=`cat databases | wc -l`

ls | grep "tables_*" > table

num_tables=`cat table | wc -l`

if [ $num_databases -eq $num_tables ];then

        for i in {1..19}
        do
                database=`cat databases | sed -n "$i p"`
                echo ".......tables for $database......."
                cat tables_"$database" | while read line
                do
                        echo $line
                        command="mysql -u utente -ppassword -e 'use $database; select * from $line LIMIT 1'"
                        echo "....Executing $command...." >> result
                        eval "$command" | tee -a result
                        echo "....End executing $command ...." >> result

                done
                echo ".......end tables for $database......."
        done

fi

Per prima cosa identifico tutti i database definiti su MySQL, escludendo quelli di default (mysql ed information_schema), formattando opportunamente l’output ( grep -v Database) e salvando i risultati all’interno del file databases:

mysql -u utente -ppassword -e 'show databases' | grep -v Database | grep -v information_schema | grep -v mysql > databases

Successivamente listo le tabelle presenti in ciascun database mediante un ciclo while che legge, riga per riga, il contenuto del file databases e, per ognuna, esegue il comando show tables:

while read line
do

        command="mysql -u utente -ppassword -e 'use $line; show tables'"
        eval $command | grep -v "Tables_in_*" > tables_$line

done < databases

Da notare come la stringa $command venga dapprima definita in modo tale da contenere il valore della variabile $line e successivamente venga eseguita sottoforma di comando mediante la direttiva eval.

Verifico, quindi, che il numero dei file tables_$line sia uguale al numero di database (e che quindi vi sia la ragionevole certezza che per ciascun database sia stato individuato il nome delle tabelle di cui è composto):

num_databases=`cat databases | wc -l` ls | grep "tables_*" > table 

num_tables=`cat table | wc -l` 

if [ $num_databases -eq $num_tables ];then

Infine, per ciascuna tabella di ciascun database, lancio la query mysql -u utente -ppassword -e ‘use $database; select * from $line LIMIT 1’, definita come stringa ed eseguita mediante eval (come visto in precedenza):

for i in {1..19}
        do
                database=`cat databases | sed -n "$i p"`
                echo ".......tables for $database......."
                cat tables_"$database" | while read line
                do
                        echo $line
                        command="mysql -u utente -ppassword -e 'use $database; select * from $line LIMIT 1'"
                        echo "....Executing $command...." >> result
                        eval "$command" | tee -a result
                        echo "....End executing $command ...." >> result

                done
                echo ".......end tables for $database......."
        done

fi

Da notare come, nel mio caso, vi sia un totale di 19 database, per cui ho utilizzato un semplice ciclo for così definito:

for i in {1..19}

Nulla però vieta di usare come upper bound la variabile $num_databases o $num_tables.

Infine rendiamo eseguibile lo scrip (che ho denominato, per semplicità, my.sh):

[root@server ~]# chmod +x my.sh

ed eseguiamolo:

[root@server ~]# ./my.sh

E’ tutto, alla prossima.

Logica di funzionamento del DNS (Domain Name System)

Prima di approfondire un argomento che avevo già trattato tempo addietro, ovvero il DNS cache poisoning (vedi qui per ulteriori dettagli), è utile illustrare la logica di funzionamento su cui si basa il Domain Name System.

Esistono, in soldoni, 2 tipologie di query DNS:
1) ricorsive;
2) iterative.

Supponiamo che il client voglia risolvere l’FQDN www.ciao.com

Nel primo caso il client contatterà il proprio resolver (ad esempio il nameserver dell’ISP) sottoponendogli la query. Se la risposta non è contenuta all’interno
della sua cache, esso effettuerà una query ai root nameserver. I root nameserver forniranno una risposta di tipo referral (con ANSWER SECTION vuota ed AUTHORITY SECTION non vuota), contenente una lista di nameserver da contattare per il top level domain (in gergo TLD) .com.
A questo punto il resolver contatterà uno dei nameserver autoritativi appena individuati, il quale fornirà una risposta di tipo referral indicando il nameserver autoritativo per il dominio cercato. Infine, quest’ultimo verrà interrogato e fornirà una risposta alla query (ANSWER SECTION non vuota) indicando l’indirizzo IP relativo al record A (www) ricercato (sempre se il suddetto record esiste).
La risposta, una volta giunta al resolver, verrà quindi girata al client.

recursiveDa notare che sia i root nameserver che i nameserver autoritativi per i TLD forniscono esclusivamente risposte di tipo referral, ergo funzionano solo
in modalità iterativa. Infatti, pensate a cosa accadrebbe se ciascun root nameserver interrogato (13 in tutto) iniziasse a fare query ricorsive per individuare
un determinato indirizzo IP… sarebbe una scelta davvero sconveniente (per usare un’espressione moderata) sia in termini di carico che in termini di latenza.

Inoltre, ciascun software che svolge funzioni di namserver utilizza degli algoritmi ben precisi per individuare il server a cui sottoporre la query (root, TLD o autoritativo che sia).
Nella fattispecie, bind usa la tecnica dell’RTT (Round Trip Time), grazie alla quale dovrebbe riuscire ad individuare il namserver più peformante (che coincide con quello che ha risposto più velocemente alla query).

Per ciò che concerne le query iterative, esse prevedono (in generale) il coinvolgimento diretto del client. Per prima cosa esso sottoporrà la query al proprio resolver, che gli risponderà con la lista dei root nameserver (risposta di tipo referral). Il root nameserver successivamente contattato dal client, gli fornirà una risposta di tipo referral indicando una lista di namserver autoritativi per il TLD .com. Dopodichè il client sottoporrà la query ad uno dei predetti nameserver, il quale gli fornirà come risposta una lista dei namserver autoritativi per il dominio ciao.com.
Infine, il client contatterà uno dei nameserver autoritativi per ciao.com che gli girerà la risposta contenente l’indirizzo IP per www.ciao.com (sempre se il record esiste).

Un esempio di query iterativa più essere facilmente ricavato utilizzando dig con l’opzione +trace, ad esempio:

[root@linuxbox ~]# dig +trace www.ciao.com

; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.37.rc1.el6_7.2 <<>> +trace www.ciao.com
;; global options: +cmd
.                       440639  IN      NS      b.root-servers.net.
.                       440639  IN      NS      m.root-servers.net.
.                       440639  IN      NS      h.root-servers.net.
.                       440639  IN      NS      k.root-servers.net.
.                       440639  IN      NS      a.root-servers.net.
.                       440639  IN      NS      i.root-servers.net.
.                       440639  IN      NS      f.root-servers.net.
.                       440639  IN      NS      e.root-servers.net.
.                       440639  IN      NS      j.root-servers.net.
.                       440639  IN      NS      d.root-servers.net.
.                       440639  IN      NS      l.root-servers.net.
.                       440639  IN      NS      g.root-servers.net.
.                       440639  IN      NS      c.root-servers.net.
;; Received 496 bytes from 192.168.1.1#53(192.168.1.1) in 38 ms

com.                    172800  IN      NS      a.gtld-servers.net.
com.                    172800  IN      NS      b.gtld-servers.net.
com.                    172800  IN      NS      c.gtld-servers.net.
com.                    172800  IN      NS      d.gtld-servers.net.
com.                    172800  IN      NS      e.gtld-servers.net.
com.                    172800  IN      NS      f.gtld-servers.net.
com.                    172800  IN      NS      g.gtld-servers.net.
com.                    172800  IN      NS      h.gtld-servers.net.
com.                    172800  IN      NS      i.gtld-servers.net.
com.                    172800  IN      NS      j.gtld-servers.net.
com.                    172800  IN      NS      k.gtld-servers.net.
com.                    172800  IN      NS      l.gtld-servers.net.
com.                    172800  IN      NS      m.gtld-servers.net.
;; Received 490 bytes from 192.203.230.10#53(192.203.230.10) in 4323 ms

ciao.com.               172800  IN      NS      dns2.progtech.net.
ciao.com.               172800  IN      NS      dns.progtech.net.
;; Received 111 bytes from 192.33.14.30#53(192.33.14.30) in 623 ms

www.ciao.com.           86400   IN      A       185.60.164.38
ciao.com.               86400   IN      NS      dns2.progtech.net.
ciao.com.               86400   IN      NS      dns.progtech.net.
;; Received 127 bytes from 80.190.149.57#53(80.190.149.57) in 82 ms

Per completezza, riporto il contenuto di una risposta ad una quey DNS, con le relative sezioni (QUERY, ANSWER, AUTHORITY, ADDITIONAL)

[root@linuxbox ~]# dig libero.it

; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.37.rc1.el6_7.2 <<>> libero.it
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 23298
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 2

;; QUESTION SECTION:
;libero.it.                     IN      A

;; ANSWER SECTION:
libero.it.              300     IN      A       37.9.239.32

;; AUTHORITY SECTION:
libero.it.              10799   IN      NS      n2.libero.it.
libero.it.              10799   IN      NS      n1.libero.it.

;; ADDITIONAL SECTION:
n2.libero.it.           10799   IN      A       156.154.67.47
n1.libero.it.           10799   IN      A       156.154.66.47

;; Query time: 256 msec
;; SERVER: 10.1.1.1#53(10.1.1.1)
;; WHEN: Tue May 31 09:29:06 2016
;; MSG SIZE  rcvd: 109

essa ci tornerà utile quando vedremo più nel dettaglio la logica su cui si basa il DNS cache poisoning.

Firebird: cancellazione dei record duplicati

Potrebbe accadere che all’interno di un database siano presenti una o più tabelle non dotate di chiavi primarie. Questo significa, in soldoni, che è molto alto il rischio di ridondanza (aka duplicati) e quindi di inconsistenza.

Fortunatamente, Firebird ci mette a disposizione un metodo abbastanza semplice per la cancellazione dei duplicati. Esso si basa sull’uso di uno specifico campo (RDB$DB_KEY), il cui valore univoco viene assegnato automaticamente a ciascun record durante la fase di inserimento nel DB.

firebirdlogo.jpg

Inutile dire che preferisco di gran lunga l’uso del suddetto metodo rispetto a soluzioni alternative, in quanto esso si basa esclusivamente su di una query secca e quindi minimizza il rischio di errore. Infatti, se ad esempio avessi utilizzato le cosiddette prepared statements avvelendomi di un qualche linguaggio di alto livello che le supporta (come Java), avrei dovuto scrivere N righe di codice, incrementando la possibilità di incappare in bachi (ecco perchè preferisco sempre mettere in pratica il motto less is more).

Di seguito la query che ho utilizzato:

DELETE FROM TABLE1 t1
WHERE EXISTS (
SELECT 1 FROM TABLE1 t2
WHERE t1.COL1 = t2.COL1 and t1.COL2 = t2.COL2
AND t1.RDB$DB_KEY < t2.RDB$DB_KEY
);

Dove t1 e t2 sono gli alias associati alla medesima tabella, mentre COL1 e COL2 sono i campi che la dovrebbero identificare univocamente (la probabile chiave primaria).

Ora, per avere la certezza che tale query non cancelli più record del previsto, si dovrebbero coinvolgere tutti i campi della tabella. Però, quando il numero dei campi è piuttosto elevato (>15), risulta più conveniente identificare la presunta chiave primaria ed utilizzare solo i campi che concorrono a formarla.

In questo caso, prima di effettuare il DELETE vero e proprio è opportuno fare delle query di verifica.

Ad esempio, dapprima si contano tutti i record della tabella:

SELECT count(*) FROM TABLE1;

e successivamente si individuano i record duplicati:

SELECT COL1, COL2, count(*) FROM TABLE1 GROUP BY COL1, COL2 HAVING count(*) >= 2;

A questo punto il numero dei record a cancellazione avvenuta dovrebbe essere pari al numero restituito dalla prima query meno il numero restituito dalla seconda query.

Per avere la certezza che il numero di record rimossi sia identico alla risultante dell’operazione di differenza effettuata in precedenza si può eseguire la query:

SELECT count(*) FROM TABLE1 t1
WHERE EXISTS (
SELECT 1 FROM TABLE1 t2
WHERE t1.COL1 = t2.COL1 and t1.COL2 = t2.COL2
AND t1.RDB$DB_KEY < t2.RDB$DB_KEY
);

Se il numero di record restituiti è identico al numero dato dalla differenza, abbiamo una buona probabilità che la chiave primaria individuata sia quella giusta (almeno dal punto di vista quantitativo).

Alla prossima.

Oracle: modificare la password utente e rimuovere la scadenza delle password

Oracle è di gran lunga il DBMS più affidabile e performante che esiste in circolazione. Detto ciò, qualche giorno fa mi si è presentato un problema su alcune macchine virtuali: le password per accedere ai DB erano scadute o stavano per scadere. Cosa fare dunque?

Per prima cosa ho lanciato la console VMWare e successivamente ho aperto il tool SQL*Plus Instant Client.

oracle.gif

A questo punto mi sono loggato come utente system, digitando il comando:

connect system/password

Per cambiare la password ho invece utilizzato la query:

ALTER USER system IDENTIFIED BY nuovapassword;

Questo output:

User altered.

mi ha confermato che la password è stata effettivamente cambiata.

Ora, per quanto riguarda invece la policy di scadenza delle password, posso dirvi che disabilitarla è controproducente dal punto di vista della sicurezza. Se, però, le macchine utilizzate sono di test e soprattutto NON sono esposte all’esterno, la si può disabilitare tranquillamente digitando:

ALTER PROFILE DEFAULT LIMIT

PASSWORD_LIFE_TIME UNLIMITED;

Alla prossima.

Script bash per l’invio di notifiche tramite email

Poco tempo fa ho deciso di creare un piccolo scrip in grado di interrogare un database, salvare temporaneamente in un file la risposta alla query ed inviare una notifica via email all’indirizzo di posta specificato. Nella fattispecie, tale scrip ha come scopo principale quello di notificare le scadenze da saldare e quelle non rispettate.

bash,mail,email,mysql,query,script,scadenze,notifiche

Ma bando alle ciance ed ecco lo scrip:

#!/bin/bash

destinatario=vostro.indirizzo@email.it

logfile=/var/log/notifier.log

ROOT_UID=0

data=`date +%s` #lavoro con i timestamp per poter confrontare le date

if [ "$UID" -ne "$ROOT_UID" ];then

        ERRORE1="Errore 1: Devi essere root per eseguire lo scrip"
        echo $ERRORE1
        echo "$(date) $ERRORE1" >> $logfile

        exit 1

fi

mysql -u root -ppassword -D nomedb -e "SELECT * FROM scadenza;" >> scadenze

echo "Hai le seguenti scadenze da saldare: " > scadenze_imminenti;

echo "Hai le seguenti scadenze non saldate: " > scadenze_nonrispettate;

while read line

do

        scad=`cat scadenze | grep - | awk '{print $2}'` #per individuare la data di scadenza.

        for info in $scad

        do

                datascad=`date --utc --date "$info" +%s`  #uso i timestamp per confrontare le date

                if [ "$data" -le "$datascad" ];then

                        if [[ $line =~ $info --;then

                                echo $line >> scadenze_imminenti

                        fi

                fi

                if [ "$data" -gt "$datascad" ];then

                        if [[ $line =~ $info --;then

                                echo $line >> scadenze_nonrispettate

                        fi
                fi

       done

done < scadenze

if grep -q "-" scadenze_imminenti ;then

uniq scadenze_imminenti | mail -iv -s "Scadenze da saldare" $destinatario;

fi

if grep -q "-" scadenze_nonrispettate ;then

uniq scadenze_nonrispettate | mail -iv -s "Scadenze non saldate" $destinatario;

fi

rm scadenze

rm scadenze_imminenti

rm scadenze_nonrispettate

exit 0

Come è facile notare, viene fatto un confronto tra la data corrente e quella di ciascuna scadenza (sottoforma di UNIX timestamp). Nel caso in cui la data corrente sia antecedente o uguale a quelle delle scadenze, esse verranno salvate nel file scadenze_imminenti, il cui contenuto verrà successivamente inserito nel corpo di una email e spedito al destinatario.

Invece, se la data attuale è maggiore di quella della scadenza, quest’ultima verrà salvata nel file scadenze_nonrispettate, il cui contenuto verrà recapitato nella casella email del destinatario.

Per qualsiasi chiarimento contattatemi.

Bye.