Guida al Terminale (base)

Gestire i file

L'articolo precedente sui terminali ha introdotto alcuni comandi essenziali per spostarsi all'interno del filesystem e per poter ottenere un elenco dei file presenti all'interno di una directory (cartella).

Oggi vederemo come è possibile gestire questi file, ovvero come si può fare per crearli, modificarli, spostarli, copiarli ed eliminarli.
Inoltre daremo una rapida occhiata al significato dei permessi citati nel precedente articolo.

Il Filesystem - questo sconosciuto

Ogni volta che noi facciamo una qualche operazione sui file del nostro computer (editiamo con word, li cancelliamo, ne creiamo di nuovi, copiamo file sulla chiavetta usb, ecc...) stiamo in realtà parlando con una parte importantissima del Sistema Operativo che è chiamata File System.
Questo componente si occupa di gestire tutte le operazioni che noi utenti compiamo su file e directory e, ancora più importante, si preoccupa che i nostri dati siano scritti in modo corretto su Hard Disk! (o altri supporti di memorizzazione, come le chiavette usb di prima, ma questo lo vediamo tra un po).

Vi ricordate cos'è il path? Bene, ora possiamo dire che il path è il percorso che noi diamo al File System per dirgli dov'è il file su cui vogliamo lavorare. Questo perchè il File System ha una sua struttura, cioè è organizzato ad albero. Immaginatevi un albero geneaologico con nella parte alta i genitori e verso il basso tutti i figli dei figli dei figli. Esattamente la stessa struttura che viene usata per salvare su Hard Disk tutti i nostri file.
In alto abbiamo la directory radice della'albero (in inglese root ed indicata con /) e scendendo, attraversando tutte le sotto-directory troveremo tutti i file che vi sono memorizzati, ad esempio al primo livello alcune delle directory tipiche di un sistema Linux (bin/, etc/, usr/, var/, tmp/, home/) ed al secondo livello altre directory, tra cui anche la mia home utente (/home/manuel/). Al terzo livello, nella directory manuel/, alcuni file e directory personali (il file gestire_file.txt e la cartella repni/).

Albero del filesystem

È interessante notare come il path sia costruito semplicemente prendendo uno dietro l'altro il nome di ogni directory ad ogni livello. (Operazione che in informatica viene spesso chiamata concatenazione).

Quindi il path del file di questo articolo (gestire_file.txt) sarà /home/manuel/gestire_file.txt. Allo stesso modo il path della cartella repni/, contenuta in /home/manuel/ sarà /home/manuel/repni/

Creare File e Directory - touch e mkdir

Vediamo come possiamo inizare a creare qualche file per giocare un po' con il File System.

