[bash] lister/copier le contenu situé à la racine d'un dossier sans rien oublier ( comme les dot-files et les « space-files »)
Le
Francois Lafont

Bonjour à tous,
J'ai deux soucis. Dans les deux cas, je précise une fois pour toutes que
je souhaite obtenir des solutions qui gèrent correctement les noms de
fichiers avec des espaces (au début et/ou au milieu et/ou à la fin)
ainsi que les fichiers avec un nom de la forme .xxx (ie un dot-file).
1) Je souhaite lister le contenu situé à la racine d'un dossier
/dossier, c'est-à-dire tous les noms des fichiers-dossiers se trouvant à
la racine de /dossier. Alors, je pense naturellement à :
#-
for i in $(ls -A "/dossier"); do
echo "--$i--"
done
#-
Je « chope » bien les dot-files, mais ça coince avec des
fichiers-dossiers dont le nom contient des espaces. C'est un problème
simple mais pourtant je n'ai pas trouvé mieux que ça pour le résoudre :
#-
find "/dossier" -maxdepth 1 -mindepth 1 | while read; do
nom=$(basename "$REPLY")
echo "--$nom--"
done
#-
Y a-t-il plus simple que ça ? (J'espère que oui quand même).
2) Je souhaite copier tout le contenu du dossier /source vers le dossier
/cible.
« cp -r /source /cible » ne marche pas car il copie le dossier "source"
dans le dossier "cible" alors que je veux copier le contenu de "source".
« cp -r /source/* /cible » ne copie pas les dot-files. Si je fais alors
« cp -r /source/.* /cible », pour des raisons que je ne comprends pas
très bien d'ailleurs (si vous avez une explication au passage ça
m'intéresse), il y a tentative de copie de ce qui se trouve « au dessus
» de /source. Là aussi, pour un problème assez simple en somme, je n'ai
pas trouvé mieux que ça :
#-
find "/source" -maxdepth 1 -mindepth 1 | while read; do
cp -r "$REPLY" "/cible"
done
#-
Y a-t-il plus simple que ça également ?
--
François Lafont
J'ai deux soucis. Dans les deux cas, je précise une fois pour toutes que
je souhaite obtenir des solutions qui gèrent correctement les noms de
fichiers avec des espaces (au début et/ou au milieu et/ou à la fin)
ainsi que les fichiers avec un nom de la forme .xxx (ie un dot-file).
1) Je souhaite lister le contenu situé à la racine d'un dossier
/dossier, c'est-à-dire tous les noms des fichiers-dossiers se trouvant à
la racine de /dossier. Alors, je pense naturellement à :
#-
for i in $(ls -A "/dossier"); do
echo "--$i--"
done
#-
Je « chope » bien les dot-files, mais ça coince avec des
fichiers-dossiers dont le nom contient des espaces. C'est un problème
simple mais pourtant je n'ai pas trouvé mieux que ça pour le résoudre :
#-
find "/dossier" -maxdepth 1 -mindepth 1 | while read; do
nom=$(basename "$REPLY")
echo "--$nom--"
done
#-
Y a-t-il plus simple que ça ? (J'espère que oui quand même).
2) Je souhaite copier tout le contenu du dossier /source vers le dossier
/cible.
« cp -r /source /cible » ne marche pas car il copie le dossier "source"
dans le dossier "cible" alors que je veux copier le contenu de "source".
« cp -r /source/* /cible » ne copie pas les dot-files. Si je fais alors
« cp -r /source/.* /cible », pour des raisons que je ne comprends pas
très bien d'ailleurs (si vous avez une explication au passage ça
m'intéresse), il y a tentative de copie de ce qui se trouve « au dessus
» de /source. Là aussi, pour un problème assez simple en somme, je n'ai
pas trouvé mieux que ça :
#-
find "/source" -maxdepth 1 -mindepth 1 | while read; do
cp -r "$REPLY" "/cible"
done
#-
Y a-t-il plus simple que ça également ?
--
François Lafont
shopt -s dotglob
for i in * ; do
echo "--$i--"
done
--
Serge http://leserged.online.fr/
Mon blog: http://cahierdesergio.free.fr/
Soutenez le libre: http://www.framasoft.org
En shell:
for f in "$dir"/* "$dir"/.[!.]* "$dir"/..?*; do
test -e "$f" || continue; # si un pattern ne matche rien, il est expansé
# en lui-même et non en rien
# do something with $f
done
Avec find, tu peux jouer avec -printf (mais ça ne marche qu'avec GNU find).
Note que la manière dont tu exploites la sortie de find est mauvaise, car tu
te fais avoir par les n dans les noms de fichiers. Ça s'exploite comme ça:
find .... -print0 | xargs -0 sh -c 'for f in "$@"; do something with "$f"; done' ploum
(le ploum sert à décaler les arguments ajoutés par xargs pour qu'ils soient
tous dans $@, tandis que ploum occupe $0).
fait de la merde sur les symlinks et les liens durs multiples. Il faut
utiliser cp -a, sauf si tu es sur un fs non unix.
Parce que « .* » attrape « .. ». C'est pour ça que je me suit fait chier à
« "$dir"/.[!.]* "$dir"/..?* » plus haut (<headdesk>note que pour nier un
ensemble de caractères en shell, il faut mettre un « ! » et non « ^ »,
contrairement aux regexps</headdesk>).
Perso, je fais :
(cd "$dir" && tar cf - .) | (cd "$dest" && tar xf -)
Outre la solution utilisant tar donnée par Luc qui est très efficace
pour transférer de grande quantités de données tu peux faire :
cp -r /source/. /cible/
(cp -a pour préserver les droits, liens symboliques, etc.)
Merci pour cette réponse Sergio, je ne connaissais pas le coup du «
shopt -s dotglob ». Du coup, effectivement, pour le problème 1, je peux
faire :
#-------------------------------------
shopt -s dotglob
for f in "/dossier/"*; do
echo "--$f--"
done
shopt -u dotglob
#-------------------------------------
Et pour le problème 2 (celui de la copie), du coup je peux faire :
#-------------------------------------
shopt -s dotglob
for f in "/source/"*; do
cp -ar "$f" "/cible"
done
shopt -u dotglob
#-------------------------------------
Effectivement comme ça, c'est quand même plus simple.
Je pensais réellement que le coup du joker * allait se planter
lamentablement dans le cas de noms de fichier avec des espaces mais à ma
grande surprise ce n'est pas le cas. Je pensais que :
« for f in "/dossier/"*; do »
se développait en :
« for f in "/dossier/"to to; do »
dans le cas où le dossier contient uniquement le fichier "to to". Mais
apparemment, c'est un peu plus subtil que ça...
--
François Lafont
Le 29/02/2012 09:33, Luc Habert a écrit :
Je comprends bien le "$dir"/* et le "$dir"/.[!.]* (le premier attrape
tout ce qui n'est pas de la forme .xxx et le second tout ce qui est de
la forme .yxxx avec y = n'importe quoi sauf un point), mais le troisième
je n'arrive pas à comprendre ce qu'il signifie et à quoi il sert.
Exact, en fait je ne pensais pas qu'on pouvait nommer un fichier
'aaanbbb' par exemple. Mais effectivement on peut et avec « ma »
méthode (avec find) le nom devient 'aaanbbb' (rq : avec la méthode de
Sergio, il n'y a pas ce problème).
Celui-là je le méditerai plus tard car pour l'instant ça me dépasse
(c'est pire que du LaTeX cette histoire :-)).
Oui, tout à fait. J'avais oublié l'option -a.
Ok, mais j'ai du mal à comprendre la logique dans cette histoire.
"/dossier/"* n'attrape pas "/dossier/".truc autrement dit le * ne se
développe pas en ".xxx". Par contre "/dossier/".* peut attraper
"/dossier/"..xxx autrement dit dans ce cas là le * peut se développer en
".xxx". Je ne trouve pas ça logique. En gros ce que je comprenais, c'est
que le * peut se développer en n'importe quoi mais pas en un truc qui
commence par un point. Et bien l'exemple du /dossier/.* contredit cela.
Ok pour le "$dir"/.[!.]* mais le "$dir"/..?* je ne vois vraiment pas.
Oui c'est dommage que tout cela ne coïncide pas avec les regexps mais bon...
À méditer aussi celui-là étant donné que je connais très mal la commande
tar.
Merci pour ta réponse Luc.
--
François Lafont
C'est pour attraper ..truc mais pas .. tout court.
Ce n'est pas fait pour être logique, c'est fait pour être pratique. Mais ce
n'est pas si absurde que ça : * et ? reconnaissent tout sauf un . en début
de nom de fichier, parce que c'est uniquement là que le point est spécial.
? matche un caractère quelconque. donc ..?* matche tout ce qui commence par
.. et est suivi d'au moins un caractère (je veux exclure .. tout court).
find ... -print0
pond sur sa sortie standard la liste des fichiers, séparés par des
caractères nuls (le seul caractère qui ne peut pas etre contenu dans un nom
de fichier (sinon ça foutrait en l'air les 3/4 de la libc) et évite donc les
ambiguités).
xargs -0 commande
lit sur son stdin en splittant sur les caractères nuls, puis appelle
commande en ajoutant comme arguments les champs obtenus de stdin. Si le
système a une limite sur la taille de l'argv (ce qui n'est plus le cas de
linux), il découpe en plusieurs appels.
Ici, j'ai mis en guise de commande
sh -c 'for f in "$@"; do something with "$f"; done' ploum
qui appelle un sh temporaire pour exécuter des commandes shell sur chaque
argument.
J'ai oublié de préciser que le -print0/-0 n'était pas standard, mais il me
semble que c'est au moins supporté sous BSD aussi.
C'est un piège à la con, faut pas chercher la logique.
tar cf - répertoire
pond sur son stdout un flux d'octets décrivant l'arborescence située sous
répertoire (y compris le contenu des fichiers)
tar xf -
lit sur son stdin un flux d'octets pondu par un autre tar, et le réplique
dans son répertoire courant.
Il y a quand même bien plus simple...
:/tmp/a$ ls -la source/
total 16
drwxr-xr-x 4 user user 4096 2012-02-29 17:02 .
drwxr-xr-x 4 user user 4096 2012-02-29 17:02 ..
-rw-r--r-- 1 user user 0 2012-02-29 17:02 a
-rw-r--r-- 1 user user 0 2012-02-29 17:02 .a
drwxr-xr-x 2 user user 4096 2012-02-29 17:02 dir
drwxr-xr-x 2 user user 4096 2012-02-29 17:02 .dir
:/tmp/a$ cp -a source/. cible/
:/tmp/a$ ls -la cible/
total 16
drwxr-xr-x 4 user user 4096 2012-02-29 17:02 .
drwxr-xr-x 4 user user 4096 2012-02-29 17:02 ..
-rw-r--r-- 1 user user 0 2012-02-29 17:02 a
-rw-r--r-- 1 user user 0 2012-02-29 17:02 .a
drwxr-xr-x 2 user user 4096 2012-02-29 17:02 dir
drwxr-xr-x 2 user user 4096 2012-02-29 17:02 .dir
Effectivement c'est très simple et ça marche.
En revanche, je ne comprends pas pourquoi la présence de ce fichu point
change tout le comportement de la commande cp ? Car au final /soure/ ou
source/. c'est la même chose non ? J'ai beau regarder la page man de cp,
je ne vois pas d'explication là-dessus.
Merci pour l'information en tout cas.
--
François Lafont
Parce que ça a été programmé comme ça...
C'est considéré comme la meme chose par les appels systèmes. Mais rien
n'empeche un programme qui les manipule de faire des finasseries. Perso, ça
me rappelle toutes les blagues avec copy et xcopy et ça me donne plus envie
de vomir qu'autre chose.