shell tools – parametric commands with xargs

Sometime is necessary to execute commands several times changing only some parameters.

In most of the cases you can use a for or a while loop:

for FILE in $(ls -1); do echo ${FILE}; done 

This form isn’t particularly robust because of the unmanaged spaces in the file names but anyway it is really common because of its simplicity.

Another option is to use the -exec option of the find command.

find . -type f -exec echo '{}' \;

This form is a really powerful way of looping but can be used only on list of files

In some cases neither of the previous forms can be used. Let see an example: you have to read a file (ei: an apache access log) elaborate the row in some way and execute a command for each row. In a case like this find isn’t an option and the for loop is inconvenient at the best because you have to put much more logic in the variable definition then in the true loop. Moreover if you have a big file to manage you couldn’t have enough memory.

It would be much better to elaborate on line at time: xargs is the tool that permits us to proceed in this way.

Let’s look at an example: suppose I want to loop through a log file and search for the served pages that doesn’t contains a word. I’ll have to read the file, probably select some lines, extract the information required to execute a curl, execute the curl and examine the result.
I can do this in a single line using xargs without involving intermediate files.

cat  20150608_access.log |grep "linuxandcompany" |awk -F"GET " '{print $2}' |awk -F" HTTP/" '{print $1}' | xargs -I {} sh -c 'curl -s "http://www.linuxandcompany.it{}" | grep "pippo" > /dev/null;  [ $? -ne 0 ] && echo "{}"'

The first part is really basic: the cat command is used to read through a log file. Then I use a filter (grep) to select some lines (the ones related to this website).

To extract the searched information (URI) I use the two times the awk command.

At this point I have a pipe with a list of URI. The function of xargs is just to get these values, substitute them in a parametrized command and execute the result.

In this example I have to execute several commands at each iteration. xargs doesn’t permit this but a simply trick will help us: we choose a shell as command and execute a script inside it.

Just to complete the description of the example the curl retrieves the web page from internet, the grep command checks for the searched value, and the result is thrown in the trash. This because I’m interested only in the return value of the grep command.

The return value of the grep is checked and if the word isn’t found the URI is written in the output.

backup e rsync

Avere una politica di backup che ci permetta di limitare i danni in caso di errori o problemi è una necessità che risale all’origine dell’informatica.

UNIX e Linux hanno ovviamente diversi tool che vengono in aiuto per questa necessità, e, almeno nel caso di una macchina singola, si possono implementare delle politiche asolutamente soddisfacenti con soli tool interni come cron ed rsync.

Come molti comandi della shell UNIX rsync è estremamente versatile. Il comando di base è:

rsync /path/to/source/file /path/to/destination

Come suggerisce il nome rsync non è un semplice tool per la copia dei file ma si occupa di sincronizzare due directory effettuando la copia dei soli file che lo richiedono.

Tendenzialmente rsync è utile soprattutto se si ha la necessità di copiare molti file e quindi sarà necessario utilizzare l’opzione -r

rsync -r /path/to/source/ /path/to/destination
rsync -r /path/to/source /path/to/destination

Ho riportato due comandi per sottolineare una particolarità di rsync. Se si copia una directory e si specifica / nel path della sorgente il contenuto della directory verrà copiato nella destinazione; se invece il path viene scritto senza / è la directory che viene copiata nella destinazione.

Rsync generalmente non ha quasi nessun output a video, il che è ottimo per script in crontab, se però si sta eseguendo il backup in real time si vorrà probabilmente un’indicazione di come sta procedendo l’attività. PEr questo si hanno due opzioni -v e –progress.

rsync generalmente controlla le dimensioni e la data di ultima modifica per capire se devono essere copiate. Sono però disponibili specifiche opzioni per fare controlli differenti. Ad esempio –size-only se si vole considerare solo le dimensioni del file o –checksum se si vuole un conformto più affidabile anche se più oneroso.

Le opzioni di questo comando sono svarite ma generalmente se si stanno effettuando dei backup -a farà si che vengano attivate le opzioni che più comunemente vengono utilizzate per questa attività.

Una mensione particolare merita l’opzione -b; in questa modalità rsync effettuerà un backup incrementale spostando i file rimossi dalla sorgente in un appositp path e scrivendo su un file tutte le operazioni effettuate. Si otterrà quindi una directory allineata a quella sorgente e si manterranno i file e le informazioni necessari per ricostruire la situazione al momento del backup precedente.

Un esempio di comando reale potrebbe quindi essere

rsync -avb --size-only --delete --log-file=/path/to/file_log/file.log --backup-dir=/path/to/directory_file_incrementali/ --progress /directory/source/ /directory/destination/

