Lucrarea 4

Utilitare soft Unix: sort, tr, cut, paste, uniq, comm, sed

  

1. Scopul lucrării

Un filtru este un program care modifică sau transformă intrarea sa. De regulă, filtrul citeşte intrarea standard şi scrie la ieşirea standard, fapt pentru care mai multe filtre pot fi conectate printr-un pipe.

 

Filtrele prezentate pe parcursul a două lucrări sunt programele: sort, tr, cut, paste, uniq, comm, sed, grep ( egrep, fgrep) şi awk.

Se doreşte ca după efectuarea lucrării să se formeze deprinderea în utilizarea acestor utilitare în activitatea de programare.

 

2. Consideraţii teoretice

2.1. SORT

Utilitarul este utilizat pentru sortarea şi/sau interclasarea mai multor fişiere. În fişierul care se sortează pot fi delimitate câmpuri ce conţin cheia de sortare. Sintaxa:

sort [opţiuni] [-o f_out] [+poz1 [-poz2]] [fişier(e)]

 

Prin +poz1 si -poz2 se delimitează poziţia ocupată de cheia de sortare în cadrul unei linii. Dacă poz2 lipseşte cheia de sortare este până la sfârşitul liniei. O poziţie se specifica utilizând formatul m.n, având semnificaţia "al n+1-lea caracter din câmpul m+1".

Cheia de căutare poate fi postfixată de fanioane care modifică interpretarea implicită a cheii ( ca şir de caractere ASCII).

Pentru a defini locaţia unei chei într-o înregistrare, înregistrarea este partiţionată în câmpuri marcate de delimitatori. Implicit delimitatorii sunt caracterele '\b' si '\t' dar aparţin câmpului ce le urmează. De exemplu linia:

sistemul Unix

 

este partiţionată în două câmpuri 'sistemul' şi ' Unix', al doilea câmp începe cu două caractere '\b'. Delimitatorii impliciţi pot fi modificaţi la un singur delimitator folosind opţiunea -t. În acest caz caracterul delimitator nu face parte din câmp. Două caractere delimitator într-o linie definesc un câmp gol.

Exemple de chei:

+2.1 -5.3 Specifică o cheie ce începe cu caracterul al 2-lea din al 3-lea câmp până la al 4-lea caracter din câmpul 6.

+1 -2 Specifică o cheie ce începe cu primul caracter din al 2-lea câmp până la primul caracter din al 3-lea câmp, adică al doilea câmp.

+2r Specifică o cheie ce începe cu câmpul al 3-lea până la sfârşitul liniei şi sortarea este inversă.

-t: +2 -2.2 +0n -1 Sortarea se face după primele două caractere din câmpul al 3-lea. Pentru chei identice sortarea se face numeric după primul câmp.

Opţiuni:

-c Se verifică dacă fişierul de intrare este sortat deja.

-m Se face doar interclasare.

 

-o f Trimite rezultatul sortării în fişierul f.

-u Se face sortare unică ( pentru chei identice).

-tx Câmpurile sunt separate de caracterul x.

Opţiuni ce pot deveni locale unei chei:

-b Separatorii de început se neglijează.

-f Literele mici sunt convertite la litere mari.

-i Se ignoră caracterele neafişabile.

-n Se face sortare numerică.

Opţiunea '-b' este echivalentă cu a postfixa fiecare cheie cu 'b'.

Exemple:

a) sort +1 -2 file

 

Sortează file după câmpul 2.

b) sort -r -o out +1.0 -1.2 fis1 fis2

 

Sortează în ordine inversă fişierele fis1 şi fis2 cheia fiind primul caracter din câmpul 2. Ieşirea este fişierul out.

c) $sort -um +2 -3 fis

 

Afişează toate liniile fişierului fis, deja sortat, şi elimină toate liniile în afara primei apariţii din grupul cu acelaşi câmp 3.

 

2.2. TR

Comanda tr permite editarea unui fişier prin înlocuirea unui şir de caractere cu un altul, prin ştergerea anumitor caractere, sau prin înlocuirea caracterelor repetitive prin caractere singulare.

Se apelează, utilizând sintaxa:

tr [opţiuni] [sir_1 [sir_2]]

 

unde, sir_1 este şirul de caractere ce se înlocuieşte sau se şterge, iar sir_2 este şirul de caractere care se substituie sau se condensează.

Opţiuni:

 

-c Complementează setul din sir_1 în raport cu setul ASCII. Opţiunea este utilă când este specificată şi opţiunea -d; caz în care toate caracterele cu excepţia celor din sir_1 sunt şterse.

