Léa-Linux & amis :   LinuxFR   GCU-Squad   GNU
Script bash : tester récursivement tous les flacs d'un répertoire.
Envoyé par: Rémi G.

Bonjour!

J'aimerais réaliser un script pour tester toutes mes musiques encodées en flac en explorant récursivement les répertoires, qui prend en argument le répertoire dans lequel travailler et qui renvoie la liste des fichiers défectueux.

1) mon premier problème : comment parcourir récursivement tous les répertoires pour obtenir une liste des fichiers à tester?
- j'ai pensé à cette commande mais je n'ai que le nom des fichiers et pas le répertoire dans lequel ils se trouvent :
ls $PWD/$1 -R | grep *.flac
Autre problème (moins important!) l'argument $PWD/$1 passé pour la commande ls est très moche... Est-ce que c'est "propre" de faire ça?

- où stocker les informations obtenues? Je comptais les stocker dans un fichier texte avec une redirection de la sortie de la commande précédente :
Commande_pour_les_fichiers > liste
Mais peut-on faire autrement?

2) deuxième problème : comment construire une boucle for qui "balaye" les différentes musiques répertoriée dans mon fichier liste?
Je pensais utiliser "tail" et "head" et récupérer les lignes une à une mais je ne sais pas comment faire une récursion (et si c'est possible?) en bash.

Enfin bref j'ai des idées bien trop compliquées pour un problème en apparence assez simple. C'est la première fois que je veux faire un script donc j'y connais pas grand chose! Voilà ci-dessous un script qui fonctionne (mais ne cherche que dans le répertoire courant et qui doit être exécuté dans le répertoire courant) :
#! /bin/bash
for i in *.flac 
do
	if !(flac -t "$i") ; then
	echo "$i"
	fi
done
Bref c'est pas folichon... smiling smiley

3) Dernier problème : comment rendre la commande "flac -t "$i"" invisible, i.e ne pas afficher son résultat à l'écran?

Merci d'avance pour vos réponses! J'espère avoir été clair... Mais c'est un peu confus dans ma tête!

Poste le Friday 8 December 2006 19:04:46
Répondre     Citer    
Re: Script bash : tester récursivement tous les flacs d'un répertoire.

Peut-être
find -name '*.flac' | xargs flac

----

Basile STARYNKEVITCH

Membre de l'APRIL « promouvoir et défendre le logiciel libre » - adhérez vous aussi à l'APRIL!

Projet logiciel libre: RefPerSys

Poste le Friday 8 December 2006 19:06:17
Répondre     Citer    
Re: Script bash : tester récursivement tous les flacs d'un répertoire.
Envoyé par: Sve@r

Citation
Rémi G.
1) mon premier problème : comment parcourir récursivement tous les répertoires pour obtenir
une liste des fichiers à tester?
find <rep_de_depart> <critère_de_recherche> <critère d'action>
Ex:
find / -type d -print => Affiche tous les répertoires de "/"
find /home -name "*.flac" -print => Affiche tous les fichiers de "/home" dont le nom se termine par ".flac"
find / -type f -exec mail {} billou \; => envoie tous tes fichiers à "billou"
=> man find pour plus de détails

Citation
Rémi G.
Autre problème (moins important!) l'argument $PWD/$1 passé pour la commande ls est très
moche... Est-ce que c'est "propre" de faire ça?
Parfaitement propre. $PWD est une variable qui t'es offerte par "bash" et "$1" en est une autre. Tu peux les utiliser comme t'en as envie. C'est d'ailleurs pas mal que t'arrives à jongler ainsi avec ces deux là et que t'aies facilement intégré la signification de "$1". Je donne des cours et j'en connais qui ont déjà du mal à faire cela...
Maintenant si ton script doit être porté sur des systèmes plus vieux (qui n'ont pas "bash" donc pas "$PWD"), tu peux remplacer "ls $PWD/$1" par "ls `pwd`/$1"
Si l'écriture compactée te semble illisible (là ça ne l'est pas mais si la commande est plus complexe cela peut le devenir), tu peux remplacer par
pwd=`pwd`
ls $pwd/$1

