Obtenir un "ls -R" formaté de manière utilisable : avec find

Salut !

Le formatage de la sortie de la commande

[code]$ ls -R
.:
autorun.inf dists install md5sum.txt pool README.mirrors.html README.txt
css doc live pics README.html README.mirrors.txt syslinux

./css:
debinstall.css debinstall-print.css

./dists:
frozen lenny stable testing unstable[/code]

ne me plaît pas du tout. On ne peut pas réutiliser ça avec un script. Ce qu’il faut c’est une sortie du genre :

./autorun.inf ./css/debinstall.css ./css/debinstall-print.css ./dists/frozen ./dists/lenny/main/binary-i386/... ./dists/lenny/main/debian-installer/... ./dists/lenny/Release ./dists/stable ./dists/testing ./dists/unstable ./md5sum.txt

Je rame un peu car je ne suis pas habitué aux langages de scripts. J’ai réussi à trouver une solution qui fonctionne à peu près mais qui est absolument horrible :

fichier lsrec.sh

[code]#!/bin/sh

Le premier argument contient le chemin du repertoire a lister.

Si le chemin n’est pas termine par un slash, en rajouter un.

echo $1 | sed ‘s%[^/]$%&/%’ > tmp1
echo “” > tmp2

tant qu’il reste des repertoires a derouler, executer lsrec.awk

while [ “cat tmp1” != “cat tmp2” ]; do
cat tmp1 | awk -f lsrec.awk > tmp2
cat tmp2 | awk -f lsrec.awk > tmp1
done[/code]

fichier lsrec.awk

[code]#!/usr/bin/awk -f

si une ligne n’est pas le nom d’un repertoire, ne rien faire

/[^/]$/ {print $0}

sinon, lister le repertoire en gardant bien tout le chemin grace a sed

//$/ {system(“ls -1p “”$0"” | sed ‘s%…*%"$0"&%’")}[/code]

Le principe c’est de faire des itérations comme ceci dans un fichier temporaire :

fichier tmp

fichier tmp

./binary/ ./binary.img ./binary.list ./binary.packages

fichier tmp

./binary/autorun.inf ./binary/css/ ./binary/dists/ ./img ./list ./packages

fichier tmp

./binary/autorun.inf ./binary/css/debinstall-print.css ./binary/css/debinstall.css ./binary/dists/frozen ./binary/dists/lenny/ ./binary/dists/stable ./binary/dists/testing ./binary/dists/unstable ./img ./list ./packages

et ainsi de suite
Si quelqu’un a une idée pour rendre ça plus propre, je suis preneur.

Inspire toi de ça : viewtopic.php?f=9&t=24054

commande find ?

Pourquoi faire simple quand on peut faire compliqué ? Merci.

Pour le lien que tu m’as donné MisterFreez, les langages utilisés disposent tous d’un moyen de traiter les entrées une à une.

M’enfin je vais quand même essayer de le finir, ne serait-ce que pour progresser en awk, qui à l’air très sympahtique.

Selon l’usage et si tu utilise une shell moderne (commendre bash > 4 ou zsh) tu as les globing :
ls -1 */

Tu peux traiter les données une à une avec find également, voir exemple :

Ce code va chercher tous les fichiers de plus de 14 jours dans le dossier défini dans $DIRBACKUP avec le motif défini et les supprimer.

J’ai réussit à faire quelque chose d’un peu plus présentable, il en allait de mon honneur :

[code]#!/bin/sh

function process_an_entry {
if [ -d “$1” ]; then
ls -1 “$1” | sed “s%.*%$1/&%” | read_input
else
echo "$1"
fi
}

function read_input {
while read line; do
process_an_entry "$line"
done
}

process_an_entry “$1”[/code]

Mais ce n’est pas encore parfait :

  • si un nom de répertoire contient un pourcentage % , ça va provoquer une erreur chez sed
  • si un répertoire contient un lien sybolique vers l’un de ses répertoires parents, ça créé une boucle infinie
  • c’est moins rapide que la commande find :

[code]$ time find ./repertoire_de_52000_elements
[…]
real 0m43.2s
user 0m00.3s
sys 0m42.9s

$ time ./lsrec.sh ./repertoire_de_52000_elements
[…]
real 1m00.5s
user 0m12.4s
sys 0m50.5s[/code]

Les temps sont une moyenne de trois exécutions.

