logo Debian Debian Debian-France Debian-Facile Debian-fr.org Forum-Debian.fr Debian ? Communautés logo inclusivité

Debian-facile

Bienvenue sur Debian-Facile, site d'aide pour les nouveaux utilisateurs de Debian.

Vous n'êtes pas identifié(e).

#1 02-03-2018 16:24:55

vakuy
Membre
Distrib. : debian 10
Noyau : 4.19.0-13-amd64
(G)UI : XFCE
Inscription : 01-03-2018

Changer la date de modification et d'accès de très nombreux fichiers

Bonjour

J'ai une archive de plus d'un million de fichiers dans plusieurs centaines de répertoires. En faisant une copie j'ai remplacé les valeurs modifiy et access time par celles du moment de la copie. Ce n'est pas dramatique, mais je souhaiterais remettre les valeurs originelles. Je n'ai plus les fichiers d'origine.

Par chance, les répertoires contenant les fichiers comprennent une date de création que je peux reprendre pour changer les valeurs des fichiers (ex. NOM_DU_REPERTOIRE-2018-03-02-15_50)

J'ai donc fait un petit script bash qui parcourt l'ensemble des répertoires et applique la date extraite du répertoire comme nouvelle valeur modify et access à l'ensemble des fichiers qu'il contient. Je précise que je suis débutant en scripting, donc j'ai fait au mieux:

#!/bin/bash

ENVDOC=/home/user/documents

cd $ENVDOC

let line=line+1

while ! [[ -z $(ls -1 $ENVDOC | sed -n "$line",1p) ]]; do

