Contenu | Rechercher | Menus

Annonce

Si vous avez des soucis pour rester connecté, déconnectez-vous puis reconnectez-vous depuis ce lien en cochant la case
Me connecter automatiquement lors de mes prochaines visites.

À propos de l'équipe du forum.

#1 Le 23/05/2008, à 20:37

Pouzy

Souci sur une fonction récursive de parcours d'un dossier (bash)

Bonjour tout le monde !

J'ai un petit souci sur une fonction bash, dont voici un condensé, je n'ai gardé que la partie qui nous intéresse :

fonction machin()
{
nbA=`ls -al $1 | wc -l`
nbB=`ls -al $2 | wc -l`
local -i i=4; #Je le mets à 4 pour ne pas avoir . .. et la ligne de total

while [ $i -le $nbA ]
do
    local -i i2=4;
    while [ $i2 -le $nbB ]
    do
        i2=`expr $i2 + 1`

			if [ $nomA == $nomB ]      				#même NOM
			then 		
					if [ $typeA == "d" -a $typeB == "d" ]   #DOSSIER tout les 2
					then 
						echo "Cool, récursion (deux dossiers, on entre dedans)";
						#Récursivement on utilise la fonction avec les bons dossiers
						#On appelle la fonction avec les dossiers en question
						ecrirejournal $1/$nomA $2/$nomB;
						break;
					fi
			else 							#pas même NOM
				echo "On continue la boucle 2";  
 			
			fi

		#Incrémentation de i2			  	
		i2=`expr $i2 + 1`
	done	#On sort de la boucle 2
	#On incrémente i
	i=`expr $i + 1`
done #fin boucle 1

echo "Fonction terminée !"
}

Vous allez voir l'intêret du "fonction terminée" : en fait, c'est pour voir s'il plante ou s'il continue la fonction. Et après la récursivité, on a deux fois de suite "Fonction terminée !". Ca veut dire que le script saute à la find e la fonction sans continuer de regarder les autres dossiers. Sûrement un problème lié aux i et i2, mais je les ai retourné dans tous les sens sans trouver.

J'ai peut être oublié quelques accolades ou trucs du genre ici, mais c'est pour le condensé.
Quelqu'un aurait-til une idée du pourquoi ? Voici un exemple d'utilisation :

boucle---------------------------------------------
nom DIR1: hoho (DOSSIER1)
nom DIR2 : gloubi (DOSSIER2)
On continue la boucle 2
i2 : 5
i : 7
nom DIR2 : gogo (DOSSIER2)
On continue la boucle 2
i2 : 6
i : 7
nom DIR2 : hoho (DOSSIER2)
Cool, récursion (deux dossiers, on entre dedans)
DOSSIER1/hoho DIR1
DOSSIER2/hoho DIR2
nbA = 6 nbB =6
boucle---------------------------------------------
nom DIR1: cxc (DOSSIER1/hoho)
nom DIR2 : cxc (DOSSIER2/hoho)
Même type
boucle---------------------------------------------
nom DIR1: gloubi (DOSSIER1/hoho)
nom DIR2 : cxc (DOSSIER2/hoho)
On continue la boucle 2
i2 : 5
i : 5
nom DIR2 : gloubi (DOSSIER2/hoho)
Même type
boucle---------------------------------------------
nom DIR1: test4 (DOSSIER1/hoho)
nom DIR2 : cxc (DOSSIER2/hoho)
On continue la boucle 2
i2 : 5
i : 6
nom DIR2 : gloubi (DOSSIER2/hoho)
On continue la boucle 2
i2 : 6
i : 6
nom DIR2 : test4 (DOSSIER2/hoho)
Même type
Fonction terminée !
Fonction terminée !

