Renommez les fichiers de manière récursive en utilisant find et sed


85

Je veux parcourir un tas de répertoires et renommer tous les fichiers qui se terminent par _test.rb pour finir par _spec.rb à la place. C'est quelque chose que je n'ai jamais vraiment compris comment faire avec bash, alors cette fois j'ai pensé que je ferais un effort pour le faire clouer. Cependant, j'ai jusqu'ici échoué, mon meilleur effort est:

find spec -name "*_test.rb" -exec echo mv {} `echo {} | sed s/test/spec/` \;

NB: il y a un écho supplémentaire après exec pour que la commande soit imprimée au lieu d'être exécutée pendant que je la teste.

Lorsque je l'exécute, la sortie de chaque nom de fichier correspondant est:

mv original original

c'est-à-dire que la substitution par sed a été perdue. C'est quoi le truc?


BTW, je suis conscient qu'il existe une commande de changement de nom, mais j'aimerais vraiment savoir comment le faire en utilisant sed afin que je puisse faire des commandes plus puissantes à l'avenir.
opsb


Réponses:


32

Cela se produit car sedreçoit la chaîne {}en entrée, comme cela peut être vérifié avec:

find . -exec echo `echo "{}" | sed 's/./foo/g'` \;

qui imprime foofoopour chaque fichier du répertoire, de manière récursive. La raison de ce comportement est que le pipeline est exécuté une fois, par le shell, lorsqu'il étend la commande entière.

Il n'y a aucun moyen de citer le sedpipeline de manière à findl'exécuter pour chaque fichier, car findn'exécute pas de commandes via le shell et n'a aucune notion de pipelines ou de backquotes. Le manuel GNU findutils explique comment effectuer une tâche similaire en plaçant le pipeline dans un script shell séparé:

#!/bin/sh
echo "$1" | sed 's/_test.rb$/_spec.rb/'

(Il peut y avoir une manière perverse d'utiliser sh -cet une tonne de citations pour faire tout cela en une seule commande, mais je ne vais pas essayer.)


27
Pour ceux qui s'interrogent sur l'utilisation perverse de sh -c, voici: find spec -name "* _test.rb" -exec sh -c 'echo mv "$ 1" "$ (echo" $ 1 "| sed s / test.rb \ $ / spec.rb /) "'_ {} \;
opsb

1
@opsb à quoi ça sert _? excellente solution - mais j'aime la réponse ramtam plus :)
iRaS

À votre santé! M'a sauvé beaucoup de maux de tête. Par souci d'exhaustivité, voici comment je le dirige vers un script: find. -nom "fichier" -exec sh /chemin/vers/script.sh {} \;
Sven M.

128

Pour le résoudre de la manière la plus proche du problème d'origine, il faudrait probablement utiliser l'option xargs "args par ligne de commande":

find . -name "*_test.rb" | sed -e "p;s/test/spec/" | xargs -n2 mv

Il trouve les fichiers dans le répertoire de travail actuel de manière récursive, fait écho au nom de fichier d'origine ( p) puis à un nom modifié ( s/test/spec/) et les alimente tous mvpar paires ( xargs -n2). Attention, dans ce cas, le chemin lui-même ne doit pas contenir de chaîne test.


9
Malheureusement, cela a des problèmes d'espace blanc. Donc, utiliser avec des dossiers qui ont des espaces dans le nom le cassera à xargs (confirmez avec -p pour le mode verbeux / interactif)
cde

1
C'est exactement ce que je cherchais. Dommage pour le problème des espaces blancs (je ne l'ai pas testé, cependant). Mais pour mes besoins actuels, c'est parfait. Je suggère de le tester d'abord avec "echo" au lieu de "mv" comme paramètre dans "xargs".
Michele Dall'Agata

5
Si vous avez besoin de gérer les espaces dans les chemins et que vous utilisez GNU sed> = 4.2.2, vous pouvez utiliser l' -zoption avec les recherches -print0et les xargs -0:find -name '*._test.rb' -print0 | sed -ze "p;s/test/spec/" | xargs -0 -n2 mv
Evan Purkhiser

Meilleure solution. Tellement plus rapide que find -exec. Merci
Miguel A. Baldi Hörlle

