dimarts, 23 de març del 2021

Ús de filtres i expressions regulars del shell bash


Filtres

Molts comandaments, no tots, admeten com a dades d'entrada el resultat d'un altre comandament, al que apliquen alguna transformació ("filtren" el resultat); per exemple sort ordena alfabèticament el resultat s'un altre comandament:

~$ ls
'Welcome to CoCalc.term'   cuatro   dos   tres   uno
~$ ls | sort
Welcome to CoCalc.term
cuatro
dos
tres
uno
~$ ls | sort -r
uno
tres
dos
cuatro
Welcome to CoCalc.term

grep

Examina les dades d'entrada i mostra cada línia que contingui un "patrò", és a dir, una cadena de text que pot incloure caràcters especials. Exemple: tenim un fitxer MAIL amb les capçaleres del correus rebuts al sistema:

~$ cat MAIL
Delivered-To: mary@gmail.com; From: joan@gmail.com
Delivered-To: mary@gmail.com; From: pere@gmail.com
Delivered-To: mary@gmail.com; From: ines@gmail.com
...

Per mostrar els correus rebuts de pere:

$ grep "From: pere" MAIL
Delivered-To: mary@gmail.com; From: pere@gmail.com
~$  
 
Per mostrar els correus NO enviats per pere:

$ grep -v "From: pere" MAIL
Delivered-To: mary@gmail.com; From: joan@gmail.com
Delivered-To: mary@gmail.com; From: ines@gmail.com
~$ 

Podem encadenar filtres: veure correus rebuts per Mary NO enviats per pere:
~$ grep -v "From: pere" MAIL | grep "To: mary"
Delivered-To: mary@gmail.com; From: joan@gmail.com
Delivered-To: mary@gmail.com; From: ines@gmail.com
~$ 
 
Un altre exemple: comprovar si en un servidor amb diversos usuaris connectats ha iniciat sessió mary:

who | grep mary

Les cometes al patrò són opcionals excepte si inclou espais en blanc o caràcters especials que poden ser malinterpretats:
 grep -v "From: pere" MAIL | grep To: mary
grep: mary: No such file or directory
~$  

Les cometes dobles indiquen al shell que si usem variables dins dels patrons les ha de tenir en compte, en canvi si usem cometes simples llavors el shell interpreta la cadena de text literalment, sense fer cas de les variables:

~$ echo "El shell que uses és $0"
El shell que uses és /bin/bash
~$ echo 'El shell que uses és $0'
El shell que uses és $0
~$ 

tail

El filtre tail pren línies concretes d'un fitxer o entrada de dades filtrada; per veure la darrera línia del fitxer MAIL:

~$ tail -n 1 MAIL
Delivered-To: mary@gmail.com; From: ines@gmail.com

O bé veure el darrer correu rebut per Mary i NO enviat per pere:

 grep -v "From: pere" MAIL | grep "To: mary" | tail -n 1
Delivered-To: mary@gmail.com; From: ines@gmail.com

Evidentment tail té moltes més opcions que podem consultar en el manual.

cut

 Aquest filtre extreu parts de línies de text d'un fitxer. És molt útil per administrar fitxers de dades en format CSV. Per exemple, per extreure la part de qui fa l'enviament de correu en el fitxer MAIL:

cat MAIL | cut -d\; -f1
Delivered-To: mary@gmail.com
Delivered-To: mary@gmail.com
Delivered-To: mary@gmail.com

El paràmetre -d seguit de ; indica a cut que el fitxer està organitzat en camps separats per punt i coma, i el paràmetre -f1 li du que d'aquests camps agafi el primer. Es pot dir que prengui una llista de camps com ara f1,2,4 o  un interval com ara -f1-3. La barra invertida en .d\; és necessària per que el ; té un significat especial pel bash. Combinant dos cut obtenim la llista de mails que han enviat correu a mary en el fitxer MAIL:

cat MAIL | cut -d\; -f2 | cut -d: -f2
 joan@gmail.com
 pere@gmail.com
 ines@gmail.com