On voit qu'il entre dans /DOSSIERx/hoho, mais au lieu de continuer une fois que celui ci a été exploré, il s'arrête ! (deux fois "fonction terminée"). Je voudrais qu'il explore le dossier en entier parce qu'il reste des trucs non lus dans DOSSIER1 et DOSSIER2, et je bloque. (j'ai mis les i et i2 en echo pour faciliter)

Merci !

#2 Le 24/05/2008, à 09:43

Pouzy

Re : Souci sur une fonction récursive de parcours d'un dossier (bash)

En y regardant de plus près, je vois que la premiere boucle n'est faite qu'une seule fois en sortie ! Au niveau des fonctions terminées, j'ai ceci : J'ai simplement rajouté un echo "début boucle" avant le while de la premiere boucle, et un echo "fin boucle" après le done de la premiere boucle aussi (pour tout encadrer dans la fonction) et je me retrouve avec ceci : (Il y a trois fonctions terminées car j'ai créé un autre dossier dans lequel rentrer récursivement, c'est normal)

Fonction terminée !
i1 5  i2 5
fin boucle 1
Fonction terminée !
i1 6  i2 5
fin boucle 1
Fonction terminée !
i1 7  i2 5

Peut être un problème au niveau du break qui vire une des boucles ? Je suis un peu perdu.

Merci

#3 Le 24/05/2008, à 10:18

snapshot

Re : Souci sur une fonction récursive de parcours d'un dossier (bash)

Bonjour !

A mon avis ton algorithme est probablement bon, mais le shell n'est pas le bon langage pour l'implémenter. Le shell est très bon pour coller ensemble tout un tas d'utilitaires unix, mais moins comme langage passe-partout.

Si j'étais à ta place, sois j'essaierai dans un autre "vrai" langage comme python, sois je reformulerai mon algo pour qu'il colle mieux à la philosophie du shell. Ce qui me fait dire ça, c'est que tu utilises des variables incrémentées. C'est ok, mais c'est rare quand on en a besoin en shell, tout simplement car c'est un langage de très haut niveau et cette fonction est en général déléguée à des appels d'utilitaires système. En gros, le truc du shell est d'en faire le moins possible, de ne pas s'attarder sur les détails, et d'appeler un maximum de programme externe spécialisés. Donc à ta place, plutot que de faire 2 compteurs, j'essaierai d'appeler find pour avoir 2 listes des répertoires uniquement, que je mettrai en relation avec un join ou un diff, et qu'il ne me restera plus qu'à parcourir... tout ceci sans aucun compteur !

En fait, que veux tu faire exactement ? Peux tu donner un jeu d'essai de tes répertoires ainsi que le résultat escompté ?

Mais si tu veux essayer dans ta voie, pas de soucis. Simplement, tu peux remplacer les i=`expr $i + 1` par des (( i+ )) dont l'énorme avantage est que cette ligne ne créera pas un nouveau processus consommateur de ressource à chaque incrémentation ! Regarde cette discussion à ce sujet pour voir ce qui est en jeu.

Pour le if [ $typeA == "d" ], il y a un moyen plus shell de faire ça... smile d'abord, il faudrait mettre if [ "$typeA" == "d" ] car si $type est null, le test sera [ == d ] et ça provoquera une erreur de syntaxe (car il n'y a rien avant le == ). Certains écrivent if [ x$typeA == xd ] dans la même optique, mais c'est moins beau et moins lisible. Mais surtout, pour tester si un nom est un répertoire, il faut utiliser [ -d "$typeA" ], sans essayer de passer par un test de variable chaine ! à moins que tu n'ai écrit ceci pour simplifier ton exemple ???


Edit:
shell et smiley ne font pas bon ménage :-)

Dernière modification par snapshot (Le 24/05/2008, à 10:22)


Pensez à mettre [Résolu] dans le titre une fois votre problème réglé !

Hors ligne

#4 Le 25/05/2008, à 13:53

Pouzy

Re : Souci sur une fonction récursive de parcours d'un dossier (bash)

Bonjour et merci de ta réponse !
En fait, le but est de regarder de façon récurisve si les dossiers contiennent les mêmes fichiers ou non, et déterminer des actions à faire sinon.

Exemple, une arborescence comme ça :

D1/
D1/test/pouet
D1/test/toto
D1/test1

D2/
D2/test/pouet

Doit sortir : "test 2 en trop dans D2" et "toto est en trop dans D1/test", et effectuer une action que je déterminerai par la suite.

Et le but est d'entrer récursivement dans les dossiers, donc en relancant la fonction pour D1/test
Ca marche jusque la, mais avec mon script actuel, le traitement s'arrête à test et ne regarde plus test1 : il termine le script sans faire la suite du dossier dans lequel il est entré récursivement.
Je me demande donc si c'est une question d'incrémentation des variables ? Comment peut il se faire que la fonction marche jusqu'à la premiere récursion et qu'elle tourne dans le vide après (la premiere boucle) ?

Pour find, je ne vois pas comment l'utiliser de façon récursive... ? Créer un fichier contenant la liste des fichiers à parcoruir et le lire ligne par ligne ? Je pense rester pour l'instant sur ma lancée, et peut être voir ensuite si je n'y arrive vraiment pas.

Merci !

#5 Le 25/05/2008, à 19:08

snapshot

Re : Souci sur une fonction récursive de parcours d'un dossier (bash)

Il y a un piège, c'est d'utiliser exit pour sortir d'une boucle/procédure. En fait, ça termine le processus.. et ça ressemble à ce que tu décrit !