-d Şterge caracterele ce corespund lui sir_1. Dacă opţiunea -s nu e precizată, sir_2 este redundant.

 

-s Suprimă caractere repetate din sir_2 ( dacă opţiunea -d este prezentă) sau din sir_1 ( dacă opţiunea -d lipseşte) la caractere singulare.

Pentru a introduce domenii de caractere sau caractere repetate, se pot utiliza prescurtările:

 

[a-z] caracter între a si z;

[a*n] n apariţii ale lui a;

\nnn caracter cu codul ASCII nnn;

Exemplu:

 

# Selectează toate cuvintele din fis1 situate unul pe linie:

tr -cs "[A-Z][a-z]""[\012*]" < fis1 > fis2

 

2.3. CUT

Comanda cut extrage porţiuni specificate din fiecare linie din intrare.

Sintaxa comenzii cut este:

cut opţiuni [fisier(e)]

Opţiuni:

-clista Extrage caracterele specificate în lista.

Exemplu: $cut -c2,5-8,14-

Extrage caracterele 2, 5 până la 8 şi 14 până la lungimea liniei.

 

-flista Extrage câmpurile specificate în lista. Câmpurile sunt concatenate şi afişate.

-dc Delimitator în loc de '\t' este c.

Exemplu: $cut -f1,3-4 -d:

Câmpurile 1, 3 şi 4 sunt extrase de pe fiecare linie cu delimitator de câmp caracterul ':'.

 

2.4. PASTE

Comanda paste permite concatenarea liniilor corespondente din diverse fişiere într-una singură. Dacă sunt precizate două fişiere de intrare fiecare linie din fişierul de ieşire este formată din prima linie a primului fişer urmată de prima linie din al doilea fişier separată prin caracterul '\t'.

Sintaxa comenzii este:

paste [opţiuni] [fişier(e)]

Opţiuni:

 

-dlista Foloseşte repetat caracterele din lista ca separatori.

-s Concatenează linii dintr-un fişier. Dacă se specifică mai multe fişiere de intrare rezultatul este inutil.

 

Comanda paste este folosita uneori cu cut pentru a rearanja câmpurile unui fişier.

 

Exemple:

$cat fis1

Popescu

Ionescu

$cat fis2

Paul

Ion

$paste fis1 fis2

Popescu Paul

Ionescu Ion

 

2.5. UNIQ

Comanda uniq elimină sau raportează linii consecutive identice dintr-un fişier.

Sintaxa comenzii este:

 

uniq [opţiuni] [infile [outfile]]

Opţiuni:

-d Produce doar prima linie din fiecare linie care se repetă.

-u Produce doar liniile care nu se repetă.

-c Prefixează fiecare linie cu numărul de apariţii ale liniei. Opţiunea implică opţiunile -d si -u.

 

+n Ignoră la comparaţie primele n câmpuri. Separatorul este caracterul '\b'.

 

-n Ignoră primele n caractere. Dacă sunt precizate ambele opţiuni, +n şi -n, se ignoră iniţial câmpurile şi apoi caracterele.

 

Implicit infile şi outfile sunt fişierele standard de intrare şi ieşire. Dacă nici una din opşiunile -u, -c sau -d nu este specificată uniq produce fiecare linie singulară şi prima linie dintr-o secvenşă de linii repetate.

 

2.6. COMM

Comanda comm permite compararea a doua fişiere sortate, afişarea liniilor comune din ambele fişiere. Afişarea conţine trei coloane:

- liniile care apar numai în primul fişier,

- liniile care apar numai în al doilea fişier,

- liniile comune ambelor fişiere.

 

Sintaxa comenzii este:

comm [-flags] fis1 fis2

Flags poate conţine cifrele 123. Fiecare cifră indică coloana care se suprimă. Argumentele fis1 şi fis2 sunt fişierele care se compară, ambele putând fi precizate ca '-' ( standardul de intrare).

 

2.7. SED

Editorul sed (stream editor) aplică un set de fix de comenzi editor pe un set de fişiere.

Sintaxa de apel:

sed [-n] [-e script] [-f script_date] [ fişier(e)]

-n Nu se produce tamponul de intrare după procesarea unei linii. Efectul acestei opţiuni se poate obţine dacă script-ul începe cu '#n'.

 

-e script Fişierele sunt editate conform script-ului. Script-ul trebuie inclus între apostrofuri, astfel încât caractere ca ';' si '\' sunt interpretate de sed şi nu de shell. Dacă o linie de comandă conţine numai o opţiune -e aceasta poate lipsi.

 