Ara ens serà fàcil obtenir, amb cut, un llistat dels usuaris del sistema junt amb el seu shell per defecte:

cat /etc/passwd | cut -d: -f1,7

Molts usuaris del sistema en realitat no tenen dret a iniciar sessió, són els que en el seu shell per defecte tenen un "nologin"; combinant amb grep podem veure els usuaris que sí poden iniciar sessió:

cat /etc/passwd | cut -d: -f1,7 | grep -v "nologin"
root:/bin/bash
sync:/bin/sync
salvus:/bin/bash
gdm:/bin/false
postgres:/bin/bash
user:/bin/bash
sbt:/bin/false
rstudio-server:/bin/sh

tr

Aquest filtre transforma caràcters segons el patrò; la forma més simple d'usuar-lo és donant dues cadenes de text:

cat MAIL
Delivered-To: mary@gmail.com; From: joan@gmail.com
Delivered-To: mary@gmail.com; From: pere@gmail.com
Delivered-To: mary@gmail.com; From: ines@gmail.com
~$ cat MAIL | tr mary Mary
Delivered-To: Mary@gMail.coM; FroM: joan@gMail.coM
Delivered-To: Mary@gMail.coM; FroM: pere@gMail.coM
Delivered-To: Mary@gMail.coM; FroM: ines@gMail.coM
~$ 

Per transformar de minúscules a majúscules usem 
cat MAIL | tr a-z A-Z 
DELIVERED-TO: MARY@GMAIL.COM; FROM: JOAN@GMAIL.COM
DELIVERED-TO: MARY@GMAIL.COM; FROM: PERE@GMAIL.COM
DELIVERED-TO: MARY@GMAIL.COM; FROM: INES@GMAIL.COM

També pot eliminar amb l'opció -d. Per treure els ; del text: 
cat MAIL | tr -d \;
Delivered-To: mary@gmail.com From: joan@gmail.com
Delivered-To: mary@gmail.com From: pere@gmail.com
Delivered-To: mary@gmail.com From: ines@gmail.com

Fixeu-vos que tr NO modifica el fitxer original, si no que pren el resultat de mostrar el fitxer per pantalla; si volem deixar el resultat en un fitxer podem fer una redirecció:

cat MAIL | tr -d \; > MAIL2

NO és recomana sobreescriure directament el fitxer font:

cat MAIL | tr -d \; > MAIL     NO recomanable!


Expressions regulars


Els filtres es converteixen en eines potents d'administració quan en els patrons s'inclouen expressions regulars. Una expressió regular serveix per expressar patrons de text que es poden usar en cerques; el patrò més simple és una cadena de text tal qual:

jordi@jordi-sve1513c5e:~$ ls | grep word
password.txt
wordlist

però podem complicar-les mitjançant caràcters especials com ara asterisc, interrogant, etc. El caràcter * significa "qualsevol seqüència de caràcters a continuació", el caràcter . significa "qualsevol caràcter simple a continuació":
jordi@jordi-sve1513c5e:~$ ls | grep h.. 
dashboard-cluster-role-binding.ym 
dashboard-cluster-role-binding.yml 
dashboard-service-account.yml

jordi@jordi-sve1513c5e:~$ ls | grep word*
wordlist


El "*" també es pot usar com prefix d'un caràcter, llavors indica 0 o més ocurrències del caràcter: si el fitxer prova conté:

cat prova
1234
1234aaa
123AAA

llavors 


~$ cat prova | grep '[1-4]a*'
1234
1234aaa
123AAA

retorna les línies que tenen números entre 1 i 4 seguits de zero, una o més d'una "a"; en canvi

cat prova | grep '[1-4]aa*'
1234aaa

mostra només les que com a mínim tenen una "a". 

 
El símbol "^" representa el principi de la línia i el símbol "$" el final; així doncs

 ls -l | grep "^d"
drwxr-xr-x 2 user user   2 Mar 23 18:22 ees
drwxr-xr-x 2 user user   2 Mar 23 18:22 exes
drwxrwxr-x 2 user user   2 Mar 23 18:22 fonts