Cela ne fonctionnera pas s'il y a plusieurs testdossiers dans un même chemin. sedne renommera que le premier et la mvcommande échouera en cas d' No such file or directoryerreur.
Casey

22

vous voudrez peut-être envisager une autre manière comme

for file in $(find . -name "*_test.rb")
do 
  echo mv $file `echo $file | sed s/_test.rb$/_spec.rb/`
done

Cela semble être une bonne façon de procéder. Cependant, je cherche vraiment à déchiffrer l'unique ligne, à améliorer mes connaissances plus que toute autre chose.
opsb

2
pour le fichier dans $ (find. -name "* _test.rb"); faire echo mv $ file echo $file | sed s/_test.rb$/_spec.rb/; fait est un one-liner, non?
Bretticus

5
Cela ne fonctionnera pas si vous avez des noms de fichiers avec des espaces. forles divisera en mots séparés. Vous pouvez le faire fonctionner en demandant à la boucle for de se diviser uniquement sur les nouvelles lignes. Voir cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html pour des exemples.
onitake

Je suis d'accord avec @onitake, bien que je préfère utiliser l' -execoption de find.
ShellFish

18

Je trouve celui-ci plus court

find . -name '*_test.rb' -exec bash -c 'echo mv $0 ${0/test.rb/spec.rb}' {} \;

Bonjour, je pense que « _test.rb» devrait être « _test.rb» (guillemets doubles en guillemets simples). Puis-je vous demander pourquoi vous utilisez le trait de soulignement pour pousser l'argument que vous souhaitez positionner $ 1 alors qu'il me semble que cela find . -name '*_test.rb' -exec bash -c 'echo mv $0 ${0/test.rb/spec.rb}' {} \;fonctionne ? Comme le feraitfind . -name '*_test.rb' -exec bash -c 'echo mv $1 ${1/test.rb/spec.rb}' iAmArgumentZero {} \;
agtb

Merci pour vos suggestions, corrigé
csg

Merci d'avoir clarifié cela - j'ai seulement commenté parce que j'ai passé un certain temps à réfléchir à la signification de _ en pensant que c'était peut-être une utilisation
astucieuse

9

Vous pouvez le faire sans sed, si vous le souhaitez:

for i in `find -name '*_test.rb'` ; do mv $i ${i%%_test.rb}_spec.rb ; done

${var%%suffix}bandes suffixde la valeur devar .

ou, pour le faire en utilisant sed:

for i in `find -name '*_test.rb'` ; do mv $i `echo $i | sed 's/test/spec/'` ; done

cela ne fonctionne pas ( sedcelui) comme l'explique la réponse acceptée.
Ali

@Ali, ça marche - je l'ai testé moi-même quand j'ai écrit la réponse. @ explication de larsman ne concerne pas for i in... ; do ... ; done, qui exécute des commandes via le shell et ne comprend backtick.
Wayne Conrad

9

Vous mentionnez que vous utilisez bashcomme shell, auquel cas vous n'en avez pas réellement besoin findet sedpour obtenir le changement de nom par lots que vous recherchez ...

En supposant que vous utilisez bashcomme shell:

$ echo $SHELL
/bin/bash
$ _

... et en supposant que vous ayez activé l' globstaroption dite du shell:

$ shopt -p globstar
shopt -s globstar
$ _

... et enfin en supposant que vous avez installé l' renameutilitaire (trouvé dans le util-linux-ngpackage)

$ which rename
/usr/bin/rename
$ _

... alors vous pouvez obtenir le changement de nom par lots dans un bash one-liner comme suit:

