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
Sergio
Le Wed, 29 Feb 2012 04:11:37 +0100, Francois Lafont a écrit :


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 :



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
Avatar
Luc.Habert.00__arjf
Francois Lafont :

1) Je souhaite lister le contenu situé à la racine d'un dossier
/dossier



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

2) Je souhaite copier tout le contenu du dossier /source vers le dossier
/cible.

« cp -r /source /cible » ne marche pas car il



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.


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.



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

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 ?



Perso, je fais :

(cd "$dir" && tar cf - .) | (cd "$dest" && tar xf -)
Avatar
YBM
Francois Lafont a écrit :
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.



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.)
Avatar
Francois Lafont
Le 29/02/2012 07:40, Sergio a écrit :

shopt -s dotglob
for i in * ; do
echo "--$i--"
done



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
Avatar
Francois Lafont
Merci Luc pour cette réponse très riches d'informations pour moi.

Le 29/02/2012 09:33, Luc Habert a écrit :

1) Je souhaite lister le contenu situé à la racine d'un dossier
/dossier



En shell:

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.

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.



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

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



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

2) Je souhaite copier tout le contenu du dossier /source vers le dossier
/cible.

« cp -r /source /cible » ne marche pas car il



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.



Oui, tout à fait. J'avais oublié l'option -a.

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.



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



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.

C'est pour ça que je me suit fait chier à
« "$dir"/.[!.]* "$dir"/..?* » plus haut



Ok pour le "$dir"/.[!.]* mais le "$dir"/..?* je ne vois vraiment pas.

(<headdesk>note que pour nier un
ensemble de caractères en shell, il faut mettre un « ! » et non « ^ »,
contrairement aux regexps</headdesk>).



Oui c'est dommage que tout cela ne coïncide pas avec les regexps mais bon...

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 ?



Perso, je fais :

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



À 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
Avatar
Nicolas George
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.

"/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.
Avatar
Luc.Habert.00__arjf
Francois Lafont :

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

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

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.

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.


(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.
Avatar
YBM
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.



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
Avatar
Francois Lafont
Le 29/02/2012 13:53, YBM a écrit :

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



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
Avatar
Luc.Habert.00__arjf
Francois Lafont :

En revanche, je ne comprends pas pourquoi la présence de ce fichu point
change tout le comportement de la commande cp ?



Parce que ça a été programmé comme ça...

Car au final /soure/ ou source/. c'est la même chose non ?



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.
1 2 3 4