Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

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

38 réponses
Avatar
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

10 réponses

1 2 3 4
Avatar
Nicolas George
YBM , dans le message <4f4e7a13$0$12416$, a
écrit :
Je me suis contenté de répondre à une question précise par une réponse
précise, point-barre



Non, tu as re-donné une deuxième fois ta réponse dans un contexte où elle
n'était plus pertinente.
Avatar
Francois Lafont
Le 29/02/2012 16:31, Nicolas George a écrit :

Francois Lafont , dans le message
<4f4e43b5$0$10697$, 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.



C'est pour attraper ..truc mais pas .. tout court.



Ok, je pense avoir compris. Avec "$dir"/* et "$dir"/.[!.]* on attrape
tout sauf :

a) « . » (mais lui on n'en veut pas donc ce n'est pas grave)
b) « .. » (mais lui on n'en veut pas non plus donc ce n'est pas grave)
c) les « ..??? » avec un nombre de ? non nul et ceux-là on les attrapera
avec le fameux "$dir"/..?*

"/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.



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.



Je viens de regarder la page de manuel du bash et la règle est la
suivante : (dans la sous partie "Pathname Expansion" de la partie EXPANSION)

« When a pattern is used for pathname expansion, the character ’’.’’ at
the start of a name or immediately following a slash must be matched
explicitly, unless the shell option dotglob is set. When matching a
pathname, the slash character must always be matched explicitly. In
other cases, the ’’.’’ character is not treated specially. »

Autrement dit, *par défaut*, les jokers (*, ? et compagnie) ne
permettront jamais à eux seuls d'attraper les slash et les points qui se
trouvent juste après un slash ou au début d'un nom. Pour pouvoir
attraper ces machins là, il faut les écrire explicitement dans la commande.


--
François Lafont
Avatar
Francois Lafont
Le 29/02/2012 16:57, Luc Habert a écrit :

for f in "$dir"/* "$dir"/.[!.]* "$dir"/..?*; do



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.




? 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).



Ok, ça c'est clair maintenant. Je ne comprenais pas ton test dans ton
exemple que je remets ici pour mémoire :

#-------------------------------------------------
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
#-------------------------------------------------

Mais en écrivant le message, je viens de comprendre : parmi la liste des
3 motifs, peut-être que certains ne correspondent à aucun fichier dans
"$dir" (par exemple "$dir" ne contient peut-être aucun fichier de la
forme ..xxx) et donc dans ce cas il faut passer à l'itération suivante.

Du coup, quand même, si c'est pour lister (ou traiter) les fichiers
contenu à la racine d'un dossier, la solution de Sergio est quand même
vraiment bien et simple je trouve :

#-------------------------------------
shopt -s dotglob
for f in "/dossier/"*; do
# traitement de "$f"
done
shopt -u dotglob
#-------------------------------------

En plus, par chance, avec « shopt -s dotglob », on n'attrape ni « . »,
ni « .. » ce qui n'était pas forcément évident au départ quand je lis
dans man bash :

« dotglob : if set, bash includes filenames beginning with a `.' in the
results of pathname expansion. »

Du coup, on aurait pu s'attendre à choper « .. » et « . » avec cette
option. Mais c'est mieux ainsi.

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).



Celui-là je le méditerai plus tard car pour l'instant ça me dépasse
(c'est pire que du LaTeX cette histoire :-)).



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).



Ok.

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.



Ok. Le « ploum » me posait problème mais toujours dans le man bash, il y a :

« -c string : If the -c option is present, then commands are read from
string. If there are arguments after the string, they are assigned to
the positional parameters, starting with $0. »

Donc effectivement, il faut bien le ploum. :-)

Enfin pour en finir avec ce code, je ne comprenais pas la présence des
doubles-quotes dans « "$@" ». En effet, cet exemple :

#-------------------------------------
$ for f in "aaa bbbb ccccc"; do echo "--$f--"; done
--aaa bbbb ccccc--
#-------------------------------------

m'incitait à penser que les doubles-quotes ferait de $@ un seul et
unique mot. Mais apparemment $@ est spécial et il est écrit
explicitement dans le manuel que "$@" (qu'on pourrait penser
légitimement n'être qu'un seul et unique mot) est développé en "$1" "$2"
etc. (soit plusieurs mots en fin de compte). Encore une feinte
supplémentaire. :-)

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.



Pour la portabilité, ça sera pour une autre vie je crois. :-)

Au passage, tu notais (à raison) que mon exemple avec find et $REPLY ne
gérait pas bien les noms de fichiers qui contenaient par exemple 'n'.
Finalement, après réflexions, j'ai pensé à l'option -r de read qui
résout ce problème :

#-------------------------------------
find ... | while read -r; do
# traitement de "$REPLY"
done
#-------------------------------------

Ok, il reste ce "$REPLY" qui est du bash pur jus, donc pas portable.
Mais bon, récupérer la sortie d'un find pour faire des traitements sur
les fichiers, le tout de manière portable et en gérant bien les noms de
fichiers un peu exotiques, ça n'a pas l'air d'être quelque chose de
facile à faire.

Parce que « .* » attrape « .. ».



Ok, mais j'ai du mal à comprendre la logique dans cette histoire.



C'est un piège à la con, faut pas chercher la logique.



C'est pas mal résumé. :-)

(cd "$dir" && tar cf - .) | (cd "$dest" && tar xf -)



À méditer aussi celui-là étant donné que je connais très mal la commande
tar.



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.



Ok.

Merci beaucoup pour toutes ces explications. Je crois que tout est clair
maintenant.

Si je résume :

1) Pour lister le contenu à la racine d'un dossier, j'aime bien la
solution de Sergio :

#-------------------------------------
shopt -s dotglob
for f in "/dossier/"*; do
# traitement de "$f"
done
shopt -u dotglob
#-------------------------------------

1bis) Si la liste des fichiers est la sortie d'un find, alors j'aime
bien ma solution (forcément :-)) avec "read -r" et "$REPLY" mais je
retiens le coup du xargs que tu m'as expliqué.

2) Pour copier le contenu d'un dossier dans un autre dossier :

# La solution de YBM
cp -r /source/. /cible/

# et la tienne
(cd "$dir" && tar cf - .) | (cd "$dest" && tar xf -)

Merci encore.
À+

--
François Lafont
Avatar
Francois Lafont
Le 29/02/2012 18:40, denis.paris a écrit :

cp -r copie le répertoire source en bloc dans un répertoire de même nom,
qu'il crée si nécessaire. Si tu écris « cp -r /some/path/source », il
veut
copier le répertoire qui s'appelle source, si tu écrirs « cp -r
/some/path/source/. », il veut copier le répertoire ., qui s'avère
être à la
source être exactement le même que source, et idem à la cible.



Je n'ai pas compris (peut-être des mots en trop, ou en moins?)



Ce que veut dire Nicolas, il me semble (qu'on me rectifie hein :-)),
c'est qu'avec "/some/path/source/.", la commande cp n'a pas besoin de
créer un répertoire dans le répertoire cible car le nom du répertoire
source est « . » et il existe *déjà* un répertoire avec ce nom là dans
le répertoire cible.

--
François Lafont
Avatar
Luc.Habert.00__arjf
Francois Lafont :

Au passage, tu notais (à raison) que mon exemple avec find et $REPLY ne
gérait pas bien les noms de fichiers qui contenaient par exemple 'n'.
Finalement, après réflexions, j'ai pensé à l'option -r de read qui
résout ce problème :

#-------------------------------------
find ... | while read -r; do
# traitement de "$REPLY"
done
#-------------------------------------



Bah non, ça ne le résoud pas. Ça résoud juste le cas où tu as des dans le
nom de fichier, que j'avais négligé de citer.
Avatar
denis.paris
Le 01/03/2012 04:14, Francois Lafont a écrit :
Le 29/02/2012 18:40, denis.paris a écrit :

cp -r copie le répertoire source en bloc dans un répertoire de même nom,
qu'il crée si nécessaire. Si tu écris « cp -r /some/path/source », il
veut
copier le répertoire qui s'appelle source, si tu écrirs « cp -r
/some/path/source/. », il veut copier le répertoire ., qui s'avère
être à la
source être exactement le même que source, et idem à la cible.



Je n'ai pas compris (peut-être des mots en trop, ou en moins?)



Ce que veut dire Nicolas, il me semble (qu'on me rectifie hein :-)),
c'est qu'avec "/some/path/source/.", la commande cp n'a pas besoin de
créer un répertoire dans le répertoire cible car le nom du répertoire
source est « . » et il existe *déjà* un répertoire avec ce nom là dans
le répertoire cible.




OK, c'est beaucoup plus clair.
Avatar
Nicolas George
Francois Lafont , dans le message
<4f4ee7c8$0$16558$, a écrit :
Du coup, quand même, si c'est pour lister (ou traiter) les fichiers
contenu à la racine d'un dossier, la solution de Sergio est quand même
vraiment bien et simple je trouve :
shopt -s dotglob
for f in "/dossier/"*; do
# traitement de "$f"



Non, il faut quand même faire le test, parce qu'il pourrait n'y avoir rien
du tout dans le répertoire en question.

(« Dossier » est un GEMisme/microsoftisme.)
Avatar
Francois Lafont
Le 01/03/2012 10:15, Luc Habert a écrit :

Au passage, tu notais (à raison) que mon exemple avec find et $REPLY ne
gérait pas bien les noms de fichiers qui contenaient par exemple 'n'.
Finalement, après réflexions, j'ai pensé à l'option -r de read qui
résout ce problème :

#-------------------------------------
find ... | while read -r; do
# traitement de "$REPLY"
done
#-------------------------------------



Bah non, ça ne le résoud pas. Ça résoud juste le cas où tu as des dans le
nom de fichier, que j'avais négligé de citer.



Est-ce que tu as un exemple de nom qui ne marcherait pas dans ce cas là ?

Si je comprends bien, si on veut être « béton » au niveau des noms de
fichiers, "find ... -print0 | xargs ..." est alors le seul moyen ?


--
François Lafont
Avatar
Francois Lafont
Le 01/03/2012 10:48, Nicolas George a écrit :

Francois Lafont , dans le message
<4f4ee7c8$0$16558$, a écrit :
Du coup, quand même, si c'est pour lister (ou traiter) les fichiers
contenu à la racine d'un dossier, la solution de Sergio est quand même
vraiment bien et simple je trouve :
shopt -s dotglob
for f in "/dossier/"*; do
# traitement de "$f"



Non, il faut quand même faire le test, parce qu'il pourrait n'y avoir rien
du tout dans le répertoire en question.



Ah oui en effet.

(« Dossier » est un GEMisme/microsoftisme.)



Je ne savais pas. J'essayerai de m'en tenir au mot répertoire.


--
François Lafont
Avatar
Luc.Habert.00__arjf
Francois Lafont :

Est-ce que tu as un exemple de nom qui ne marcherait pas dans ce cas là ?



Un nom de fichier avec un n dedans. Il se fera couper en deux.

Si je comprends bien, si on veut être « béton » au niveau des noms de
fichiers, "find ... -print0 | xargs ..." est alors le seul moyen ?



En fait, il y en a un autre, à savoir

find ... -exec commande '{}' +

qui fait faire à find le boulot d'xargs. Et celui-ci est standard (la raison
qu'ils invoquent pour avoir inventer le find + plutot que de standardiser le
print0 est à se frapper la tete contre les murs). J'ai pris l'habitude
d'utiliser print0 et xargs à une époque où GNU find ne supportait pas encore
le +, et résultat, je ne pense jamais au find +.
1 2 3 4