$ rename _test _spec **/*_test.rb

(l' globstaroption shell garantira que bash trouve tous les *_test.rbfichiers correspondants , peu importe à quel point ils sont imbriqués dans la hiérarchie des répertoires ... utilisez help shoptpour savoir comment définir l'option)


7

Le moyen le plus simple :

find . -name "*_test.rb" | xargs rename s/_test/_spec/

Le moyen le plus rapide (en supposant que vous ayez 4 processeurs):

find . -name "*_test.rb" | xargs -P 4 rename s/_test/_spec/

Si vous avez un grand nombre de fichiers à traiter, il est possible que la liste des noms de fichiers acheminés vers xargs fasse en sorte que la ligne de commande résultante dépasse la longueur maximale autorisée.

Vous pouvez vérifier la limite de votre système en utilisant getconf ARG_MAX

Sur la plupart des systèmes Linux, vous pouvez utiliser free -bou cat /proc/meminfotrouver la quantité de RAM avec laquelle vous devez travailler; Sinon, utilisez topou l'application de surveillance de l'activité de votre système.

Un moyen plus sûr (en supposant que vous ayez 1000000 octets de RAM pour travailler):

find . -name "*_test.rb" | xargs -s 1000000 rename s/_test/_spec/

2

Voici ce qui a fonctionné pour moi lorsque les noms de fichiers contenaient des espaces. L'exemple ci-dessous renomme récursivement tous les fichiers .dar en fichiers .zip:

find . -name "*.dar" -exec bash -c 'mv "$0" "`echo \"$0\" | sed s/.dar/.zip/`"' {} \;

2

Pour cela, vous n'avez pas besoin sed. Vous pouvez parfaitement vous retrouver seul avec une whileboucle alimentée par le résultat d' findune substitution de processus .

Donc, si vous avez une findexpression qui sélectionne les fichiers nécessaires, utilisez la syntaxe:

while IFS= read -r file; do
     echo "mv $file ${file%_test.rb}_spec.rb"  # remove "echo" when OK!
done < <(find -name "*_test.rb")

Cela les findfichiers et les renommer tous en séparant la chaîne _test.rbde la fin et en l'ajoutant _spec.rb.

Pour cette étape, nous utilisons l' expansion des paramètres du shell${var%string}supprime le modèle correspondant le plus court "chaîne" $var.

$ file="HELLOa_test.rbBYE_test.rb"
$ echo "${file%_test.rb}"          # remove _test.rb from the end
HELLOa_test.rbBYE
$ echo "${file%_test.rb}_spec.rb"  # remove _test.rb and append _spec.rb
HELLOa_test.rbBYE_spec.rb

Voir un exemple:

$ tree
.
├── ab_testArb
├── a_test.rb
├── a_test.rb_test.rb
├── b_test.rb
├── c_test.hello
├── c_test.rb
└── mydir
    └── d_test.rb

$ while IFS= read -r file; do echo "mv $file ${file/_test.rb/_spec.rb}"; done < <(find -name "*_test.rb")
mv ./b_test.rb ./b_spec.rb
mv ./mydir/d_test.rb ./mydir/d_spec.rb
mv ./a_test.rb ./a_spec.rb
mv ./c_test.rb ./c_spec.rb

Merci beaucoup! Cela m'a aidé à supprimer facilement .gz de fin de tous les noms de fichiers de manière récursive. while IFS= read -r file; do mv $file ${file%.gz}; done < <(find -type f -name "*.gz")
Vinay Vissh

1
@CasualCoder agréable à lire :) Notez que vous pouvez dire directement find .... -exec mv .... Faites également attention $filecar il échouera s'il contient des espaces. Mieux vaut utiliser des citations "$file".
fedorqui 'SO arrêtez de nuire'

1

si vous avez Ruby (1.9+)

ruby -e 'Dir["**/*._test.rb"].each{|x|test(?f,x) and File.rename(x,x.gsub(/_test/,"_spec") ) }'

1

Dans la réponse de ramtam que j'aime, la partie de recherche fonctionne bien mais le reste ne fonctionne pas si le chemin contient des espaces. Je ne suis pas trop familier avec sed, mais j'ai pu modifier cette réponse pour:

find . -name "*_test.rb" | perl -pe 's/^((.*_)test.rb)$/"\1" "\2spec.rb"/' | xargs -n2 mv

J'avais vraiment besoin d'un changement comme celui-ci car dans mon cas d'utilisation, la commande finale ressemble plus à

find . -name "olddir" | perl -pe 's/^((.*)olddir)$/"\1" "\2new directory"/' | xargs -n2 mv

1

Je n'ai pas le cœur de tout recommencer, mais j'ai écrit ceci en réponse à Commandline Find Sed Exec . Là, le demandeur voulait savoir comment déplacer une arborescence entière, en excluant éventuellement un répertoire ou deux, et renommer tous les fichiers et répertoires contenant la chaîne "OLD" pour qu'ils contiennent à la place "NEW" .