let line=line+1

       for i in $(ls -1 $ENVDOC | sed -n "$line",1p); do touch -m -a -t  $(echo $i | grep -Poa '(?<=_).*(?=)' | sed 's/-//g' | sed 's/_//g' |  grep -Poa '[0-9]*') $i/* ; done

done



Ca marche impec sur les dossiers avec peu de fichiers. Le problème, c'est qu'il y a des dossiers avec un très grand nombre de fichiers (jusqu'à 300'000) et le script échoue sur ces dossiers avec une erreur "argument list too long".

En cherchant un peu, j'ai vu que c'était causé par le $i/*:

It's a kernel limitation on the size of the command line argument. Use a for loop instead.
Origin of problem

This is a system issue, related to execve and ARG_MAX constant. There is plenty of documentation about that (see man execve, debian's wiki).

Basically, the expansion produce a command (with its parameters) that exceeds the ARG_MAX limit. On kernel 2.6.23, the limit was set at 128 kB.


https://stackoverflow.com/questions/112 … v-commands

J'ai donc un peu changé et j'ai mis une deuxième boucle for, avec une commande find:

#!/bin/bash

ENVDOC=/home/user/documents

cd $ENVDOC

let line=line+1


  while ! [[ -z $(ls -1 $ENVDOC | sed -n "$line",1p) ]]; do

    let line=line+1

for i in $(ls -1 $ENVDOC | sed -n "$line",1p); do echo $i; for f in $(find $i -type f ); do touch -a -m -t $(echo $i | grep -Poa '(?<=_).*(?=)' | sed 's/-//g' | sed 's/_//g' |  grep -Poa '[0-9]*') $f ; done; done

  done



Ca marche aussi pour les petits répertoires, mais quand j'arrive au gros répertoire de 300'000 fichiers, le script affiche le message


touch: invalid option -- '4'



et ne bouge plus.

J'ai cherché sur le net et n'ai rien trouvé sur une "option invalide 4" avec la commande touch...

Si je vais simplement "à la main" dans ce répertoire et fais

touch -a -m -t 201803021550 *



j'obtiens à nouveau une erreur

Argument list too long



et si je fais


for f in $(find -type f); do touch -a -m -t 201708201436 $f; done



j'obtiens à nouveau


touch: invalid option -- '4'



Quelqu'un aurait-il une solution à ce problème?

Dernière modification par vakuy (02-03-2018 16:34:08)

Hors ligne

#2 02-03-2018 16:55:12

otyugh
CA Debian-Facile
Lieu : Quimperlé/Arzano
Distrib. : Debian Stable
Inscription : 20-09-2016
Site Web

Re : Changer la date de modification et d'accès de très nombreux fichiers

Essaye

find -type f -exec touch -amt 201708201436 {} \;


virtue_signaling.pngpalestine.png
~1821942.svg

Hors ligne

#3 02-03-2018 16:58:48

vakuy
Membre
Distrib. : debian 10
Noyau : 4.19.0-13-amd64
(G)UI : XFCE
Inscription : 01-03-2018

Re : Changer la date de modification et d'accès de très nombreux fichiers

Merci, je viens de lancer la commande sur le gros répertoire, je vais laisser tourner mais ça a l'air de marcher!

Hors ligne

#4 02-03-2018 17:12:52

vakuy
Membre
Distrib. : debian 10
Noyau : 4.19.0-13-amd64
(G)UI : XFCE
Inscription : 01-03-2018

Re : Changer la date de modification et d'accès de très nombreux fichiers

Je confirme que ça a marché! et c'est plutôt rapide! Un grand merci pour ton aide smile

Par contre, quand j'essaie d'intégrer la commande dans mon script, j'obtiens

find: missing argument to `-exec'



Une idée du pourquoi? J'ai essayé sans la longue variable à la fin, avec un simple 201803031600 comme valeur à touch, ça ne marche pas non plus.

#!/bin/bash

ENVDOC=/home/user/documents

cd $ENVDOC

let line=line+1


  while ! [[ -z $(ls -1 $ENVDOC | sed -n "$line",1p) ]]; do

    let line=line+1

for i in $(ls -1 $ENVDOC | sed -n "$line",1p); do find $i -type f -exec touch -mat $(echo $i | grep -Poa '(?<=_).*(?=)' | sed 's/-//g' | sed 's/_//g' |  grep -Poa '[0-9]*') {} \ ;

done
done

Hors ligne

#5 02-03-2018 17:32:35

vakuy
Membre
Distrib. : debian 10
Noyau : 4.19.0-13-amd64
(G)UI : XFCE
Inscription : 01-03-2018

Re : Changer la date de modification et d'accès de très nombreux fichiers

Résolu: il y avait un espace de trop avant ; dans

 find $i -type f -exec touch -mat $(echo $i | grep -Poa '(?<=_).*(?=)' | sed 's/-//g' | sed 's/_//g' |  grep -Poa '[0-9]*') {} \;



roll

Je précise que mon script démarre à partir du deuxième répertoire seulement, je n'ai pas réussi à le faire démarrer à partir du premier répertoire smile

Hors ligne

#6 02-03-2018 18:12:22

enicar
Membre
Lieu : pas ici
Distrib. : sid
Noyau : Linux 6.5.3
(G)UI : openbox
Inscription : 26-08-2010

Re : Changer la date de modification et d'accès de très nombreux fichiers

Ça vaudrait le coup de regarder du côté de xargs
Bon, je  regarderai plus profondément demain si j'ai le temps pour faire éventuellement quelques
propositions wink

Hors ligne

#7 02-03-2018 18:48:36

vakuy
Membre
Distrib. : debian 10
Noyau : 4.19.0-13-amd64
(G)UI : XFCE
Inscription : 01-03-2018

Re : Changer la date de modification et d'accès de très nombreux fichiers

Merci, toute proposition d'amélioraton est bienvenue smile

Pour info j'ai fait passer le script et ça a fonctionné niquel! Tous les modifiy et access time des fichiers ont été changés comme il fallait!

Dernière modification par vakuy (02-03-2018 18:51:15)

Hors ligne

#8 03-03-2018 17:48:51

otyugh
CA Debian-Facile
Lieu : Quimperlé/Arzano
Distrib. : Debian Stable
Inscription : 20-09-2016
Site Web

Re : Changer la date de modification et d'accès de très nombreux fichiers

Re, j'avais perdu ton topic de vue.
Sans le tester y a de mauvaises pratiques, faut vraiment prendre l'habitude d'utiliser des guillemets pour encadrer TOUTES les variables contenant des chaines de caractères ("string" en anglais) pouvant contenir des espaces ; parce que hors ce contexte ça peut parfois marcher, et parfois pas ; l'espace est une caractère spécial de délimitation, ça pose pleeeeeeeein de problème potentiels. Donc quand c'est une $chaine tu dois tout de suite penser "$chaine" !
Si tu fais

commande $(echo lapin)


Non.
Si tu fais

commande $(echo "lapin")


Non.
Si tu fais

commande "$(echo "lapin")"
#ou vu que lapin est figé dans le marbre on peut utiliser les ''
commande "$(echo 'lapin')"


Ok : on encadre systématiquement les chaine de caractères dans des guillemets, c'est une question de bonne pratique.

Je sais pas si ça va corriger le problème, mais ptéte.

je n'ai pas réussi à le faire démarrer à partir du premier répertoire


Initialise $line avec "line=0" pas avec "let line=line+1" (c'est crade en plus, bash initalise bien à 0 mais c'est pas du tout compréhensible de loin tu fais +1 à un truc non initialisé) - ou à la limite fais "let line=0" mais c'est du pareil au même.
A moins que ton problème était ailleurs ?


virtue_signaling.pngpalestine.png
~1821942.svg

Hors ligne

#9 04-03-2018 11:24:25

vakuy
Membre
Distrib. : debian 10
Noyau : 4.19.0-13-amd64
(G)UI : XFCE
Inscription : 01-03-2018

Re : Changer la date de modification et d'accès de très nombreux fichiers

Merci de tes remarques!

C'est vrai que j'oublie toujours de "quoter" mes variables, du coup j'ai souvent des problèmes.

Mais ici, ce qui faisait échouer le script, c'était l'expansion * sur les gros dossiers, ou la commande touch qui renvoyait une erreur incompréhensible.

Ta solution d'utiliser find -exec -touch à la place était la bonne. Avec ça, ça a fonctionné!

Bien vu, line=0 devrait résoudre le problème de la liste qui commence avec le deuxième élément au lieu du premier. Je vais essayer.

Je sais que c'est un peu bizarre les deux "let line", mais il me semble que quand je n'en mettais qu'une, le script échouait. Comme je suis vraiment débutant en scripting bash, j'essaie différentes choses jusqu'à ce que ça marche, mais je ne comprends pas toujours pourquoi smile

Hors ligne

#10 04-03-2018 12:56:57

enicar
Membre
Lieu : pas ici
Distrib. : sid
Noyau : Linux 6.5.3
(G)UI : openbox
Inscription : 26-08-2010

Re : Changer la date de modification et d'accès de très nombreux fichiers

J'ai plusieurs remarques au sujet de ton script.
D'abord, comme déjà dit, faire :


let line=line+1
 


alors que line n'est pas initialisé est incorrecte.
Il vaut mieux directement faire :


line=1
 


Ensuite tu loupes la premiière entrée car dans ta boucle while,
tu incrémentes line avant ta boucle for… il aurait mieux valu
faire :


#!/bin/bash

ENVDOC=/home/user/documents

cd $ENVDOC

let line=1


while ! [[ -z $(ls -1 $ENVDOC | sed -n "$line",1p) ]]; do

    for i in $(ls -1 $ENVDOC | sed -n "$line",1p); do
        find $i -type f -exec touch -mat $(echo $i | grep -Poa '(?<=_).*(?=)' | sed 's/-//g' | sed 's/_//g' |  grep -Poa '[0-9]*') {} \ ;

    let line=line+1
    done
done
 


mais ça reste assez tordu comme code. J'ai d'autres propositions à
faire.

Ta façon de procéder est un peu complexe, je trouve. D'autant qu'il
faut éviter les $(ls -l …) dans des boucle for et while.

Mais prenons les choses dans l'ordre.
Remarque que pour compter le nombre d'entrée dans le répertoire $ENVDOC
il suffit de faire :


maxline=$(ls -l $ENVDOC|wc -l)
 


Et ensuite puisque tu veux faire une boucle sur entrée avec son numéro
de ligne commençant à 1 (pour sed), il suffit de faire :


#!/bin/bash

ENVDOC=/home/user/documents

cd $ENVDOC

let maxline=$(ls -l "$ENVDOC" |wc -l)

for line in $(seq 1 $maxline); do
    for i in $(ls -1 $ENVDOC | sed -n "$line",1p); do
        find $i -type f -exec touch -mat $(echo $i | grep -Poa '(?<=_).*(?=)' | sed 's/-//g' | sed 's/_//g' |  grep -Poa '[0-9]*') {} \ ;
    done
done
 


On peut même faire la même chose avec une seule boucle for :


#!/bin/bash

ENVDOC=/home/user/documents

cd $ENVDOC

let line=1
for i in $(ls -1 $ENVDOC | sed -n "$line",1p); do
        find $i -type f -exec touch -mat $(echo $i | grep -Poa '(?<=_).*(?=)' | sed 's/-//g' | sed 's/_//g' |  grep -Poa '[0-9]*') {} \ ;
    let line=line+1
    done
 


Bon on a déjà supprimé une boucle avec un ls -l. Mais ce n'est pas
tout. On peut tous les supprimer et on a pas besoin de la variable
line. Car à quoi sert le


$(ls -l $ENVDOC |sed -n $line,1p)
 


sinon à afficher le nom de l'entrée courante. Bash sait très bien le
faire sans passer par ls -l.
En faisant tout simplement :


for f in *; do

done
 


Et puisque que tu extrais la date du nom de chaque répertoire,
il n'y a pas pas besoin du « ls -l » (Je le répète, de toute façon ce
n'est pas la bonne manière de procéder pour une boucle).
Et ton code devient le suivant :


#!/bin/bash

ENVDOC=/home/user/documents

cd $ENVDOC

for i in "$ENVDOC"/*; do
        find $i -type f -exec touch -mat $(echo $i | grep -Poa '(?<=_).*(?=)' | sed 's/-//g' | sed 's/_//g' |  grep -Poa '[0-9]*') {} \ ;
done
 


On peut encore améliorer, en extrayant une seule fois la date de
chaque répertoire de $ENVDOC, et faisant un touch massif sur tous les
fichiers de ce répertoire.
Ce qui fait qu'en utilisant xargs derrière le find, ça devrait être
incomparablement plus rapide :


#!/bin/bash

ENVDOC=/home/user/documents
cd $ENVDOC

for i in *; do
    madate=$(echo $i | grep -Poa '(?<=_).*(?=)' | sed 's/-//g' | sed 's/_//g' |  grep -Poa '[0-9]*')
    find $i -type f |xargs touch -mat "$madate"
done
 



Je ne suis pas sûr que ça marchera, à tester sur un répertoire de
test !

Hors ligne

#11 04-03-2018 18:22:05

vakuy
Membre
Distrib. : debian 10
Noyau : 4.19.0-13-amd64
(G)UI : XFCE
Inscription : 01-03-2018

Re : Changer la date de modification et d'accès de très nombreux fichiers

Un grand merci pour tes remarques!

Effectivement, partir sur une boucle while et un test est inutilement compliqué ici. Je n'avais pas pensé à utiliser une simple boucle for!

J'ai testé ta commande sur un répertoire de test qui contient 600 documents, le résultat était correct et immédiat! Je ne vais pas le refaire sur l'ensemble des archives car mon script (après correction par otyugh) y est quand même parvenu (ça a pris un bon moment quand même), mais je garde ta version pour une prochaine fois!

Au passage, je dois dire que je ne comprends pas bien le rôle de xargs. J'ai lu qqch à ce sujet dernièrement mais j'ai tout oublié. Si tu as le courage et le temps de m'expliquer à quoi sert cette commande... smile

Dernière modification par vakuy (04-03-2018 18:22:30)

Hors ligne

#12 04-03-2018 19:06:19

enicar
Membre
Lieu : pas ici
Distrib. : sid
Noyau : Linux 6.5.3
(G)UI : openbox
Inscription : 26-08-2010

Re : Changer la date de modification et d'accès de très nombreux fichiers

Bon qu'est-ce que fait xargs ? Il sert (pas uniquement) à résoudre ce
problème : 

vakuy a écrit :

Ca marche impec sur les dossiers avec peu de fichiers. Le problème, c'est qu'il y a des dossiers avec un très grand nombre de fichiers (jusqu'à 300'000) et le script échoue sur ces dossiers avec une erreur "argument list too long".

En cherchant un peu, j'ai vu que c'était causé par le $i/*:

It's a kernel limitation on the size of the command line argument. Use a for loop instead.
    Origin of problem

    This is a system issue, related to execve and ARG_MAX constant. There is plenty of documentation about that (see man execve, debian's wiki).

    Basically, the expansion produce a command (with its parameters) that exceeds the ARG_MAX limit. On kernel 2.6.23, the limit was set at 128 kB.



Voilà comment il opère, il lit une liste de chaîne passée sur son
entrée standard, et il donne cette liste à la commande qu'on lui donne
en argument, mais en plus il tient compte de la limitation sur la
taille de ARG_MAX, et il va relancer la commande autant de fois que
nécessaire jusqu'à épuiser les données sur l'entrée standard.

Mais je peux faire un petite amélioration à ce que je t'ai proposé.
Le défaut lorsqu'on utilise find et xargs de cette façon, c'est que
les noms de fichiers vont être séparés par des nouvelles lignes. Pour
être tout à fait général et sûr il vaut mieux utiliser un caractère
nul à la place. Et ceci a été prévu dans find et xargs. D'où ma
nouvelle version :


#!/bin/bash

ENVDOC=/home/user/documents
cd $ENVDOC

for i in *; do
    madate=$(echo $i | grep -Poa '(?<=_).*(?=)' | sed 's/-//g' | sed 's/_//g' |  grep -Poa '[0-9]*')
    find "$i" -type f -print0 |xargs -0 touch -mat "$madate"
done
 



Le -print0 demande à find de terminer chaque chaîne par un
caractère nul et le -0 dit à xargs que les chaînes données
en entrée sont séparées par des caractères nul (au lieu des nouvelles
lignes par défaut). Le caractère nul (c'est à dire zéro) est le seul caractère avec
le / a être interdit dans un nom de fichier. Voilà pourquoi, c'est plus
sûr de faire comme cela.

En résumé, grâce à xargs on peut opérer sur plusieurs fichier en même temps,
tout en prenant compte de la longueur maximum de la liste d'arguments que l'on peut
donner à une commande.

Dernière modification par enicar (04-03-2018 19:07:21)

Hors ligne

#13 04-03-2018 19:36:29

vakuy
Membre
Distrib. : debian 10
Noyau : 4.19.0-13-amd64
(G)UI : XFCE
Inscription : 01-03-2018

Re : Changer la date de modification et d'accès de très nombreux fichiers

C'est très clair, merci beaucoup!

De plus en plus je me rends compte que find est d'une utilité redoutable, avec les bons arguments c'est vraiment une machine à tout faire!

Hors ligne

Pied de page des forums