Indice dei contenuti
Vi trovate nella condizione di dover copiare un intero albero di filesystem (ad esempio $HOME), da una directory ad un'altra, la directory di origine contiene dei file e/o delle directory nascoste, quelle tipicamente precedute da un punto (. più avanti DOT) e chiamate anche hidden files/directories o dotted files/directories.
Il nostro obiettivo[1] è copiare ricorsivamente tutto il contenuto della directory con un solo comando nel modo più pulito ed elegante possibile.
Se hai fretta vedi #La soluzione oppure #Un po' di verità.
Premettiamo che stiamo lavorando in Bash ed in particolar modo con la versione 3.1.0(1)-release su Debian Etch, è utile precisarlo perchè non tutte le versioni di Bash si comportano allo stesso modo e soprattutto non tutte le shell.
$ bash --version GNU bash, version 3.1.0(1)-release (i486-pc-linux-gnu) Copyright (C) 2005 Free Software Foundation, Inc.
Inoltre nel resto dell'articolo si userà questa convenzione:
Per simulare questo scenario occorre creare una directory e metterci dentro alcuni file. Scegliamo di lavorare nella directory tmp della nostra $HOME.
$ mkdir -p ~/tmp/hidden/sor
$ touch ~/tmp/hidden/sor/{.d,.do,.dot,..double,.dot.dot,...triple,....quadruple,visibile,'visible with space'}
$ mkdir ~/tmp/hidden/sor/.sorsor
$ touch ~/tmp/hidden/sor/.sorsor/somefile
In questo modo abbiamo creato all'interno di sor qualche file nascosto ed una directory; i file hanno nomi assurdi che difficilmente troverete all'interno di qualche filesystem, ma saranno utili per spiegare gli esempi. L'ultima cosa che rimane da fare è creare una directory di destinazione.
$ mkdir ~/tmp/hidden/des
Questa directory andrà svuotata spesso, il modo più rapido per farlo è:
$ rm -rf ~/tmp/hidden/des; mkdir ~/tmp/hidden/des
Ora posizioniamoci nel path giusto e inziamo:
$ cd ~/tmp/hidden $ pwd /home/$USER/tmp/hidden $ ls -Ra sor/ sor/: . .. .d .do .dot .dot.dot ..double ....quadruple .sorsor ...triple visibile visible with space sor/.sorsor: . .. somefile
L'utility standard cp è solitamente quella utilizzata proprio per copiare file e directory, solo che in questo caso risulta piuttosto limitata, infatti il comando
$ cp -rav sor/* des/ `sor/visibile' -> `des/visibile' `sor/visible with space' -> `des/visible with space'
non copia affatto tutti i file, ma solo quelli visibili con ls. Ora potremmo anche lanciare un secondo comando, tipo
$ cp -rav sor/.* des/ `sor/./.d' -> `des/./.d' `sor/./.do' -> `des/./.do' `sor/./.dot' -> `des/./.dot' `sor/./..double' -> `des/./..double' `sor/./.dot.dot' -> `des/./.dot.dot' `sor/./...triple' -> `des/./...triple' `sor/./....quadruple' -> `des/./....quadruple' `sor/./visibile' -> `des/./visibile' `sor/./visible with space' -> `des/./visible with space' `sor/./.sorsor' -> `des/./.sorsor' `sor/./.sorsor/somefile' -> `des/./.sorsor/somefile' cp: impossibile creare l'hard link `des/sor' alla directory `des/.' `sor/../des' -> `des/des' `sor/../des/visibile' -> `des/des/visibile' `sor/../des/visible with space' -> `des/des/visible with space' `sor/../des/.d' -> `des/des/.d' `sor/../des/.do' -> `des/des/.do' `sor/../des/.dot' -> `des/des/.dot' `sor/../des/..double' -> `des/des/..double' `sor/../des/.dot.dot' -> `des/des/.dot.dot' `sor/../des/...triple' -> `des/des/...triple' `sor/../des/....quadruple' -> `des/des/....quadruple' cp: impossibile copiare la directory `sor/..' dentro sè stessa, `des/' cp: impossibile copiare la directory `sor/..' dentro sè stessa, `des/' `sor/.d' -> `des/.d' `sor/.do' -> `des/.do' `sor/.dot' -> `des/.dot' `sor/.dot.dot' -> `des/.dot.dot' `sor/..double' -> `des/..double' `sor/....quadruple' -> `des/....quadruple' cp: impossibile creare l'hard link `des/.sorsor' alla directory `des/./.sorsor' `sor/...triple' -> `des/...triple'
Ora, già dagli errori si capisce che l'operazione non è andata a buon fine, infatti utilizzando la wildcard `.*' abbiamo erroneamente interagito con gli hardlink (.) e (..) presenti in ogni directory. Questi link rappresentano rispettivamente la directory stessa e quella superiore e sono indispensabili al sistema. Se listiamo il contenuto di des ci accorgiamo che il nostro comando ha operato ricorsivamente, producendo quanto segue.
$ ls -Ra des/ des/: . .. .d des .do .dot .dot.dot ..double ....quadruple .sorsor ...triple visibile visible with space des/des: . .. .d .do .dot .dot.dot ..double ....quadruple ...triple visibile visible with space des/.sorsor: . .. somefile
Si potrebbe pensare allora di ricorrere a questo trucchetto:
$ cp -rav sor/.??* des/ `sor/.do' -> `des/.do' `sor/.dot' -> `des/.dot' `sor/.dot.dot' -> `des/.dot.dot' `sor/..double' -> `des/..double' `sor/....quadruple' -> `des/....quadruple' `sor/.sorsor/somefile' -> `des/.sorsor/somefile' `sor/...triple' -> `des/...triple'
che però a due debolezze:
Un'altra soluzione potrebbe essere il comando che segue, che però, lo diciamo subito, sposta solo il problema "più in là", ovvero copia .d, ma omette ..double, ...triple, etc., ovvero copia solo i file preceduti ad un solo DOT.
$ cp -rav sor/.[!.]* des/ `sor/.d' -> `des/.d' `sor/.do' -> `des/.do' `sor/.dot' -> `des/.dot' `sor/.dot.dot' -> `des/.dot.dot' `sor/.sorsor/somefile' -> `des/.sorsor/somefile'
Fino ad ora abbiamo visto soluzioni parziali, alcune drammaticamente problematiche ed altre che risolvono il problema solo parzialmente, a questo punto, quindi, pare che la soluzione sia nel combinare le varie possibilità in un solo comando cp, capace di copiare ricorsivamente l'interno contenuto della directory, compresi gli elementi preceduti da uno o più DOT, ma senza interagire con gli hardlink (.) e (..). Eccola.
$ cp -rav sor/{[!.],.[!.],..?}* des/
`sor/visibile' -> `des/visibile'
`sor/visible with space' -> `des/visible with space'
`sor/.d' -> `des/.d'
`sor/.do' -> `des/.do'
`sor/.dot' -> `des/.dot'
`sor/.dot.dot' -> `des/.dot.dot'
`sor/.sorsor' -> `des/.sorsor'
`sor/.sorsor/somefile' -> `des/.sorsor/somefile'
`sor/....quadruple' -> `des/....quadruple'
`sor/...triple' -> `des/...triple'
`sor/..double' -> `des/..double'
Spieghiamo il comando fra breve, ma intando godiamoci il risultato: sor e des ora hanno lo stesso contenuto.
$ ls -Ra des/ des/: . .. .d .do .dot .dot.dot ..double ....quadruple .sorsor ...triple visibile visible with space des/.sorsor: . .. somefile
~$ cp -rav sor/{[!.],.[!.],..?}* des/
Questo codice a prima vista folle è molto semplice da spiegare, il cuore sta nella brace expansion, ovvero nei caratteri posti tra {..}, questo particolare tipo di wildcard infatti è utile per comporre una stringa arbitraria che usi le parole (separate da virgola) all'interno delle parentesi esattamente nell'ordine fornito. Ad esempio:
~$ echo {aa,xx,qq,dd}suffix
aasuffix xxsuffix qqsuffix ddsuffix
Nel nostro caso le parole sono a loro volta delle wildcard che, analizzando il loro prefisso in unione con `*' che rappresenta ogni carattere, escludono e/o includono particolari file e directory.
L'ordine delle tre wildcard in questo caso è indifferente, ma come si è potuto vedere nel primo listato de #La soluzione durante le operazioni di copia viene rispettato.
In vero abbiamo fatto le cose semplici in modo complesso. Per raggiugere il nostro risultato in un colpo solo avremmo potuto semplicemente digitare (posto che des non esista e che non sia scritta così des/):
$ cp -rav sor des
senza usare alcuna wildcard e pace all'anima. Ma quello visto sopra ci ha insegnato a usare proprio le wildcard, quindi adesso saremo in grado di copiare selettivamente solo alcuni file dalla directory, magari proprio solo quelli nascosti senza preoccuparci di (.) e (..):
$ cp -rav sor/{.[!.],..?}* des
Ovvio che le wildcard possono essere utilizzate anche per cancellare o spostare o fare altre operazioni. Ad esempio per cancellare solo i DOT file senza toccare (.) e (..):
$ rm -rvf des/{.[!.],..?}*
removed `des/.d'
removed `des/.do'
removed `des/.dot'
removed `des/.dot.dot'
removed `des/.sorsor/somefile'
removed directory: `des/.sorsor'
removed `des/....quadruple'
removed `des/...triple'
removed `des/..double'
$
$ ls -Ra des/
des/:
. .. visibile visible with space
Il problema di cui sopra può essere risolto piuttosto brillantemente con soluzioni differenti da cp. Volersi incaponire con cp tuttavia mantiene un buon valore didattico e aiuta nel caso non siano presenti altri strumenti.
rsync è la migliore utility per questo tipo di lavoro: ha una sinstassi davvero ridotta al minimo (ma può essere arricchita a piacere con man rsync), fa il lavoro in modo molto efficace e pulito, è piuttosto portabile, ma occorre ovviamente che sia installato, non essendo tra il corredo minimo di tutte le distribuzioni.
$ rsync -av sor/ des/ building file list ... done ./ ....quadruple ...triple ..double .d .do .dot .dot.dot visibile visible with space .sorsor/ .sorsor/somefile sent 671 bytes received 252 bytes 1846.00 bytes/sec total size is 0 speedup is 0.00
Fatto.
E' possibile usare find in combinazione con cp, anche in questo caso le wildcard viste prima sono un toccasana.
$ find sor/{[!.],.[!.],..?}* -exec cp -rav {} des/ \;
`sor/visibile' -> `des/visibile'
`sor/visible with space' -> `des/visible with space'
`sor/.d' -> `des/.d'
`sor/.do' -> `des/.do'
`sor/.dot' -> `des/.dot'
`sor/.dot.dot' -> `des/.dot.dot'
`sor/.sorsor/somefile' -> `des/.sorsor/somefile'
`sor/.sorsor/somefile' -> `des/somefile'
`sor/....quadruple' -> `des/....quadruple'
`sor/...triple' -> `des/...triple'
`sor/..double' -> `des/..double'
A prima vista sembra tutto corretto, eppure:
`sor/.sorsor/somefile' -> `des/.sorsor/somefile' `sor/.sorsor/somefile' -> `des/somefile'
Ecco un errore: ha copiato `des/somefile' che invece non ragione d'essere copiato in quella posizione! Eppure:
$ find sor/{[!.],.[!.],..?}* -print
sor/visibile
sor/visible with space
sor/.d
sor/.do
sor/.dot
sor/.dot.dot
sor/.sorsor
sor/.sorsor/somefile
sor/....quadruple
sor/...triple
sor/..double
Leggendo man find con attenzione però si trova questo:
To ignore a whole directory tree, use -prune rather than checking every file in the tree.
Quindi proviamo un po' ...
$ find sor/{[!.],.[!.],..?}* -prune -exec cp -rav {} des/ \;
`sor/visibile' -> `des/visibile'
`sor/visible with space' -> `des/visible with space'
`sor/.d' -> `des/.d'
`sor/.do' -> `des/.do'
`sor/.dot' -> `des/.dot'
`sor/.dot.dot' -> `des/.dot.dot'
`sor/.sorsor' -> `des/.sorsor'
`sor/.sorsor/somefile' -> `des/.sorsor/somefile'
`sor/..double' -> `des/..double'
`sor/....quadruple' -> `des/....quadruple'
`sor/...triple' -> `des/...triple'
Note al documento
Alcune altre informazioni su questo problema possono essere lette in questo thread dell'ottobre 2004 sulla lista di Fedora che questo intervento intende completare e "risolvere": OT (ish): How to copy hidden files/directories