Apriamo un terminale, ci troveremo davanti alla ormai nota scritta [manuel@localhost ~ ] che abbiamo imparato a conoscere (ricordiamo comunque che la tilde, ~, indica la home directory dell'utente collegato, che in questo caso è /home/manuel/).

Cominciamo con il creare un file vuoto, proviamo il comando touch che in realtà serve a cose diverse, ma nel caso il file non esista, provvede a creare uno completamente vuoto.

[manuel@localhost  ~  ] touch fileprova 

È stato creato un file di nome fileprova; infatti, se proviamo a listare il contenuto della directory con ls -l, vedremo tra le altre una riga così:

-rw-r--r--  1 manuel users        0 10 dic 13.58 fileprova

Come abbiamo imparato, si legge nella quinta colonna la dimensione del file: zero perchè è un file vuoto.

Perfetto, ma se volessimo creare una nuova directory? Purtroppo il comando touch non può farlo, e per questo abbiamo bisogno di un'altro comando particolare, chiamato make directory o meglio mkdir

[manuel@acer ~ ]$ mkdir repni 

Ora anche voi avrete sul vostro computer una cartella chiamata repni all'interno della vostra home directory quindi il suo path assoluto sarà /home/manuel/repni/ ricordate che al posto di "manuel" voi avrete il vostro nome utente!

Ma se la directory esiste già? Nessun problema, infatti il comando mkdir è molto efficace e vi mostrerà un messaggio di errore, questo è il mio caso dato che, come dimostra l'albero illustrato sopra, io avevo già una cartella di nome repni:

[manuel@acer ~ ]$ mkdir repni
mkdir: impossibile creare la directory "repni": Il file esiste
[manuel@acer ~ ]$

Qui i più attenti si saranno accorti di qualcosa che non va! Come mai se state cercando di creare una cartella già esistente il sistema vi risponde che "Il file esiste"? Non dovrebbe rispondervi con "La cartella esiste"? NO
Questo è dovuto alla filosofia che si cela dietro i sistemi operativi derivati dalla filosofia Unix (vedi filosofia su articolo1). In questa tipologia di sistemi qualsiasi cosa viene vista come se fosse un file. Quindi le cartelle vengono interpretate come se fossero dei file speciali, che contengono altri file, che a loro volta possono essere file speciali e così via.

Riassumendo, i sistemi operativi Unix considerano tutto come file. Le cartelle saranno file speciali mentre quelli che siamo abituati a chiamare semplicemente "file" saranno file regolari (file di testo, programmi, documenti word, immagini, video, audio....).
Per questo motivo, se volessimo creare una directory chiamata proprio fileprova come il file precedente, non ci riusciremmo:

[manuel@acer ~ ]$ mkdir fileprova
mkdir: impossibile creare la directory "fileprova": Il file esiste
[manuel@acer ~ ]$

Anche se questo concetto è un po' strano e confusionario, prestate molta attenzione perchè è una delle basi fondamentali per altri concetti più complessi.

Manipolare il filesystem - mv, cp, rm

Non avere una distinzione tra file e cartelle nel filesystem ci permette di usare gli stessi comandi per spostare, copiare ed eliminare sia i file sia le cartelle. Questi comandi sono tre:

  • mv - Move, serve a spostare files o directory, ma serve anche a cambiarne il nome.
  • cp - Copy, serve per copiare file o directory da un punto ad un altro del filesystem.
  • rm - Remove, serve ad eliminare file e cartelle.

Move - spostamento e rinomina di file e directory

Il comando mv serve a spostare o rinominare files nel File System.

File regolari

Proviamo a spostare il file che abbiamo creato prima nella nostra home directory, fileprova, nella cartella che abbiamo creato (repni/). Per farlo dobbiamo usare il comando mv da move in inglese, sposta. Questo comando vuole sapere cosa vogliamo spostare e dove vogliamo spostarlo. Avrà quindi la seguente sintassi: mv <cosa> <dove>

Esempio:

[manuel@acer ~ ]$ mv fileprova repni

Il file fileprova sarà spostato nel file speciale (directory) repni/

Il comando move ha un'altro importantissimo compito, quello di poter rinominare un file (o una directory). Facciamo un esempio:

[manuel@acer ~ ]$ cd repni

Spostiamoci nella directory repni/ e rinominamo fileprova come fileprova2:

[manuel@acer repni ]$ mv fileprova fileprova2

Proviamo a listare il contenuto della directory con ls:

[manuel@acer repni ]$ ls
fileprova2

Due osservazioni importanti.

  • La sintassi del comando mv è ambigua. Ovvero, se leggeste il comando del primo esempio, sapreste dire cosa fa, senza consocere che repni è in realtà una directory? No ovviamente, perchè potrebbe anche essere una ridenominazione del file fileprova in un nuovo nome (repni).

    Per questo solitamente quando si spostano dei file in altre cartelle si scrive alla fine un carattere slash (/) per indicare esplicitamente che si tratta di una directory (mv fileprova repni/).

  • Dato che il filesystem in realtà conosce la natura dei file presenti (ovvero sa distinguere tra file "regolari", directory, ed altri file "speciali"), nel caso 1 l'effetto ottenuto è che fileprova si troverà dentro la directory repni/.

Directory

Move funziona esattamente nello stesso modo anche si dobbiamo spostare/rinomanare directory, anzi vi dirò di più, funziona spostando tutte le sotto-directory contenute all'interno di quella che stiamo spostando! Facciamo un'altro esempio, ma prima creiamo un po' di cartelle innestate (attenzione: ci troviamo all'interno della cartella repni/, creata in precedenza nella nostra home directory), per questo scopo ci serverimo di una opzione di mkdir che ci consente di creare sotto-directory senza prima entrarvici. Tale opzione è -p che crea tutte le directory specificate se non esistono.

 [manuel@acer repni ]$ mkdir dir1
 [manuel@acer repni ]$ mkdir -p dir1/dir1_2/dir1_2_3

Il primo comando (mkdir dir1) crea la directory dir1 come di consueto, il secondo (mkdir -p dir1/dir1_2/dir1_2_3), grazie all'opzione -p crea tutte le directory che non esistono nel path specificato, quindi creerà dir1_2 dentro dir1 e dir1_2_3 dentro dir1_2. Proviamo a dare un ls -l -R per avere conferma di ciò (l'opzione -R di ls permette di visitare tutte le sotto-directory una per una ricorsivamente):