Si tu ne sais pas se qui se passe, met set -x en en-tête de ton script, ou à l'endroit où tu veux tracer ton script : ça demande au shell d'afficher chaque ligne qu'il analyse avant de l'executer. Pour l'enlever : set +x

pour obtenir la liste des fichiers d'un répertoire :

find D1 -maxdepth 1 -mindepth 1 -type d

mais si tu veux comparer avec D2, la liste sera préfixée avec D1/ et D2/, donc :

(cd D1; find -maxdepth 1 -mindepth 1 -type d)

Le fait de mettre entre parenthèse rend "local" le cd.

Ensuite, pour comparer facilement entre deux répertoires, il faut classer par ordre alphabétique la sortie :

(cd D1; find -maxdepth 1 -mindepth 1 -type d | sort)

Pour effectuer la comparaison, la commande diff est là pour ça. Elle demande deux fichiers :

(cd D1; find -maxdepth 1 -mindepth 1 -type d | sort) > /tmp/fic1
(cd D2; find -maxdepth 1 -mindepth 1 -type d | sort) > /tmp/fic2
diff /tmp/fic1 /tmp/fic2
rm /tmp/fic1/tmp/fic2

Mais la gestion des fichiers temporaires n'est pas propre du tout : elle echouera avec un appel recursif car les noms sont fixes, et les fichiers s'écraseront mutuellement à chaque appel. Confions ce problème au shell, avec l'opérateur <() qui fabrique un fichier temporaire du résultat de la commande donnée entre parenthèse (en fait, c'est un pipe, et c'est encore mieux):

diff <(cd D1; find -maxdepth 1 -mindepth 1 -type d | sort) <(cd D2; find -maxdepth 1 -mindepth 1 -type d | sort)

Cette commande devrait donner le résultat suivant :

2c2
< ./test1
---
> ./test2

C'est pas génial comme sortie, car les répertoires identiques ne s'affichent plus... Un petit tour dans la page man indique qu'il faut utiliser des options supplémentaires pour paramétrer la sortie :

diff --unchanged-line-format="= %L" --new-line-format="+ %L" --old-line-format="- %L" <(cd D1; find -maxdepth 1 -mindepth 1 -type d | sort) <(cd D2; find -maxdepth 1 -mindepth 1 -type d | sort)

Qui produit le résultat suivant, prêt à être exploité :

= ./test
- ./test1
+ ./test2

Ensuite, il ne reste plus qu'a donner ce flux en entrée à une boucle while :

diff --unchanged-line-format="= %L" --new-line-format="+ %L" --old-line-format="- %L" <(cd D1; find -maxdepth 1 -mindepth 1 -type d | sort) <(cd D2; find -maxdepth 1 -mindepth 1 -type d | sort) | while read action directory
do
  case $action in
  +) echo "Répertoire en 2 mais pas en 1: $D2/$directory"
    ;;
  -) echo "Répertoire en 1 mais pas en 2: $D1/$directory"
    ;;
  =) echo "Répertoire en 1 et en 2: $D1/$directory"
    ;;
  *)
    echo "Erreur interne dans l'analyse de diff ! " >&2
    exit 2
    ;;
  esac
done

Et ensuite, l'empaqueter dans une procédure qu'on rend récursive :

function parcours() {
  local D1=$1; 
  local D2=$2
  diff --unchanged-line-format="= %L" --new-line-format="+ %L" --old-line-format="- %L" <(cd $D1; find -maxdepth 1 -mindepth 1 -type d | sort) <(cd $D2; find -maxdepth 1 -mindepth 1 -type d | sort) | while read action directory
  do
    case $action in
    +) echo "Répertoire en 2 mais pas en 1: $D2/$directory"
      ;;
    -) echo "Répertoire en 1 mais pas en 2: $D1/$directory"
      ;;
    =) echo "Répertoire en 1 et en 2: $D1/$directory et $D2/$directory"
      parcours $D1/$directory $D2/$directory
      ;;
    *)
      echo "Erreur interne dans l'analyse de diff ! " >&2
      exit 2
      ;;
    esac
  done
}

Voilà... il resterait à la rendre un peu plus jolie, et peut être enlever le ./ que find ajoute sans arrêt. C'est valide du point de vue système, mais un peu déroutant


Pensez à mettre [Résolu] dans le titre une fois votre problème réglé !

Hors ligne

#6 Le 26/05/2008, à 21:34

Pouzy

Re : Souci sur une fonction récursive de parcours d'un dossier (bash)

Coucou et merci à toi !

J'ai réussi ce que je voulais faire avec mes deux boucles, via un pipe sur le while. Mais ton code est très instructif, j'en prends note même si pour l'instant je ne le comprends pas intégralement !

Merci d'avoir passé un peu de ton temps à m'aider.

Pouzy