ens mostra només els directoris; si ho combinem amb el caràcter "." podem obtenir els directoris que tenen permís d'escriptura per el grup d'usuaris del propietari:

ls -l | grep "^d" | grep "^.....w"
drwxrwxr-x 2 user user   2 Mar 23 18:22 fonts

Per trobar línies que només contenen un patró determinat combinem ^ i $:

cat prova | grep '^1234$'
1234

Claudàtors []

Els caràcters que estiguin entre claudàtors són expressions regulars que signifiquen "coincidir amb qualsevol del caràcter de l'interior dels claudàtors".

jordi@jordi-sve1513c5e:~$ ls | grep h[aeiou]
hello.yml
hola
kubectl.sha256
nohup.out
while.sh

S'admeten rangs de valors, com ara:

jordi@jordi-sve1513c5e:~$ ls | grep h[a-i]   
dashboard-cluster-role-binding.ym
dashboard-cluster-role-binding.yml
dashboard-service-account.yml
hello.yml
kubectl.sha256
while.sh

Com a cas especial, [a-zA-Z]  significa qualsevol cadena alfabètica; per exemple, si el fitxer prova conté:
cat prova
1234
1234aaa
123AAA
mary
joan

llavors:

cat prova | grep [a-zA-Z]
1234aaa
123AAA
mary
joan

Repeticions



El caràcter ? significa "el caràcter anterior pot estar o no"; per usar-lo amb cerques cal afegir l'opció -E de grep, o usar el comandament egrep:

jordi@jordi-sve1513c5e:~$ ls | egrep  "as?s"
dashboard-cluster-role-binding.ym
dashboard-cluster-role-binding.yml
dashboard-service-account.yml
password.txt

Veiem que com a mínim ha d'haver una "s", l'altre és opcional. 

Amb el caràcter | expressem "un o l'altre":
jordi@jordi-sve1513c5e:~$ ls | egrep  "prov|pas"
password.txt
prova2
prova3
provadir