awk e linea di comando

La shell di UNIX/Linux è uno strumento potentissimo che permette di velocizzare ed automatizzare le più svariate operazione. Dato che in UNIX ogni cosa è un file, sono di fondamentale importanza quegli strumenti che permettono di analizzare e gestire i file.

Spessissimo i file con cui si ha a che fare nelle attività sistemistiche sono dei rudimentali database strutturati con i record in righe e i campi in colonne. In questa categoria rientrano ad esempio i file passwd e moltissimi file di log.

Per poter automatizzare operazioni su questo tipo di file è necessario uno strumento che dia accesso a specifici campi nei vari record. Come sempre in ambiente UNIX/Linux è possibile procedere in diversi modi ma uno strumento di sicuoro interesse è awk. Si tratta di uno di quei tool la cui conoscenza non dovrebbe mancare a nessun sistemista.

Nella sua forma più semplice awk accetta in stdin un file, e permette di estrarre dei campi da ogni riga.

Ed esempio se volessi estrarre gli utenti che hanno script schedulati nel seguente file crontab:

17 *	* * *	root    cd / && run-parts --report /etc/cron.hourly
25 6	* * *	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6	* * 7	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6	1 * *	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )

potrei utilizzare la seguente righa di comando:

cat /etc/crontab | awk '{print $6}'

all’interno di awk ogni campo, infatti, è contenuto in una variabile $n, dove n è il numero del campo ed i campi sono separati de default da spazi. L’intera riga è contenuta nella variabile $0. Ci sono molte altre variabili predefinite in awk e la lista completa è riportata nel suo manuale (man awk). Una menzione la merita $NR che contiene il numero della riga che si sta elaborando.

Non in tutti i file il separatore è lo spazio; l’opzione -F permette di scegliere un separatore differente.

awk può essere utilizzato come filtro sfruttando il costrutto if al suo interno:

Ad esempio se voglio sapere quali utenti non hanno una shell reale posso utilizzare:

cat /etc/passwd|awk -F: '{if ($7=="/bin/false") print $1}'

Di grande interesse anche i costrutti BEGIN ed END che permettono di definire blocchi di operazioni da eseguire prima o dopo l’esame del file. Posso ad esempio riscrivere lo stesso esempio di prima come:

cat /etc/passwd|awk 'BEGIN{FS=":"}{if ($7=="/bin/false") print $1}'

awk è un vero e proprio linguaggio di programmazione anche se abbastanza semplice, e le possibilità che offre sono molto ampie; nondimeno la sua utilità è soprattutto nelle sue forme più semplici dove, con molta semplicità, permette di improvvisare filtri o operazioni anche molto sofisticati.

Concludo con un esempio dove mostro come awk può essere utilizzato per calcolare la durata media delle slow query a partire da un file di log di mysql:

cat mysql-slow.log |grep Query_time|awk '{cont++;totale+=$3}END{media=totale/cont; print "media = " media}'

perl e linea di comando

Il perl con le sue espressioni regolari è anche un fantastico tool per la linea di comando in grado di sostituire e surclassare i pur notevoli awk e sed. Usato con le opzioni del caso, infatti, il perl ne riprende tutte le caratteristiche ma aggiunge la sua flessibilità e ricchezza.

Le opzioni chiave per l’uso da linea di comando sono “-p” e “-n“: in entrambi i casi lo script o il comando in perl viene eseguito per ogni riga del file in ingresso ma nel secondo caso il risultato va in output.

Queste opzioni corrispondono ai loop che riporto sotto:

-n

LINE:
while (<>) {
        ...             # programma
      }

-p

LINE:
while (<>) {
         ...             # programma
      } continue {
         print or die "-p destination: $!\n";
      }

Molto utili sono anche

  • -e che permette di inserire direttamente sulla linea di comando il codice perl da eseguire
  • -i che fa si che venga modificato direttamente il file che viene letto

Ad esempio entrambi i seguenti comandi

cat /etc/passwd |perl -pe 's/(.*):(.*):(.*):(.*):(.*):(.*):(.*)/$1\t\t$6/'
cat /etc/passwd | awk -F: '{print $1"\t\t"$6}'

permettono di estrarre dal file password gli utenti e le loro home.

Perl, utilizzato in questo modo, permette di definire dei blocchi di codice BEGIN e END per eventuali operazioni preliminari a o conclusive del loop implicito: si immagini il caso in cui si voglia contare il numero di occorrenze di un qualche pattern in un file.

La documentazione delle opzioni è su perldoc perlrun.