@MisterFreeze : j’ai essayé la syntaxe */ , elle me donne le même formatage que ls -R

Pourquoi cherches tu à produire le même comportement d’une commande qui existe déjà ?

C’est juste pour m’entrainer. Et comme j’était un peu mal à l’aise d’avoir posté un code trop sale, je me suis senti obligé de le corriger.
Je ne pense pas que la commande find soit fournie par POSIX (sinon ils l’auraient marqué sur linux-kheops). Ca me fait une version de remplacement au cas ou que find ne soit pas installé.

J’en profite pour mettre la nouvelle version :

[code]#!/bin/sh

function lsrec {
while read path; do
if [ -d “$path” ]; then
ls “$path” | { while read name; do echo “$path/$name”; done;} | lsrec
else
echo "$path"
fi
done
}

echo “$1” | lsrec[/code]

J’ai pu me débarasser de sed et donc du problème des pourcentages. Il ne reste plus qu’à trouver un moyen de ne pas suivre les liens symboliques pour arriver à quelque chose de potable.

Et bien voilà, les liens symboliques ne sont plus suivis :

[code]#!/bin/sh

function lsrec {
while read path; do
if [ -d “$path” -a ! -h “$path” ]; then
ls “$path” | { while read name; do echo “$path/$name”; done;} | lsrec
else
echo "$path"
fi
done
}

echo “$1” | lsrec[/code]

Après la seule optimisation que je voit c’est de ne pas lancer la fonction récursive sur les feuilles.

Soit tu écris :

[code]#!/bin/bash

function lsrec {
while read path; do
if [ -d “$path” -a ! -h “$path” ]; then
ls “$path” | { while read name; do echo “$path/$name”; done;} | lsrec
else
echo "$path"
fi
done
}

echo “$1” | lsrec[/code]
Soit :

[code]#!/bin/sh

lsrec () {
while read path; do
if [ -d “$path” -a ! -h “$path” ]; then
ls “$path” | { while read name; do echo “$path/$name”; done;} | lsrec
else
echo "$path"
fi
done
}

echo “$1” | lsrec[/code]
Mais sinon c’est pas moche :slightly_smiling:

[quote=“MisterFreez”]Mais sinon c’est pas moche :slightly_smiling:[/quote] :smt026

Déjà merci pour cette façon de déclarer les fonctions, qui est plus portable (syntaxe Bourne).

Mais j’ai pas encore dit mon dernier mot :

Déjà on peut retirer l’une des deux boucles “while read” :

[code]#!/bin/sh

lsrec() {
ls -U “$1” | while read name; do
if [ -d “$1/$name” -a ! -h “$1/$name” ]
then lsrec "$1/$name"
else echo $1/$name
fi
done
}

lsrec “$1”[/code]

Ca ne m’a pas fait gagner beaucoup en temps d’exécution. A mon avis, c’est l’utilisation du pipe qui est pénalisant.
Un autre moyen de transmettre les résultats de la commande ls est d’utiliser une boucle for :

lsrec() { for name in `ls -U "$1"`; do # pb pathname="$1/$name" if [ -d "$pathname" -a ! -h "$pathname" ] then lsrec "$pathname" else echo $pathname fi done }

Par contre ça bug lorsqu’un nom de répertoire contient un espace. C’est à cause de la ligne ou il y a marqué “# pb” : la substitution de ls -U "$1" ne conserve pas en un seul morceau les nom de fichier/répertoire avec un espace dedans (comme le fait ls d’habitude).
Peut-être qu’en jouant sur la valeur de la variable IFS (internal field separator) ça peut améliorer la situation ?

Il y a aussi une solution alternative qui remplace “ls” par le mécanisme de substitution d’expressions régulières par le shell : si on compare uniquement le temps “système”, cette solution est plus rapide que la commande find : 42,5 sec pour find contre 39,5 sec pour ceci :

lsrec() { for path in "$1"/*; do if [ -d "$path" -a ! -h "$path" ] then lsrec "$path" else echo $path fi done }

Mais bon le temps “utilisateur” est toujours 40 fois plus long que la commande find (0,25 sec pour find contre 9,6 sec pour le script …) et on perd l’avantage des options de ls (prendre les fichiers cachés ou non, ordre du tri … etc), donc c’est juste pour apprécier la concision.

Il faut que je trouve un moyen de régler le problème de découpage du résultat de ls.