Els caràcters especials, que són ^ . [ $ ( ) | * + ? { \, si es volen usar com caràcters normals, s'han "d'escapar", això és, precedir de la barra invertida; per buscar IPs en la xarxa 192.168.1.x
ls | egrep "192\.168\.1"
192.168.1.200
192.168.1.210

sed

Aquest filtre és un editor de text: permet inserir, modificar i eliminar text en un fitxer des de línia de comandaments. Per exemple per substituïr una cadena de text per una altre usem s/cadena/cadena nova

 cat prova | sed 's/1234/192.168.1.100/'

canviarà la cadena 1234 per 192.168.1.100. Al combinar sed amb patrons regulars amplifiquem molt la seva funcionalitat. Per exemple sabem que ls torna el llistat del directori actual en format apaisat

ls
 MAIL   'Welcome to CoCalc.term'   dos   exes    prova   uno
 MAIL2   cuatro                    ees   fonts   tres

Si filtrem amb sed obtenim la llista en vertical
ls | sed 's/  */\n/'
MAIL
MAIL2
Welcome
to CoCalc.term
cuatro
dos
ees
exes
fonts
prova
tres
uno

el patrò regular  's/  */\n/' significa substituïr un blanc seguit de qualsevol nombre de blancs per un salt de línia (caràcter especial \n).
Per esborrar línies usem "d"

cat llista | sed '$d'

salta al final del fitxer (símbol $) i esborra aquella línia ("d"). 

Exercicis

1. Volem obtenir un llistat d'usuaris amb els que darrerament s'ha intentat obrir una sessió sense èxit, pot tenir utilitat per detectar possibles usos no autoritzats de comptes d'usuari. El comandament lastb ja ho fa això:

sudo lastb
mysql    pts/2                         Tue Apr  6 12:29 - 12:29  (00:00)
mysql    pts/2                         Tue Apr  6 12:29 - 12:29  (00:00)
UNKNOWN  pts/2                         Tue Apr  6 12:28 - 12:28  (00:00)
anna     pts/2                         Tue Apr  6 12:28 - 12:28  (00:00)
jcuesta  pts/2                         Tue Apr  6 12:12 - 12:12  (00:00)

btmp begins Tue Apr  6 12:12:06 2021


No obstant volem formatar la sortida de lastb de forma que només mostri el nom de l'usuari, la data i el nombre de intents no autorizats en aquella data, l'anterior llistat s'ha de veure així:

1 anna,Apr,6
1 jcuesta,Apr,6 
2 mysql,Apr,6
1 UNKNOWN,Apr,6

La primera columna mostra el nombre d'intents, la segona columna l'usuari, mes i dia separats per comes. La línia  "btmp begins Tue Apr  6 12:12:06 2021" s'ha filtrat i ja no surt. Escriviu un comandament en línia per aconseguir-ho usant una combinació de filtres i expressions regulars. Ajuda: useu cut per seleccionar els camps usuari, mes i dia, amb sed i una expressió regular podeu eliminar els espais repetits i substituir-los per comes, amb sort i uniq podeu eliminar intents repetits el mateix dia i hora, i comptar-los. Finalment grep us pot ser útil per eliminar la línia que sobra.

2. Escriviu un script bash que, aprofitant el comandament de l'exercici anterior, admeti com a parámetre una llista d'usuaris i mostri només els intents sense èxit d'aquests usuaris:

./exercici.sh jcuesta mysql
     1 jcuesta;Apr;6
     2 mysql;Apr;6


Ajuda: una forma de fer-ho és seguir el següent algorisme:
 
guardar en una variable LOGINS usuaris que han tingut logins fallit
recorrer la llista d'usuaris que ens pasen en la línia de comandament de l'script:
    per cada usuari, 
         buscar-lo en la variable LOGIN
         si el trobem, llavors llistar els seus intents fallits 
    fi-per
    
La llista de usuaris que ens pasen en la línia de comandaments queda respresentada dins de l'script com a vector, anomenat $@, per tant es pot recorrer la llista d'usuaris de la línia del comandament amb un for:

for user in $@; do

Per buscar un usuari en una llista d'usuaris de forma exacta useu grep -w.
Per comprovar si una variable es buida podeu usar el paràmetre -z del if:
 
 if [ -z "$variable" ]
 
serà cert si la variable no conté res.
 
 



















 

dilluns, 8 de març del 2021

El shell bash per usuaris avançats i administradors de sistemes

El shell és la capa més externa del sistema operatiu, on l'usuari pot interactuar usant comandaments; aquest comandaments són interpretats per un intèrpret, que en el cas de Linux pot ser un de diversos disponibles, essent l'anomenat bash el més usat, i el que tractem a continuació no des de el punt de vista de l'usuari habitual, més acostumat a treballar amb entorns gràfics d'escriptori,si no de l'usuari avançat i dels administradors de sistemes.

Execució de comandaments múltiples amb connectors

Podem unir comandament en una única línia usant el ;com a connector:

root@jordi-sve1513c5e:/media/jordi# who; date
jordi    tty1         2021-03-06 12:29 (:0)
jordi    pts/0        2021-03-06 12:29 (:0)
jordi    pts/1        2021-03-06 13:15 (:0)
dissabte, 6 de març de 2021, 19:50:09 CET

Els espais en blanc abans o després del ; són indiferents. L'operador barra vertical | serveix per connectar el resultat d'un comandament amb l'entrada de dades d'un altre comandament, com ara:

root@jordi-sve1513c5e:/media/jordi# who | wc
     3      15     132

wc ha comptat el nombre de línies, paraules i lletres del resultat de who.  Ara bé, què passa si li enviem dos comandaments a wc?

root@jordi-sve1513c5e:/media/jordi# who; date | wc
jordi    tty1         2021-03-06 12:29 (:0)
jordi    pts/0        2021-03-06 12:29 (:0)
jordi    pts/1        2021-03-06 13:15 (:0)
     1       8      43

Veiem que wc només té en compte el darrer comandament dels dos que hem executat; això és per que el shell tracta a ; i | com a operadors, i el darrer operador té més precedència que el primer. Podem modificar aquest comportament usant parèntesi:

(who; date) | wc
     4      23     175

Ara sí, el resultat con junt dels dos comandaments es passa a wc.  En aquestes interconnexions ens pot interessar guardar resultats intermitjos en un fitxer, ho podem fer amb tee:

root@jordi-sve1513c5e:/media/jordi# (who; date) | tee qui_data | wc
     4      23     175
root@jordi-sve1513c5e:/media/jordi# cat qui_data
jordi    tty1         2021-03-06 12:29 (:0)
jordi    pts/0        2021-03-06 12:29 (:0)
jordi    pts/1        2021-03-06 13:15 (:0)
dissabte, 6 de març de 2021, 19:59:28 CET

Un altre operador de connexió de comandaments és l'ampersand que uneix comandaments en una única línia com el ; però amb la diferència de que no espera que un acabi per llençar el següent: s'executen els comandaments en paral·lel. Una forma fàcil de provar-ho és amb el comandament sleep, que és un temporitzador que espera els segon que li diem:

sleep 5; vi

esperarà cinc segons i després executar l'editor del sistema; en canvi

sleep 5 & vi

deixarà al bash cinc segons en espera però l'editor vi s'obrirà immediatament. Un altre exemple:

root@jordi-sve1513c5e:/media/jordi# (sleep 5; date) & date
[1] 15926
dissabte, 6 de març de 2021, 20:09:48 CET
[1]  
Done                    ( sleep 5; date )
root@jordi-sve1513c5e:/media/jordi# dissabte, 6 de març de 2021, 20:09:53 CET

El primer agrupament entre () s'executa simultàniament amb el segon date, aquest darrer es mostra de seguida en pantalla, però el primer ha d'esperar els 5 segons marcats en sleep.  Fixem-nos que el procés que espera queda en segon pla (en background) amb el número de procés 15926, és com si el procés quedes en espera, es pot comprovar que està en segon pla usant jobs:

root@jordi-sve1513c5e:/media/jordi# jobs
[1]+  Running                 ( sleep 20; date ) &

Per aquest motiu un altre ús de l'operador & és enviar un procés a 2n pla, com ara:

wget http://prdownloads.sourceforge.net/lam/ldap-account-manager_6.8-1_all &
[1] 16494

En el cas de connexions amb | els comandament connectats es consideren un únic procés encadenat, i si afegim el & afecta a tot plegat:

Metacaràcters dels shell

 Els "metacaràcters" són caràcters amb significat especial, com ara * que el shell interpreta com "qualsevol caràcter":

jordi@jordi-sve1513c5e:~$ ls d*
dashboard-cluster-role-binding.ym  dashboard-cluster-role-binding.yml  dashboard-service-account.yml  data.txt

Si volem usar un metacaràcter com a caràcter normal el posem entre cometes simples, o bé el precedim per la barra invertida \ : Així, ls * mostrarà tots els fitxers del directori actual, mentre que 'ls *' només mostrarà el fitxer anomenat * si existeix. Què creieu que mostrarà ls '*'*  ?   I què mostrarà ls \**     ?

Alguns metacaràcters interessants:

> >> < >>

Redireccions d’entrada i sortida estàndar



; & |


Connectors de comandaments en línia



* ?


Caràcters «comodí»



$


Prefix de les variables d’entorn




\





Prefix per anul·lar un metacaràcter com a tal

‘ ‘


No interpretat el contingut



&&


Connector: executar un comandament, si acaba bé, executar el següent


||


Connector: executar un comandament, si no acaba bé, executar el següent


Alguns exemples:

jordi@jordi-sve1513c5e:~$ (ls -d Baixades) && (ls -d samba)  
Baixades
samba            --> Baixades existeix, es motra també samba


jordi@jordi-sve1513c5e:~$ (ls -d Downloads) && (ls -d samba)            
ls: cannot access 'Downloads': No such file or directory

--> Downloads no existeix, el comandament dóna error, no es mostra samba


jordi@jordi-sve1513c5e:~$ (ls -d Downloads) || (ls -d samba)   
ls: cannot access 'Downloads': No such file or directory
samba

--> Downloads no existeix, el comandament dóna error, sí es mostra samba

Variables d'entorn i variables del shell

Tot sistema operatiu manté una llista de variables anomenades "d'entorn" que són útils per recuperar informació de configuració de l'entorn de l'usuari; en el shell aquestes variables estan en majúscules i poden accedir-se amb el prefix $, exemples:

jordi@jordi-sve1513c5e:~$ echo $HOME
/home/jordi
jordi@jordi-sve1513c5e:~$ echo $USER
jordi
jordi@jordi-sve1513c5e:~$ echo $PATH
/home/jordi/.local/bin:/home/jordi/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
:/usr/local/games:/snap/bin

També es poden crear variables d'usuari, que poden ser en minúscules, o no, però el shell les distingirà:

jordi@jordi-sve1513c5e:~$ x=10
jordi@jordi-sve1513c5e:~$ X=100
jordi@jordi-sve1513c5e:~$ echo $x,$X
10,100

Per assignar valor no es posa el prefix $, però per accedir a la variable ja creada, llavors sí.

Les cometes invertides es poden usar per deixar en una variable el resultat d'un comandament:

pwd
/home/jordi/snap/docker
carpeta=`pwd`
echo $carpeta
/home/jordi/snap/docker

Una forma alternativa és usant el prefix $ i tancat el comandament entre ():

root@jordi-sve1513c5e:/home/jordi# carpeta=$(pwd)
root@jordi-sve1513c5e:/home/jordi# echo $carpeta

Si canviem el valor d'una variable d'entorn del sistema, només tindrà validesa en la sessió actual, quan iniciem una altre, el sistema carrega de nou el valor anterior; es pot fer un canvi permanent canviant els valors en el fitxer .profile de l'usuari.  D'altre banda quan executem un comandament dins d'un fitxer de text (és a dir, un shell script), el shell obre una còpia de sí mateix (un subshell) on s'executa l'script, de forma que els canvis en les variables només afecten al subshell. Per veure-ho estudiem el següent exemple:


Aquestes línies estan en  el fitxer variables.sh que té drets d'execució. Fixeu-vos en l'ús que fa de les cometes simples, dobles i invertides: quan són simples vol dir que el contingut entrecomillats es rpen tal qual, literalment, quan són dobles vol dir que el shell interpreta el contingut entrecomillat.  Mostrem ara la variable $HOME i executem l'script:

echo $HOME      
/home
./variables.sh  
\home\jordi
echo $HOME
/home

Fixeu-vos que mentre s'executa l'script el contingut de $HOME ha variat, però en acabar l'execució, es recupera el valor inicial. Si volem que l'script no s'executi en un subshell si no en el shell actual, ho podem indicar usant un punt i un espai abans del nom de l'script:

. ./variables.sh  
\home\jordi
echo $HOME
\home\jordi

Ara sí que l'script modifica la variable del shell. Observem que usem el punt dues vegades, amb significats diferents:

  • el primer punt, separat per espai, indica executar l'script en el mateix shell i no en un subshell
  • el segon punt, que té a continuació la barra, indica al shell que l'script està situat en el directori actual

El segon punt-barra és prescindible si l'script està situat en una carpeta llistada en la variable PATH; si afegim aquesta carpeta, per exemple així:

PATH=$PATH:`pwd`

Llavors ja no ens cal el punt-barra:

. variables.sh    
\home\jordi

L'altre manera de comunicar valors de variables a subshells és usant el comandament export. Estudiem el següent exemple:

root@jordi-sve1513c5e:/home# x=100
root@jordi-sve1513c5e:/home# echo $x
100
root@jordi-sve1513c5e:/home# bash
root@jordi-sve1513c5e:/home# echo $x

root@jordi-sve1513c5e:/home# exit
exit
root@jordi-sve1513c5e:/home# echo $x
100


Veiem que quan executem un shell dintre de l'actual, la variable x no es reconeix en el subshell; en canvi si la "exportem" llavors sí:

root@jordi-sve1513c5e:/home# x=100
root@jordi-sve1513c5e:/home# export x
root@jordi-sve1513c5e:/home# bash
root@jordi-sve1513c5e:/home# echo $x
100

Entrades, sortides i errors estàndard

Tot programa té associat pel sistema operatiu tres fitxers especials: la entrada estàndard, la sortida estàndard i la sortida d'error estàndard, amb uns números associats, anomenats descriptors de fitxers, que són el 0, 1 i 2 respectivament. Per defecte el 0 s'assigna al teclat i els 1 i 2 a la pantalla, però tots es poden redigir.

La redirecció de la sortida d'error estàndard pot ser útil en encadenaments de comandaments on algun de intermig dóna missatges d'error que no es interessen:

root@jordi-sve1513c5e:/home/jordi# ls -l | find "samb" | wc
find: ‘samb’: No such file or directory
     0       0       0

Si redigirim l'error ja no el veiem per pantalla:

root@jordi-sve1513c5e:/home/jordi# ls -l | find "samb" 2>/dev/null | wc            
     0       0       0

També pot passar al revés, que en un encadenament de comandaments algun de entre mig no mostri cap error. Un altre ús seria redirigir els errors a un fitxer de log, per exemple:

find trash 2>> error.log

Qualsevol missatge d'error s'escriu en el fitxer error.log, com usem  l'operador >> s'afegirà a l final del contingut del fitxer.

Iteracions de comandaments

El shell bash és pràcticament un llenguatge de programació, amb comandaments i variables, i també estructures iteratives i condicionals. Repetir una acció sobre un conjunt de fitxers és molt usual en administració de sistemes, i ho podem fer amb la sentencia for

 for x in *
> do
>  echo $x
> done

La x és la variable, que pren tots els noms de fitxers del directori actual (recordem que el metacaràcter * significa això), el for recorre tots aquests noms i els mostra amb echo.  

Com a exemple més pràctic suposem que volem comparar uns fitxers amb la seva còpia de seguretat, per saber si han hagut canvis; el comandament diff compara fitxers línia a línia, si són iguals no diu res, i si són diferents indica la línia on està la diferencia 

diff error.log backup/error.log                            no diu res: iguals

echo "hola" >> backup/error.log                            modifiquem un fitxer

diff error.log backup/error.log  
1a2                                                        en la fila 1
> hola


Suposem que volem comparar els fitxers anomenat error1.log, error2.log, etc amb les seves còpies en el directori backup, podem fer-ho així:

fitxers=$(ls error*.log)        generem la llista de fitxers

for x in $fitxers               recorrem la llista

do

    diff $x backup/$x           comparem un a un

done


Si resulten tots iguals l'script no genera cap sortida per pantalla, altrament mostrarà les diferencies. Potser només ens interessa saber quants fitxers han canviat, sense mostrar res més. Sabem que diff mostra la línia on hi ha diferencia, i a continuació, un ">" seguit de la diferencia

jordi@jordi-sve1513c5e:~/Documents$ ./diff.sh  
1a2
> hola

Per tant si encadenem l'script amb una cerca del caràcter ">", per exemple amb grep, obtindrem només les diferències:

./diff.sh | grep ">"
> hola

Per aquest motiu al comandament grep se l'anomena un filtre, doncs filtra la sortida d'un programa; hi han diversos comandaments del bash que funcionen com a filtres, alguns fent un processament programat sofisticat usant les anomenades expressions regulars.  Si tornem a encadenar-ho amb el comandament wc amb el paràmetre -l (comptar només línies) obtenim un comptador de diferencies trobades en els fitxers

./diff.sh | grep ">" | wc -l
1

En realitat podem incloure l'encadenament dintre del text de l'script:

fitxers=$(ls error*.log)

for x in $fitxers

do

    diff $x backup/$x    

done | grep ">" | wc -l


El Bash també disposa de les repeticions while

read a

while [ $a -gt 0 ] 

do 

  echo "més gran que zero"

  read a

done


Execució condicional

El comandament if del bash selecciona si cal executar o no comandaments depenent d'una condició; tenim vàries possibilitats.

1. La condició és que un comandament no doni error. Per exemple el comandament ls lmv* el podem incloure'l com a condició en el if:

if $(ls lmv*); then

si dóna error,

ls lmv*
ls: cannot access 'lmv*': No such file or directory

llavors NO és compleix la condició del if:

if $(ls lmv*); then  echo "trobats"; else  echo "cap valor"; fi      
ls: cannot access 'lmv*': No such file or directory
cap valor

Com el missatge d'error no ens interessa, el redirigim:

if $(ls lmv* 2>/dev/null); then  echo "trobats"; else  echo "cap valor"; fi  
cap valor

Un cas especial és comprovar si un fitxer o carpeta existeix amb el comandament test:

if test -f pepe.txt; then  echo "existeix"; else  echo "no hi és"; fi    
no hi és

2. La condició compara uns valors amb altres. Per exemple per comparar dues variables numèriques usem el comparador -eq

x=1;y=2
if [ $x -eq $y ]; then  echo "iguals"; else echo "diferents";fi
diferents

si les variables contenen text usem els operador "=":

x="hola";y="adeu"
if [ $x = $y ]; then  echo "iguals"; else echo "diferents";fi    
diferents

3. La condició comprova alguna cosa. Com ara que existeixi un fitxer usant l'operador -e

if [ -e pepe.txt ]; then echo "trobat"; fi

O el contrari, que no existeixi, usant l'operador de negació !

if [ ! -e pepe.txt ]; then echo "no trobat"; fi

Amb l'operador -d verifica si existeix una carpeta. L'operador negació es pot aplicar a qualsevol condició, com ara

if [ ! $x -eq $y ]; then  echo "diferents"; else echo "iguals";fi 

Per comparacions numèriques tenim també els operadors -gt (més gran que) i -lt (més petit que).

Podem incloure sentències if dins d'altres if, i també usar la condició case; veure per exemple https://ryanstutorials.net/bash-scripting-tutorial/bash-if-statements.php

Exercicis

Exercici 1. La majoria d'scripts del sistema estan en el directori /usr/bin. D'altre banda, el comandament file mostra el tipus de fitxer, com ara:

file /usr/bin/bash  
/usr/bin/bash: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linu
x-x86-64.so.2, BuildID[sha1]=a6cb40078351e05121d46daa768e271846d5cc54, for GNU/Linux 3.2.0, stripped

Escriviu un script llistascript.sh que mostri tots els scripts situats en /usr/bin/

$ ./llistascript.sh  
/usr/bin/7z: POSIX shell script, ASCII text executable
/usr/bin/7za: POSIX shell script, ASCII text executable
/usr/bin/7zr: POSIX shell script, ASCII text executable

Ajuda: useu els comandaments ls, grep i file aquest darrer ens dirà de quin tipus és cada fitxer:

file /usr/bin/xzmore

/usr/bin/xzmore: POSIX shell script, ASCII text executable

Podeu usar un for per recórrer tots els fitxers del directori i per cada un executar el comandament file, comprovant que és un script. 

Exercici 2. Molts dels executables de /usr/bin en realitat són vincles simbòlics a altres fitxers; escriviu un altre script  que a més a més de llistar els scripts, mostri aquells que són vincles:

$ ./script2.sh  
/usr/bin/7z: POSIX shell script, ASCII text executable
/usr/bin/7za: POSIX shell script, ASCII text executable
/usr/bin/7zr: POSIX shell script, ASCII text executable
/usr/bin/add-apt-repository: Python script, ASCII text executable
/usr/bin/addr2line: symbolic link to x86_64-linux-gnu-addr2line
/usr/bin/addr2line: symbolic link to x86_64-linux-gnu-addr2line
































Gestió d'usuaris i grups en Linux

Usuaris i grups Linux  Els comptes de Linux són com els comptes de Windows o MacOS; però els detalls no, així que cal explicar alguns detall...