En plus de décrire le comment avec une verbosité minutieuse ci-dessous, cette méthode peut également être unique en ce qu'elle intègre le débogage intégré. Il ne fait fondamentalement rien du tout tel qu'il est écrit, sauf compiler et enregistrer dans une variable toutes les commandes qu'il pense devoir faire pour effectuer le travail demandé.

Il évite également explicitement les boucles autant que possible. Outre la sedrecherche récursive de plus d'une correspondance du motif, il n'y a pas d'autre récursivité à ma connaissance.

Et enfin, il est entièrement nulldélimité - il ne se déclenche sur aucun caractère dans aucun nom de fichier à l'exception du null. Je ne pense pas que tu devrais avoir ça.

Au fait, c'est VRAIMENT rapide. Regardez:

% _mvnfind() { mv -n "${1}" "${2}" && cd "${2}"
> read -r SED <<SED
> :;s|${3}\(.*/[^/]*${5}\)|${4}\1|;t;:;s|\(${5}.*\)${3}|\1${4}|;t;s|^[0-9]*[\t]\(mv.*\)${5}|\1|p
> SED
> find . -name "*${3}*" -printf "%d\tmv %P ${5} %P\000" |
> sort -zg | sed -nz ${SED} | read -r ${6}
> echo <<EOF
> Prepared commands saved in variable: ${6}
> To view do: printf ${6} | tr "\000" "\n"
> To run do: sh <<EORUN
> $(printf ${6} | tr "\000" "\n")
> EORUN
> EOF
> }
% rm -rf "${UNNECESSARY:=/any/dirs/you/dont/want/moved}"
% time ( _mvnfind ${SRC=./test_tree} ${TGT=./mv_tree} \
> ${OLD=google} ${NEW=replacement_word} ${sed_sep=SsEeDd} \
> ${sh_io:=sh_io} ; printf %b\\000 "${sh_io}" | tr "\000" "\n" \
> | wc - ; echo ${sh_io} | tr "\000" "\n" |  tail -n 2 )

   <actual process time used:>
    0.06s user 0.03s system 106% cpu 0.090 total

   <output from wc:>

    Lines  Words  Bytes
    115     362   20691 -

    <output from tail:>

    mv .config/replacement_word-chrome-beta/Default/.../googlestars \
    .config/replacement_word-chrome-beta/Default/.../replacement_wordstars        

REMARQUE: ce qui précède functionnécessitera probablement des GNUversions de sedet findpour gérer correctement lefind printf et sed -z -eet:;recursive regex test;t appels . Si ceux-ci ne sont pas disponibles pour vous, la fonctionnalité peut probablement être dupliquée avec quelques ajustements mineurs.