Citation
Rémi G.
où stocker les informations obtenues? Je comptais les stocker dans un fichier texte avec une redirection de la sortie de la commande
précédente :
Commande_pour_les_fichiers > liste
Mais peut-on faire autrement?
Absolument pas et c'est une des grandes forces d'Unix, c'est que toute commande peut rediriger ses infos vers un fichier ce qui t'évite la peine, si tu veux écrire un programme, de t'occuper de l'endroit où ira écrire le programme. Tu lui fais écrire à l'écran et si l'utilisateur veut renvoyer ce que t'écris :
- vers un fichier
- vers un autre programme
- vers une imprimante
L'utilisateur insère lui-même la redirection qui va bien lors de l'appel du programme et Unix se charge lui-même du renvoi
Seul petit détail : Il faut penser que t'es en environnement multi utilisateur multi-processus. Donc chaque fois que tu fais écrire un fichier, il faut toujours te demander
- que se passe-t-il si je lance 2 fois mon programme en parallèle via 2 fenêtres différentes
- que se passe-t-il si mon programme devient tellement puissant qu'il sera implanté sur un gros Unix et qu'il sera suceptible d'être lancé en même temps par plusieurs utilisateurs différents
Pour répondre à ces 2 questions, il faut te rappeler que :
- tu as à ta disposition les répertoires "/tmp" et "$HOME/tmp" pour tes fichiers temporaires (par exemple, créer le fichier dans "$HOME/tmp" règlera le problème des utilisateurs multiples)
- tu as à ta disposition la variable "$$" qui te donne ton n° de processus. Ce n° étant unique à chaque instant, insérer cette variable dans ton nom de fichier te donnera un nom dont tu es certain qu'il ne sera utilisé que par ton processus et si tu lances ton programme 2 fois, chaque exécution aura un n° différent

Citation
Rémi G.
2) deuxième problème : comment construire une boucle for qui "balaye" les différentes musiques répertoriée dans mon fichier liste?
for fichier in `cat liste`
do
....traitement $fichier
done
Problème: le "for" découpe ses infos sur l'espace. Donc si tu as dans ta liste un nom de fichier qui contient un espace, ton "for" te génèrera 2 itérations et chaque itération n'aura qu'une partie du nom. Le fichier ne sera pas traité

Autre solution plus élégante
while read fichier
do
....traitement $fichier
done < liste
Mieux car le "read" arrête sa lecture au "return" et non à l'espace. Donc tu boucleras sur chaque nom tant que le "read" en lit un. Dès qu'il n'y aura plus rien à lire, ton "read" renverra "faux" et ton while s'arrêtera.
Problème: Si tu veux faire une saisie dans la boucle, style :
while read fichier
do
....echo "Voulez-vous traiter le fichier $fichier ?"
....read rep
....test "$rep" = "o" && traitement $fichier
done < liste
Ben ton "read" intèrieur prendra aussi ses info dans la redirection et non chez l'utilisateur comme t'espères.

Solution finale (mais plus complexe)
exec 3<liste       # On crée un canal n° 3 (0, 1 et 2 étant déjà utilisés) qui contiendra le fichier "liste"

# On lance une boucle infinie
while true
do
....# On lit le fichier sur le canal 3 (et on sort si le "read" renvoie "faux" => il n'y a plus rien à lire)
....read fichier 0<&3 || break

....# On fait notre saisie qui, elle, se fera sur le clavier normal
....echo "Voulez-vous traiter le fichier $fichier ?"
....read rep
....test "$rep" = "o" && traitement $fichier
done

Citation
Rémi G.
Enfin bref j'ai des idées bien trop compliquées
Non, c'est courant ce genre d'idées chez les débutants. Avec le temps on comprend de mieux en mieux la subtilité du shell ce qui aide à trouver la meilleure des solutions pour résoudre un problème...

Citation
Rémi G.
C'est la première fois que je veux faire un script donc j'y connais pas grand chose!
=> [fr.lang.free.fr]

L'homme qui murmurait à l'oreille des pingouins
[fr.lang.free.fr]

Poste le Friday 8 December 2006 20:08:20
Répondre     Citer    
Re: Script bash : tester récursivement tous les flacs d'un répertoire.
Envoyé par: Rémi G.

Merci pour vos réponses à tous les deux!
J'ai beaucoup cherché ce week-end en suivant vos deux méthodes et j'ai réussi! ouf #%b