[manuel@acer repni ]$ ls -l -R
.:
totale 4
drwxr-xr-x 3 manuel users 4096 10 dic 14.46 dir1
-rw-r--r-- 1 manuel users    0 10 dic 13.58 fileprova2

./dir1:
totale 4
drwxr-xr-x 3 manuel users 4096 10 dic 14.46 dir1_2

./dir1/dir1_2:
totale 4
drwxr-xr-x 2 manuel users 4096 10 dic 14.46 dir1_2_3

./dir1/dir1_2/dir1_2_3:
totale 0

Perfetto, abbiamo le nostre sotto-directory, ora non ci resta che creare un po' di file dentro di esse:

[manuel@acer repni ]$ touch dir1/prova1 dir1/prova2 dir1/prova3
[manuel@acer repni ]$ touch dir1/dir1_2/prova1 dir1/dir1_2/prova2 dir1/dir1_2/prova3
[manuel@acer repni ]$ touch dir1/dir1_2/dir1_2_3/prova1 dir1/dir1_2/dir1_2_3/prova2 dir1/dir1_2/dir1_2_3/prova3

In ogni sotto-directory abbiamo creato i file prova1, prova2 e prova3. Da questo ultimo esempio si può capire inoltre che il comando touch permette di specificare il path (relativi in questo caso) di più di un file per volta. Il nostro albero del filesystem (relativo al punto in cui siamo noi, ovvero la directory repni/ sarà fatto così:

./
├── dir1
│   ├── dir1_2
│   │   ├── dir1_2_3
│   │   │   ├── prova1
│   │   │   ├── prova2
│   │   │   └── prova3
│   │   ├── prova1
│   │   ├── prova2
│   │   └── prova3
│   ├── prova1
│   ├── prova2
│   └── prova3
└── fileprova2

Ora possiamo iniziare a giocare con il nostro amato comando mv.

Proviamo a spostare tutta la directory dir1_2 dentro repni/:

[manuel@acer repni ]$ ls
dir1  fileprova2
[manuel@acer repni ]$ mv dir1/dir1_2/ ./
[manuel@acer repni ]$ ls
dir1  dir1_2  fileprova2
[manuel@acer repni ]$ ls -l -R
.:
totale 8
drwxr-xr-x 2 manuel users 4096 10 dic 14.57 dir1
drwxr-xr-x 3 manuel users 4096 10 dic 14.53 dir1_2
-rw-r--r-- 1 manuel users    0 10 dic 13.58 fileprova2

./dir1:
totale 0
-rw-r--r-- 1 manuel users 0 10 dic 14.52 prova1
-rw-r--r-- 1 manuel users 0 10 dic 14.52 prova2
-rw-r--r-- 1 manuel users 0 10 dic 14.52 prova3

./dir1_2:
totale 4
drwxr-xr-x 2 manuel users 4096 10 dic 14.53 dir1_2_3
-rw-r--r-- 1 manuel users    0 10 dic 14.53 prova1
-rw-r--r-- 1 manuel users    0 10 dic 14.53 prova2
-rw-r--r-- 1 manuel users    0 10 dic 14.53 prova3

./dir1_2/dir1_2_3:
totale 0
-rw-r--r-- 1 manuel users 0 10 dic 14.53 prova1
-rw-r--r-- 1 manuel users 0 10 dic 14.53 prova2
-rw-r--r-- 1 manuel users 0 10 dic 14.53 prova3
[manuel@acer repni ]$

Abbiamo giocato un po', ora spieghiamo. Il primo comando ls lo usiamo per vedere ora cosa contiene repni/ la directory dove siamo ora. Il secondo (mv dir1/dir1_2/ ./) è più interessante perchè con mv spostiamo dir1_2/ (contenuta in dir1/ ) dentro repni/ ma la cosa interessante è come diciamo al comando la cartella di destinazione: con la scorciatoia ./.

La volta precedente abbiamo visto che il path può essere assoluto, quando inizia con root → / oppure può essere relativo, se ci riferiamo alla directory corrente, inoltre abbiamo visto che ../ è una sequenza di caratteri speciali che indica la directory padre (quella più su di un livello nell'albero del filesystem). Ma noi siamo in repni/ (come possiamo vedere dal prompt: [manuel@acer repni ]$) e vogliamo spostarla dentro alla directory dove siamo già. Ottimo, il terminale ci mette a disposizione un'altra sequenza di caratteri che ci permette di dirgli "questa cartella qua, dove siamo ora". Tale sequenza è quella che abbiamo usato, ovvero: ./

Perfetto ora se controlliamo nuovamente con ls le sotto-directory di repni/ scopriamo che ora dir1_2 risiede qui. Con l'ultimo comando ls -l -R controlliamo come è fatto l'albero del filesystem dal punto in cui siamo noi in poi. Scopriamo è fatto così:

repni/
├── dir1/
│   ├── prova1
│   ├── prova2
│   └── prova3
├── dir1_2/
│   ├── dir1_2_3/
│   │   ├── prova1
│   │   ├── prova2
│   │   └── prova3
│   ├── prova1
│   ├── prova2
│   └── prova3
└── fileprova2

Copia di file e directory - cp

Come copiare file e cartelle, se non con un comando che in piena filosofia Unix riassume l'equivalente nome inglese di copy: cp.

cp permette di copiare un file da una porzione di filesystem ad una destinazione, come al solito la sintassi è: cp <sorgente> <destinazione>.

[manuel@acer repni ]$ ls
dir1  dir1_2 fileprova2
[manuel@acer repni ]$ cp fileprova2 filep
[manuel@acer repni ]$ ls
dir1  dir1_2 filep  fileprova2
[manuel@acer repni ]$ 

Ecco qui un esempio, copiamo il file fileprova2 nella stessa cartella ma con un nuovo nome, filep. Vediamo ovviamente che il comando ls ci da conferma di quanto avvenuto.

Attenzione: il comando cp non da conferma di sovrascrittura, quindi se il file filep fosse già esistito sarebbe stato sovrascritto! Ma ovvimanete esistono le opzioni -n che forzano il comando a non sovrascrivere file esistenti, e -i che chiedono interattivamente se sovrascrivere il file: provate ;)

Il comando copy per le directory funziona un po' diversamente. Provate ad esempio a copiare la sotto-directory dir1_2_3, in dir1_2 nella directory corrente:

[manuel@acer repni ]$ cp dir1_2/dir1_2_3/ ./
cp: directory "dir1_2/dir1_2_3/" omessa
[manuel@acer repni ]$

Come vedere viene specificato che la directory è stata omessa, questo vuol dire per copiare delle intere sotto-directory (e quindi tutto il ramo dell'albero del filesystem che discende da li in poi) sia necessario forzare cp a farlo. Esiste anche per questo una opzione, -r che copia ricorsivamente.

Diversa la situazione in cui volessimo copiare solamente i file all'interno di dir1_2_3. Come fare? Semplice, usando un metacarattare ovvero uno speciale carattere che indica al terminale una particolare opzione: è questo il caso di * che indica qualsiasi sia il nome del file.

[manuel@acer repni ]$ cp dir1_2/dir1_2_3/* ./
[manuel@acer repni ]$ ls
dir1  dir1_2  filep  fileprova2  prova1  prova2  prova3
[manuel@acer repni ]$

Il comando cp dir1_2/dir1_2_3/* ./ significa "Copia tutti (*) i files contenuti in dir1_2/dir1_2_3/ nella cartella corrente (./)". In questo modo abbiamo copiato i file prova1, prova2 e prova3, contenuti in dir1_2/dir1_2_3, nella directory corrente (./) repni.

Nuovamente, se in dir1_2_3 ci fosse stata anche una cartella, la dicitura di omissione, sarebbe comparsa ancora.

Eliminare file o directory - rm e rmdir

L'eliminazione di file è una operazione semplice ma pericolosa. A differenza di quanto accade nelle shell grafiche, dove se diamo un comando di eliminazione il file viene semplicemente spostato in una locazione chiamata Cestino o Trash Bin o simile, quando diamo un comando per la cancellazione di un file (rm) esso è semplicemente rimosso dall'albero del filesystem senza possibilità di recupero!.

È bene quindi, essere certi del comando da dare e delle possibili conseguenze che potrebbe avere, poiché ad esempio la presenza di un metacarattere jolly (l'asterisco * visto in precedenza) usato male, potrebbe cancellarvi molti file che non volevate rimuovere.

Detto ciò, vediamo come è possibile rimuovere dei file. Negli esempi precedenti abbiamo copiato tre file da una sottodirectory nella directory corrente, eliminiamoli usando rm (sintassi: rm <file1> <file2> ...)

[manuel@acer repni ]$ rm prova1 prova2 prova3
[manuel@acer repni ]$ ls
dir1  dir1_2  filep  fileprova2
[manuel@acer repni ]$

Da notare che, come molti altri comandi, anche rm accetta molteplici nomi di file contemporaneamente. Ovviamente il nome può essere un path sia assoluto, sia relativo.
In questo caso, dato che i nomi sono tutti uguali, eccetto che per un numero alla fine, avremmo potuto usare il famoso metacarattere jolly, in questo modo:

[manuel@acer repni ]$ rm prova*
[manuel@acer repni ]$ ls
dir1  dir1_2  filep  fileprova2
[manuel@acer repni ]$

e come vedete il risultato è esattamente lo stesso. Il comando rm prova* infatti significa "elimina tutti i file il cui nome inizia per prova e che terminano in un modo qualsiasi".

Attenzione però, usando il jolly * se avessimo avuto un file prova4 che non volevamo eliminare, non avremmo potuto farlo.

Perchè non si è eliminato anche fileprova2? Ovviamente perchè prima dei caratterti prova esistono anche file quindi non è lo stesso insieme di nomi. Ma per queste cose interessanti rimando ad un articolo medio-avanzato sui Metacaratteri di Shell e le Espressioni Regolari. Proviamo ora ad eliminare la directory dir1, che contiene tre file:

[manuel@acer repni ]$ rm dir1
rm: impossibile rimuovere "dir1": Is a directory

Sebbene la descrizione di rm (man rm) sia "Rimuove file o directory" in realtà per rimuovere directory esiste un comando specifico, che le rimuove solo se esse suono vuote. Tale comando è rmdir:

[manuel@acer repni ]$ rmdir dir1
rmdir: rimozione di "dir1" non riuscita: La directory non è vuota

Appunto, solo se sono vuote, quindi teoricamente dovremmo eliminare tutti i file (e le directory) al suo interno.

Mettiamo l'ipotesi in cui, dentro a dir1, vi siano 234 directory, con ognuna 20 file. Capite che fare rm su ognuna delle sotto-directory per svuotarle e rimuoverle solo dopo vuote, per rimuovere la directory dir1 è poco intelligente e molto lungo.
Per questo ci viene in aiuto una opzione del comando rm che permette, come per cp di operare ricorsivamente: tale opzione è (ovviamente) -r

[manuel@acer repni ]$ rm -r dir1
[manuel@acer repni ]$ ls
dir1_2  filep  fileprova2
[manuel@acer repni ]$

Efficace e rapidissimo (in realtà è più o meno veloce a seconda di quante sotto-directory vi siano all'interno)

Logicamente rm riporta un'errore se la directory non esiste:

[manuel@acer repni ]$ rm dir1
rm: impossibile rimuovere "dir1": No such file or directory
[manuel@acer repni ]$

Un'ulteriore opzione interessante è -i che rende ogni eliminazione una domanda per l'utente, utile nel caso non si vogliano eliminare alcuni tra molti file. Proviamo: vogliamo eliminare tutti i file di dir1_2/ ma senza eliminare prova2 e prova3:

[manuel@acer repni ]$ rm -i dir1_2/*
rm: impossibile rimuovere "dir1_2/dir1_2_3": Is a directory
rm: rimuovere file regolare vuoto "dir1_2/prova1"? y
rm: rimuovere file regolare vuoto "dir1_2/prova2"? n
rm: rimuovere file regolare vuoto "dir1_2/prova3"? n

Vediamo subito che ci segnala l'impossibilità di eliminare una directory (per forza, avremmo dovuto usare anche -r se avessimo voluto, dopo proviamo!) e successivamente ci chiede, per ogni file, se vogliamo eliminarlo. Certo, se la lista di file è molto lunga, questo non è il modo migliore per farlo, ma ritorniamo all'argomento medio-avanzato su Metacaratteri di Shell e le Espressioni Regolari che prima o poi nella vostra vita vi consiglio di leggere (almeno la prima parte).

Riassumendo

Abbiamo visto come creare file e directory, come rinominarli, spostarli, copiarli, ed eliminarli, ed abbiamo introdotto un concetto fondamentale per i computer di oggi: il filesystem.
Alla prossima per vedere come gestire i permessi di accesso a file e directory.

  1. I primi passi
  2. Gestire i file
  3. Permessi su file e directory