Cela devrait faire tout ce que vous vouliez du début à la fin avec très peu de complications. Je l'ai fait forkavec sed, mais je pratiquais aussi seddes techniques de branchement récursives, c'est pourquoi je suis ici. C'est un peu comme obtenir une coupe de cheveux à prix réduit dans une école de coiffeur, je suppose. Voici le flux de travail:

  • rm -rf ${UNNECESSARY}
    • J'ai volontairement laissé de côté tout appel fonctionnel qui pourrait supprimer ou détruire des données de toute nature. Vous dites que cela ./apppourrait être indésirable. Supprimez-le ou déplacez-le ailleurs au préalable, ou, alternativement, vous pouvez créer une \( -path PATTERN -exec rm -rf \{\} \)routine pour findle faire par programme, mais celui-ci est tout à vous.
  • _mvnfind "${@}"
    • Déclarez ses arguments et appelez la fonction de travail. ${sh_io}est particulièrement important en ce qu'il enregistre le retour de la fonction. ${sed_sep}vient dans une seconde près; c'est une chaîne arbitraire utilisée pour référencer sedla récursion de la fonction. Si ${sed_sep}est défini sur une valeur qui pourrait potentiellement être trouvée dans l'un de vos noms de chemin ou de fichier sur lesquels vous avez agi ... eh bien, ne le laissez pas être.
  • mv -n $1 $2
    • L'arbre entier est déplacé depuis le début. Cela évitera beaucoup de maux de tête; Crois moi. Le reste de ce que vous voulez faire - le changement de nom - est simplement une question de métadonnées du système de fichiers. Si vous étiez, par exemple, en train de déplacer cela d'un lecteur à un autre, ou à travers les limites du système de fichiers de tout type, vous feriez mieux de le faire immédiatement avec une seule commande. C'est aussi plus sûr. Notez l' -noclobberoption définie pour mv; comme écrit, cette fonction ne mettra pas ${SRC_DIR}là où un ${TGT_DIR}existe déjà.
  • read -R SED <<HEREDOC
    • J'ai localisé toutes les commandes de sed ici pour économiser sur les tracas et les lire dans une variable pour alimenter sed ci-dessous. Explication ci-dessous.
  • find . -name ${OLD} -printf
    • Nous commençons le findprocessus. Avec findnous ne recherchons que tout ce qui doit être renommé car nous avons déjà effectué toutes les opérations de placement à place mvavec la première commande de la fonction. Plutôt que de prendre une action directe avec find, comme un execappel, par exemple, nous l'utilisons à la place pour construire dynamiquement la ligne de commande avec -printf.
  • %dir-depth :tab: 'mv '%path-to-${SRC}' '${sed_sep}'%path-again :null delimiter:'
    • Après avoir findlocalisé les fichiers dont nous avons besoin, il construit et imprime directement (la plupart ) de la commande dont nous aurons besoin pour traiter votre changement de nom. Le %dir-depthclouage au début de chaque ligne aidera à nous assurer que nous n'essayons pas de renommer un fichier ou un répertoire dans l'arborescence avec un objet parent qui n'a pas encore été renommé. findutilise toutes sortes de techniques d'optimisation pour parcourir l'arborescence de votre système de fichiers et il n'est pas certain qu'il renverra les données dont nous avons besoin dans un ordre sûr pour les opérations. C'est pourquoi nous ...
  • sort -general-numerical -zero-delimited
    • Nous trions toute findla sortie de %directory-depthafin que les chemins les plus proches en relation avec $ {SRC} soient travaillés en premier. Cela évite les erreurs possibles impliquant des mvfichiers dans des emplacements inexistants et minimise le besoin de bouclage récursif. ( en fait, vous pourriez avoir du mal à trouver une boucle )
  • sed -ex :rcrs;srch|(save${sep}*til)${OLD}|\saved${SUBSTNEW}|;til ${OLD=0}
    • Je pense que c'est la seule boucle dans tout le script, et elle ne boucle que sur la seconde %Pathimprimée pour chaque chaîne au cas où elle contiendrait plus d'une valeur $ {OLD} qui pourrait avoir besoin d'être remplacée. Toutes les autres solutions que j'ai imaginées impliquaient un deuxième sedprocessus, et même si une boucle courte n'est peut-être pas souhaitable, elle bat certainement la génération et la fourche d'un processus entier.
    • Donc fondamentalement quoi sed fait ici est de rechercher $ {sed_sep}, puis, après l'avoir trouvé, le sauvegarde et tous les caractères qu'il rencontre jusqu'à ce qu'il trouve $ {OLD}, qu'il remplace ensuite par $ {NEW}. Il revient ensuite à $ {sed_sep} et cherche à nouveau $ {OLD}, au cas où cela se produirait plus d'une fois dans la chaîne. S'il n'est pas trouvé, il imprime la chaîne modifiée dansstdout (qu'il attrape ensuite à nouveau) et termine la boucle.
    • Cela évite d'avoir à analyser la chaîne entière et garantit que la première moitié de la mvchaîne de commande, qui doit bien sûr inclure $ {OLD}, l'inclut, et la seconde moitié est modifiée autant de fois que nécessaire pour effacer le $ {OLD} nom du mvchemin de destination de.
  • sed -ex...-ex search|%dir_depth(save*)${sed_sep}|(only_saved)|out
    • Les deux -execappels se produisent ici sans une seconde fork. Dans le premier, comme nous l'avons vu, nous modifions la mvcommande fournie par findla -printfcommande de fonction de s comme nécessaire pour modifier correctement toutes les références de $ {OLD} à $ {NEW}, mais pour ce faire, nous devions utiliser des points de référence arbitraires qui ne devraient pas être inclus dans le résultat final. Donc, une fois que sedtout ce qu'il a à faire est terminé, nous lui demandons d'effacer ses points de référence du tampon de maintien avant de le transmettre.

ET MAINTENANT NOUS SOMMES DE RETOUR

read recevra une commande qui ressemble à ceci:

% mv /path2/$SRC/$OLD_DIR/$OLD_FILE /same/path_w/$NEW_DIR/$NEW_FILE \000

Il sera readen ${msg}tant que${sh_io} qui peut être examinée à volonté en dehors de la fonction.

Cool.

-Mike


1

J'ai pu gérer les noms de fichiers avec des espaces en suivant les exemples suggérés par onitake.

Cela ne casse pas si le chemin contient des espaces ou la chaîne test:

find . -name "*_test.rb" -print0 | while read -d $'\0' file
do
    echo mv "$file" "$(echo $file | sed s/test/spec/)"
done

1

C'est un exemple qui devrait fonctionner dans tous les cas. Fonctionne recursiveley, nécessite juste un shell et prend en charge les noms de fichiers avec des espaces.

find spec -name "*_test.rb" -print0 | while read -d $'\0' file; do mv "$file" "`echo $file | sed s/test/spec/`"; done

0
$ find spec -name "*_test.rb"
spec/dir2/a_test.rb
spec/dir1/a_test.rb

$ find spec -name "*_test.rb" | xargs -n 1 /usr/bin/perl -e '($new=$ARGV[0]) =~ s/test/spec/; system(qq(mv),qq(-v), $ARGV[0], $new);'
`spec/dir2/a_test.rb' -> `spec/dir2/a_spec.rb'
`spec/dir1/a_test.rb' -> `spec/dir1/a_spec.rb'

$ find spec -name "*_spec.rb"
spec/dir2/b_spec.rb
spec/dir2/a_spec.rb
spec/dir1/a_spec.rb
spec/dir1/c_spec.rb

Ah ... je ne connais pas un moyen d'utiliser sed autre que de mettre la logique dans un script shell et de l'appeler dans exec.
n'a

0

Votre question semble concerner sed, mais pour atteindre votre objectif de renommer récursif, je suggère ce qui suit, sans vergogne extrait d'une autre réponse que j'ai donnée ici: renommer récursif dans bash

#!/bin/bash
IFS=$'\n'
function RecurseDirs
{
for f in "$@"
do
  newf=echo "${f}" | sed -e 's/^(.*_)test.rb$/\1spec.rb/g'
    echo "${f}" "${newf}"
    mv "${f}" "${newf}"
    f="${newf}"
  if [[ -d "${f}" ]]; then
    cd "${f}"
    RecurseDirs $(ls -1 ".")
  fi
done
cd ..
}
RecurseDirs .

Comment fonctionne le sedfonctionnement sans échapper ()si vous ne définissez pas l' -roption?
mikeserv

0

Manière plus sûre de renommer avec les utilitaires find et le type d'expression régulière sed:

  mkdir ~/practice

  cd ~/practice

  touch classic.txt.txt

  touch folk.txt.txt

Supprimez l'extension ".txt.txt" comme suit -

  cd ~/practice

  find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} \;

Si vous utilisez le + à la place de; afin de travailler en mode batch, la commande ci-dessus renomme uniquement le premier fichier correspondant, mais pas la liste entière des correspondances de fichiers par «trouver».

  find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} +

0

Voici un joli oneliner qui fait l'affaire. Sed ne peut pas gérer cela correctement, surtout si plusieurs variables sont passées par xargs avec -n 2. Une sous-station bash gérerait cela facilement comme:

find ./spec -type f -name "*_test.rb" -print0 | xargs -0 -I {} sh -c 'export file={}; mv $file ${file/_test.rb/_spec.rb}'

L'ajout de -type -f limitera les opérations de déplacement aux fichiers uniquement, -print 0 gérera les espaces vides dans les chemins.



0

Voici ma solution de travail:

for FILE in {{FILE_PATTERN}}; do echo ${FILE} | mv ${FILE} $(sed 's/{{SOURCE_PATTERN}}/{{TARGET_PATTERN}}/g'); done
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.