- Méthode de Basile STARYNKEVITCH :
Le programme suivant (ou plutôt la ligne suivante) marche, mais il m'affiche tous les messages d'erreurs des fichiers défectueux en sortie ce qui rend le résultat peu lisible et difficilement utilisable dans un autre programme :
find répertoire -name *.flac | xargs -i flac -t -s "{}"
(l'option "-t" de la commande "flac" signifie "tester" et l'option "-s" signifie "afficher que les messages d'erreurs")

Pour corriger les défauts de ce programme, j'ai fait beaucoup de tentatives au pif :-(
Ci-dessous un exemple parmi les nombreuses commandes fausses que j'ai essayé :
# commande fausse 1 :
find répertoire -name *.flac | xargs -i flac -t --totally-silent "{}" || echo "{}"

J'ai donc la tête pleine de questions :
1) Comment ne renvoyer que le nom des fichiers défectueux?
2) Pourquoi la ligne suivante ne marche pas :
find répertoire -name *.flac | xargs -i flac -t "{}" >> fichier
3) Pourrais-tu me donner quelques détails sur la commande "xargs", je n'ai rien compris (ou presque) à la page de manuel! Il m'a fallut plusieurs heures d'essais pour trouver une commande qui fonctionne (mes fichiers contenaient des espaces alors j'ai essayé l'option "-0" qui marchait partiellement puis j'ai fini par trouver que c'était l'option "-i" dans mon cas...)!


- Méthode de Sve@r :
Tou d'abord merci pour ton cours, je n'ai pas fini de le lire mais il m'a beaucoup aidé (je travaillais auparavant sur un cours en anglais : [www.freeos.com]).
J'ai dans un premier temps cherché à mettre en application mes idées premières (utilisation d'un fichier temporaire, utilisation de "head" et "tail"...) en utilisant tes conseils. Il m'a fallut beaucoup de temps pour l'écrire sans fautes et il marche nickel :-)
#! /bin/bash
## testflac1.sh (utilise une boucle for)
## Programme qui teste l'intégrité de tous les fichiers .flac contenus récursivement 
## dans le répertoire passé en argument et qui renvoie la liste des fichiers corrompus.

# Etape 1 : création de la liste des fichiers flac contenus récursivement dans le répertoire
find $1 -name *.flac > /tmp/testflac$$ 		

# Etape 2 : recherche du nombres de fichiers de la liste (i.e du nombre de lignes du fichier /tmp/testflac$$) 
n=($(wc -l /tmp/testflac$$)) && n=${n[0]}

# Etape 3 : test des fichiers flac
for ((i=n ; i>=1 ; i=i-1)) 
do	
	# Etape 3.1 : chargement de la ligne n-i+1 du fichier /tmp/testflac$$ dans fichier
	fichier=$(tail -n$i /tmp/testflac$$ | head -n1)
	# Etape 3.2 : test du fichier chargé dans la variable "fichier"
	if !(flac -t --totally-silent "$fichier")
		then 
		echo "$fichier"
	fi
done
rm /tmp/testflac$$
Mais les étapes 2 et 3.1 ne me satisfont pas... j'ai donc quelques questions dessus :
4) Y-a-t-il une autre façon de compter les lignes dans l'étape 2?
5) Je n'ai pas réussi à imbriquer les commandes "tail" et "head" en niveaux de sous-execution dans l'étape 3.1... Comment faire? J'ai essayé ceci :
fichier=$(head -n1 $(tail -n$i /tmp/testflac$$))

Sinon après avoir réussi à obtenir un programme fonctionnel, j'ai essayé ta première méthode avec la boucle while (j'ai eu du mal car je n'avais pas bien compris sur le coup) et cela donne ce programme :
#! /bin/bash
## testflac2.sh (utilise une boucle while)
## Programme qui teste l'intégrité de tous les fichiers .flac contenus récursivement 
## dans le répertoire passé en argument et qui renvoie la liste des fichiers corrompus.

# Etape 1 : création de la liste des fichiers flac contenus récursivement dans le répertoire
find $1 -name *.flac > /tmp/testflac$$ 		

# Etape 2 : recherche du nombres de fichiers de la liste
n=($(wc -l /tmp/testflac$$)) && n=${n[0]}

# Etape 3 : test des fichiers flac
while read fichier
do
	# test du fichier chargé dans la variable "fichier"
	if !(flac -t --totally-silent "$fichier")
		then 
		echo "$fichier"
	fi
done < /tmp/testflac$$
rm /tmp/testflac$$