-f sfile Editează fişierul conform script-ului din sfile. Întrucât sed citeşte script-ul, convenţiile shell asupra '\' si '\n' nu se aplică.

Exemple: Substituie fiecare apariţie 'adi' din fişierul name cu 'Adi' şi afişează rezultatul la ieşirea standard.

$sed -e 's/adi/Adi/g' name

$cat chname

s/adi/Adi/g

$sed -f chname name

 

Sed foloseşte două buffere: input si hold. Acestea sunt numite în paginile de manual sed ca "pattern space" şi "hold space". Pentru majoritatea editoarelor, buffer-ul hold nu este necesar, el devine activ doar dacă se foloseşte o comandă care se referă la el. Iniţial buffer-ul hold conţine o linie vidă.

 

Ciclul folosit de sed până la epuizarea intrării constă din paşii:

1. Dacă buffer-ul de intrare este gol, sed citeşte următoarea linie din intrare şi o plasează în buffer. Buffer-ul de intrare poate să nu fie gol dacă ciclul anterior a fost terminat cu comanda 'D' ( nu se citeşte altă linie şi se aplica paşii următori).

2. Inspectează comenzile din script, executându-le pe cele selectate.

 

3. Dacă script-ul nu a fost terminat prin comenzile 'd' sau 'D' şi dacă ieşirea nu a fost suprimată prin opţiunea -n, conţinutul buffer-ului este scris la ieşirea standard şi buffer-ul de intrare este golit. În anumite cazuri este mai convenabilă suprimarea ieşirii normale şi realizarea acesteia explicit prin comenzile 'p' sau 'P'.

Un script de editare constă dintr-o secvenţă de comenzi separate prin caracterul '\n' sau ':'. O comandă este formată dintr-un caracter. Majoritatea comenzilor sunt urmate de un selector, care poate fi o adresă sau o pereche de adrese. O adresă selectează linii individuale, iar o pereche un grup de linii. De exemplu:

/aaa/, /bbb/d

Şterge toate liniile începând cu prima ce conţine şirul 'aaa' şi până la linia ce conţine şirul 'bbb' inclusiv.

Sed permite adrese specificate ca: număr de linie sau expresie regulată. Un număr de linie este un întreg sau caracterul '$' ( ultima linie). O adresa specificată ca expresie regulată are forma:

 

/exprreg/ sau \cexprregc

unde c este orice caracter singular. De exemplu, selectarea liniilor ce conţin şirul 'sistem':

\#sistem#

Dacă selectorul precizează un grup de linii oricare dintre adrese poate fi un număr de linie sau o expresie regulată. De exemplu:

/wall/,5 selectează grupul de linii ce începe cu prima linie ce conţine şirul 'wall' până la linia 5.

Comenzi sed

 

În comenzile ce vor fi discutate în cele ce urmează, r indică mai multe linii, iar a indică o adresă singulară. Ambele pot fi omise, iar a poate fi folosit în locul lui r. O comandă este executată pentru liniile care satisfac selectorul, iar în cazul în care selectorul lipseşte pentru toate liniile.

 

Comenzile sed sunt:

s/exprreg/nou/[f] Substituie prima apariţie a şirului ce se potriveşte cu exprreg prin şirul nou. Pe poziţia lui f pot să apară:

g substituie toate apariţiile;

 

n substituie a n-a apariţie;

p afişează buffer-ul de intrare după substituşie;

w file adaugă buffer-ul de intrare la file dacă a avut loc o substituţie.

 

Caracterul '\n' are semnificaţie diferită în şirul nou. Pentru a include acest caracter se foloseşte caracterul '\' la sfârşit de linie, ca în exemplul:

 

s/cut here/cut\

here/

în care caracterul '\b' a fost înlocuit cu caracterul '\n'.

r y/s/sn/ Substituie s cu sn.

r d Şterge buffer-ul de intrare, reia ciclul şi ignoră restul script-ului.

r D Şterge prima linie a buffer-ului de intrare, reia ciclul şi ignoră restul script-ului.

a a\text Afişează text înainte de a citi următoarea linie. Efectul comenzii este întârziat până la execuţia tuturor comenzilor din script.

r i\text Afişează text imediat.

r c\ text Modifică liniile din r la text şi lansează un nou ciclu ignorând restul script-ului.

r g Înlocuieşte input buffer cu hold buffer.

r G Adaugă hold buffer la input buffer.