Pour la dernière méthode que tu me proposes (la plus complexe), je vais attendre de finir de lire ton cours pour essayer parce que j'avoue que je ne pige pas tout lol

J'ai sinon d'autres questions :
6) Comment rendre une commande "invisible", i.E ne pas avoir d'affichage à l'écran? J'ai utilisé l'option "--totally-silent" pour la commande flac mais j'aurais aimé connaître une méthode générale!
7) Quelle est la différence entre un pipe "|" et la combinaison "| xargs"? Je crois ne pas avoir saisi la subtilité...

Merci d'avances pour vos réponses! J'espère que j'ai pas été trop long ;-)

Poste le Sunday 10 December 2006 18:03:25
Répondre     Citer    
Re: Script bash : tester récursivement tous les flacs d'un répertoire.

Citation
Remi G.
Comment rendre une commande "invisible"
Si par invisible tu veux dire ignorer sa sortie standard et d'erreur, il suffit de rediriger celles-ci vers un puits
commande invisible > /dev/null 2>&1
Mais on pourra toujours savoir que la commande (si elle dure assez) s'exécute - avec ps ou top
Citation
Remi G.
Quelle est la différence entre un pipe "|" et la combinaison "| xargs"
le pipe redirige la sortie standard d'une commande (à sa gauche) vers l'entrée standard de l'autre (à droite du pipe). Et xargs est documenté, donc RTFM
man xargs

----

Basile STARYNKEVITCH

Membre de l'APRIL « promouvoir et défendre le logiciel libre » - adhérez vous aussi à l'APRIL!

Projet logiciel libre: RefPerSys

Poste le Sunday 10 December 2006 18:33:23
Répondre     Citer    
Re: Script bash : tester récursivement tous les flacs d'un répertoire.
Envoyé par: Sve@r

Citation
Rémi G.
1) Comment ne renvoyer que le nom des fichiers
défectueux?
Comment tu sais qu'un fichier est défectueux ??? Répond à cette question et essaye de la traduire en algorithme simple que tu traduiras ensuite en shell

Citation
Rémi G.
2) Pourquoi la ligne suivante ne marche pas :
find répertoire -name *.flac | xargs -i flac -t "{}" >> fichier
Parce que t'as lu ni le man de "find" ni le man de "xargs"
Les accolades {} sont associées à find et non à xargs. elles symbolisent le nom du fichier.
Par exemple, si tu veux lancer une copie de tous les fichiers de /home dans /tmp, il te faudra à un moment donné faire "cp". Mais la commande "cp" prend 2 paramètres: le fichier d'origine et le dossier de destination. Comment tu indiques à find où placer le nom du fichier en paramètre ? Grâce aux accolades
find /home -type f -exec cp {} /tmp \:

Citation
Rémi G.
3) Pourrais-tu me donner quelques détails sur la commande "xargs", je n'ai rien compris (ou presque) à la page de manuel!
C'est vrai que "xargs" est une commande un peu compliquée car c'est une commande qui sert à lancer une autre commande !!!
Exemple simple: Imagine que tu veulles faire un "rm -f" de tous les fichiers situés dans une liste.
Syntaxe de base:
rm -f `cat liste`
Ou bien
rm -f $(cat liste)
Dans ce genre de syntaxe, le shell commence par faire "cat liste" ce qui renvoie à l'écran toute une liste de noms puis fait "rm -f <la_liste_de_noms>". Si cette liste est trop longue, "rm" refuse car toute commande possède une limite en terme de nombre d'arguments. Solution: Mettre le "rm -f" dans une boucle sur chaque nom.
Pour améliorer le travail des utilisateurs, les pro Unix ont créé la commande "xargs" qui se charge elle-même de la boucle. Il ne te reste plus qu'à lui passer la commande à effectuer car xargs ne la connait pas !!!
cat liste |xargs rm -f

Citation
Rémi G.
# Etape 2 : recherche du nombres de fichiers de la
liste (i.e du nombre de lignes du fichier /tmp/testflac$$)
n=($(wc -l /tmp/testflac$$)) && n=${n[0]}
4) Y-a-t-il une autre façon de compter les lignes dans l'étape 2?
Bien sûr. Tu écrits "n=${n[0]}" parce que t'as bien remarqué que "wc -l fic" te donne à l'écran le nb de lignes suivi du nom de fichier et que toi, tu ne désires que le nb de lignes.
Mais t'as pas tenté de voir ce que donne "cat /tmp/testflac$$ |wc -l" ??? Ben comme "wc" ne reçoit pas de fichier mais une entrée standard il ne t'affiche aucun nom !!!