r h Înlocuieşte hold buffer cu input buffer.

r H Adaugă input buffer cu hold buffer.

r x file Interschimbă input buffer cu hold buffer.

De exemplu: 4h; 5,10H; $G

muta liniile 4,10 la sfârşitul fişierului de intrare. Comanda '4,10H' nu lucrează corect deoarece introduce o linie vida înainte ( conşinutul hold bufferului).

r n Afişează input buffer şi-l înlocuieşte cu următoarea linie. Numărul de linie este incrementat.

r N Adaugă următoarea linie la input buffer. Numărul de linie este incrementat.

r p Afişează input buffer. Utilă în conjuncţie cu opţiunea -n, deoarece permite înlocuirea ieşirii implicite printr-una explicită.

r l Afişează input buffer cu afişarea caracterelor netipăribile.

r P Afişează prima linie din input buffer. Comanda este urmată deseori de 'D'.

a= Afişează o linie cu numărul liniei curente. Nu afectează conţinutul input buffer-ului.

r r file Afişează conşinutul fişierului file înainte de a citi următoarea linie.

r w file Adaugă input buffer la file.

 

: label Plasează eticheta label.

r b [lab] Salt la eticheta lab din script.

r t [lab] Salt la eticheta lab din script dacă a avut loc o substituţie folosind comanda s, deoarece ultima acţiune a lui sed a fost citirea unei linii de intrare sau execuţia unei comenzi t.

a q Părăsirea editării.

 

# Linie de comentariu (doar prima linie).

r!cmd Execută comanda cmd pentru liniile neselectate prin r.

r{[cmd]...} Execută comenzile cmd.... Comenzile sunt separate prin '\n' sau ':'.

 

3. Aplicaţii

3.1. Să se scrie un fişier de comenzi care să afişeze luna şi data de astăzi în video invers.

 

zi=`date +%d`

luna=`date +%m`

set `date`

an=$6

zi=$3

INVERS=`tput smso`

NORMAL=`tput rmso`

cal $luna $an |

sed " s/^/ /

s/$/ /

s/ $zi / ${INVERS}$zi${NORMAL} /g"

$azi

September 1995

S M Tu W Th F S

1 2

3 4 5 6 7 8 9

10 11 12 13 14 15 16

17 18 19 20 21 22 23

24 25 26 27 28 29 30

3.2. Să se scrie un fişier de comenzi care folosind utilitare descrise în lucrare să afişeze subarborele pentru un director dat ca argument în linia de comandă.

# mytree

if [ $# lt 1 ]

then

echo "mytree Directory tree (C) 1995 Adi K."

echo "usage: $0 name1 name2 ... [ optiuni find ]"

else

find $@ print 2>/dev/null |

sed e 's/[^\/]*\//| /g' e 's/ |/ |/g'

fi

Rezultatul acestui program este:

$mytree /dosc/lab/

mytree 
| | lab 
| | | cpy 
| | | l4 
| | | | mytree 
| | | | azi 
| | | | a.c 
| | | | myfinger 
| | | | b.c 
| | | | c.c 
| | | mycd 
| | | l5 
| | | | l5util.wp 
| | | | ok 
| | | | | allhome.awk 
| | | | | dame.awk 
| | | | | finger.awk 
| | | | | fmt.awk 
| | | | | inv.awk 
| | | l9 
| | | | pipecu.wp 

3.3. Să se scrie un fişier de comenzi care să permită poziţionarea automată după compilare pe prima eroare detectată. După corectarea erorii programul să fie recompilat până când se epuizează toate erorile. Se presupune că editorul cu care se lucrează este vi, iar o eroare raportată de cc are structura:

sursa.c:nr: syntax error sau

sursa.c: Error in function :

#autocc

#initializare signalhandling 
trap 'rm $errdat 2>/dev/null; exit 1' 1 2 3 4 5 15 
# 
errdat="/tmp/${LOGNAME}.err" 
args=$@ 
var=$args 
# intrare pt.cc 
echo 
echo cc $@ 
cc $@ 2>$errdat 
cat $errdat | sed '/:$/d' > /tmp/bad 
mv /tmp/bad $errdat 
# cat timp exista erori 
while [ s $errdat ] 
do 
echo 
echo "Prima eroare de compilare:" 
echo '*' 
sed '1q' $errdat 
echo '*' 
echo "Tastati CR, pentru apelul editorului" 
read X 
datein=`sed '1{s/\([^:]*\).*/\1/1 
q 
}' $errdat` 
echo 
if [ w $datein ] 
then 
# extrage numărul liniei în care apare prima eroare 
nr_lin=`sed '1{s/.*:\([19][09]*\):.*/\1/ 
q 
}' $errdat` 
# apel editor vi si poziţionare cursor pe prima linie cu eroare 
echo "Apel vi cu prelucrare $datein" 
vi +$nr_lin $datein 
var=`echo $args | 
sed "h 
s/\(.*\)$datein.*/\1/ 
x 
s/.*\($datein.*\)/\1/ 
x 
G 
s/\(.*\)\n\(.*\)/\1\2/ 
q"` 
echo 
echo "Recompilare:" 
echo " cc $var" 
cc $var 2> $errdat 
cat $errdat | sed '/:$/d' > /tmp/bad 
mv /tmp/bad $errdat 
else 
echo "Nimic de corectat în $datein" 
exit 1 
fi 
done 
echo 
echo "cc $@" 
echo " compilat fără erori !" 
echo 
echo "cc $var" 
echo " a fost ultimul apel" 
echo 
rm $errdat 
exit 0 

 

4. Probleme propuse

4.1. Ce realizează comenzile ?

 

a) $tr "[a-z]" "[A-Z]"