Citation
Rémi G.
5) Je n'ai pas réussi à imbriquer les commandes
"tail" et "head" en niveaux de sous-execution dans
l'étape 3.1... Comment faire? J'ai essayé ceci
:fichier=$(head -n1 $(tail -n$i /tmp/testflac$$))
Tu n'as pas encore bien perçu la signification de "$(commande)"
Quand tu écris "... $(commande) ...", le shell exécute la ligne en remplaçant "$(commande)" par "tout ce que la commande affiche"
Ex: a=$(pwd) => Le shell exécute "a=xxx", la chaîne "xxx" provenant de l'affichage de la commande "pwd".
Donc si tu écrits "head -n1 $(tail -n$i /tmp/testflac$$)"
Le shell voit "head -n1 xxx", ce "xxx" provenant de la commande "tail" c.a.d. une liste de lignes.
Mais l'argument qu'on donne à "head" n'est pas sensé être une suite de lignes, c'est censé être un nom de fichier !!!
Si tu veux enchaîner deux commandes, c'est à dire donner à manger à la commande de droite ce que la commande de gauche écrit, faut utiliser le pipe !!!
fichier=$(tail -n$i /tmp/testflac$$ |head -n1)

Citation
Rémi G.
Pour la dernière méthode que tu me proposes (la plus complexe), je vais attendre de finir de lire ton cours pour essayer parce que j'avoue que je ne pige pas tout lol
Je te conseille de bien comprendre le principe.
Il faut commencer par te souvenir que chaque script, chaque programme Unix possède 3 canaux IO numérotés 0, 1 et 2.
Ensuite, il te faut juste savoir que, grâce à "exec", il t'est possible de créer d'autres canaux à volonté (jusqu'à 64 si mes souvenirs sont bons mais ça peut avoir changé) associés à des fichiers; les canaux input étant symbolisés par "<", lex canaux output étant symbolisés par ">". Et comme les chiffres 0, 1 et 2 sont déjà utilisés, le n° suivant utilisable est le 3. Donc tu peux créer des canaux numérotés de 3 à 64.
Exemple
exec 3</etc/passwd      # Associe le canal 3 en input au fichier "/etc/passwd"
exec 4 >toto            # Associe le canal 4 en output au fichier "toto"
Ensuite, dès que tu veux écrire dans un canal, il te suffit de dire "je redirige le flux 1 (ou 2) vers le canal de mon choix en utilisant le signe "&" bien connu des programmeurs de "C" signifiant "adresse de".
Exemple
echo "salut" 1>&4    # Tout ce qui passe normallement par le canal 1 ira dans le 4 c.a.d. dans le fichier "toto"

Et si tu veux lire une info prise dans le fichier "/etc/passwd", il te suffit de dire "tout ce que je lis sera pris à partir du canal 3"
Exemple
read var 0<&3
# Toute ligne sensée être lue depuis le clavier et stockée dans "var" sera en fait lue depuis le tampon 3 contenant à l'origine "/etc/passwd"
# Dans le même temps, ce tampon mémoire perdra la ligne lue (mais "/etc/passwd" n'est pas affecté)

Une fois ceci bien assimilé, tu peux l'utiliser ou pas suivant ton besoin. Je t'ai expliqué le danger de la redirection en fin de "done", où toute lecture clavier placé dans la boucle sera elle-aussi prise dans la redirection. si t'es pas géné par ce pb possible parce que tu ne fais pas de lecture clavier, tu peux sans soucis utiliser la méthode de redirection simple sans créer de canal spécialisé...

L'homme qui murmurait à l'oreille des pingouins
[fr.lang.free.fr]

Poste le Sunday 10 December 2006 21:17:24
Répondre     Citer    

Veuillez vous authentifier auparavant pour commenter.

 

Ce forum !
Script bash : tester récursivement tous les flacs d'un répertoire.
Pour poser vos questions sur les scripts shell, le Perl, le C, etc... Attention : nous ne sommes pas des spécialistes du dev, ce forum est juste pour de petites aides ponctuelles concernant le développement et les outils de développement.

Sauf mention contraire, les documentations publiées sont sous licence Creative-Commons