b) $who | grep 'Adi'

c) $who | grep '^Adi'

d) $sort < fis | find "unu"

e) $ls -l | sort +4n

f) $ls | paste -d' ' -s -

g) $date | cut -c12-16

h) $date | cut -c5-11,25- | sed 's/\([0-9]\{1,2\}\)/\1,/'

i) fis1 si fis2 sunt din exemplul de la paste, iar fişierul adrese are următorul conţinut:

22 Decembrie nr.34

Bd. Bucureşti nr.2

$paste -d'+' fis1 fis2 adrese

4.2. Să se scrie comenzile pentru:

a) Afişarea utilizatorilor din sistem ce au numele de minim patru caractere.

b) Afişarea utilizatorilor care au identificatorul utilizator mai mare ca 99.

c) Afişarea tuturor fişierelor din director în ordinea descreşterii dimensiunii fişierelor.

4.3. Ce realizează acest fişier de comenzi:

if [ $# ne 1 ]

then

echo "Usage: finger name|pattern"

exit 1

fi

for b în `cut d: f1,5 /etc/passwd| egrep în "$1"| cut d: f1`

do

passwd_l=`sed n "$b p" /etc/passwd`

home_dir=`echo $passwf_l | cut d: f6`

echo $passwd_l |

cut d: f1,5 |

sed 's/:/ Real Name: /

s/^/Login: /'

done

4.4. Să se scrie un fişier de comenzi whos care afişează o listă sortată a utilizatorilor care sunt în sesiune.

4.5. Cum se poate folosi aplicaţia 3.3 pentru a afişa numai directoarele (se va folosi type d).

4.6. Să se testeze aplicaţia 3.3 pe următoarele programe:

$cat a.c

main() 
{ 
int a+1, b=2; 
add( a,b); 
a=17; 
b=13; 
add2(a,b); 

$cat b.c

add( x,y) 
{ 
printf("%d + %d = %d\n", x, y, x+y); 

$cat c.c

add2( x1, x2) 
{ 
printf("%d + %d = %d\n", x1, x2, x1+x2); 

4.7. Să se rescrie programul de la aplicaţia 3.3. dacă erorile raportate de compilator au structura:

"sursa.c", line 2: syntax error

4.8. Sa se explice funcţionarea programului.

# monitor - wait until a specified user logs on

mailOpt=FALSE 
interval=60 
while getopts mt: option 
do 
case "$option" 
în 
m) mailOpt=TRUE;; 
t) interval=$OPTARG;; 
\?) echo "Usage: mon [m] [t n] user" 
echo " m informed by mail" 
echo " t check every n seconds." 
exit 1;; 
esac 
done 
# make sure a user name was specified 
if [ "$OPTIND" gt "$#" ] 
then 
echo "Missing user name!" 
exit 1; 
fi 
shiftno=`expr $OPTIND 1 ` 
shift $shiftno 
user=$1 
# check for user logging on 
until who | grep "^$user " > /dev/null 
do 
sleep $interval 
done 
# the user has logged on 
if [ "$mailOpt" = FALSE ] 
then 
echo "$user has logged on" 
else 
me=` who am i | cut c18` 
echo "$user has logged on" | mail $me 
fi 

4.9. Să se modifice programul mon ( de la 4.8) astfel încât să se afişeze şi terminalul la care lucrează un anume utilizator.