Transformation de Burrows-Wheeler


22 juillet 2007

J'avais besoin d'un programme qui réalisait la transformation de Burrows-Wheeler (TBW) sur de simples lignes de texte et non pas sur des énormes fichiers binaires comme c'est le cas d'habitude. J'ai donc pondu le code qui suit :

#include <iostream>

int LineLen;

int Cmp(const void* a,const void*b)
{
    return strncmp(*(const char**)a,*(const char**)b,LineLen);
}

int main()
{
    int i;
    char    *Line=new char[1000], **Rows=new char*[1000];
    while(gets(Line)){
        LineLen = strlen(Line);
        //On utilise un double buffer pour la ligne courante
        memcpy(Line+LineLen, Line, LineLen);
        //Les permutations en utilisant de simples pointeurs
        for(i=0; i<LineLen; i++)        Rows[i]=Line+i;
        //On trie les pointeurs en fonction de leurs données
        qsort(Rows, LineLen, 4, Cmp);
        //On cherche la ligne originale dans la liste triée.
        i=0;
        while (i<LineLen && strncmp(Rows[i], Line, LineLen))    i++;
        int OriginalIndex = i;
        //
        printf("%d\t",OriginalIndex);
        for(i=0; i<LineLen; i++)    printf("%c", Rows[i][LineLen-1]);
        printf("\n");
    }
    delete []Line;
    delete []Rows;
}

pour les données d'entrée suivante :

ABCABC
abracadabra
BurrowsWheelerTransform
gaattcctgggcctggggctgtggcagctgcctcgtccct
tcacctcctggcttattctctccctccatatcttagcaat
ctcatgcctgtaatcccagcattttggtaggccaaggcgg
gtcggatcacctgaggtcaggagttcgagggccagcctga
tgaccatggtgaaaccccatctctactaaaaatacaaaat
taatcgggcatggtggcacatgcctgtaatcccagctact
ctgaggcaggagaatcgcttaaacccaggaagtggaggtt
tcagtgagctgagattgtgccattgcactccagcctgggt
aacaagagcaaaactccatcaaaaaaaataatatatgtat
atatatattacaattttatatatatatacacattatgtaa
taccattttatatatatacattacgtaatggtaaatgttt
gatcgtctccctggagaataatccccaatgtgaaattact
ctaagtggtgggattacaggcgtgtgcccaacttttcctg
agccttttgaggctgacaccagaggtagaagcccagcctc
tccccactggccatgtggggagaggctccagcctgcagca
accagggatctggcctcaagtgatgccccaacagtgggcg
1001110010001111110101011101101110010100
1100111011000100110110010000011011100111
1000100001000001010101101000010110000011
0000000000111111000100100001011010100011
0011011000001111001010010001001011101111
0010110110101011100100010011000000010000
1101011110010100001000111001000100011001
0011001010001001001110100000101001001101
1110110100001001100110001010101001100100
1110001011101110110000110100011011100001

le programme renvoie :

0   CCAABB
2   rdarcaaaabb
0   mrsrhelsWerafreToruwnBo
16  gcagtgctgtccgccgtgtgagtggtgtctgtcccgccca
28  cctctatgtcttcatctctctgagttattcccctacccaa
17  ctctaacccctggctgggcagtggaacttgggcaatctta
31  cccgggggtctgagttcccttgggaacgaagaaagtgccg
37  tacgaaaaatgataccaacccacaatttttgcaccagtaa
31  ttctcaaccgcagctgtgcaggtatgcttggtcaaagacc
17  taggagccggaacgcattggagtggcaataaagtatcgcg
29  cgcgccggctctggagcttattaatgatgtccgtgctgaa
8   caaacaaatacataaagtaatcttgactaaataagaaaca
11  tacttctttttattttcaaaatgtaaaaataataaatata
25  ttatttttttaaccacaatgctggattaaataatatttga
25  ggtcatgaagaacccttcttactagttctatacaggccaa
13  cttacagcacgtggcagtgtgatttgaccttgcgggattc
7   ggctcaccggacctacgggcgcattaaaagaaggcctttc
35  cgccgcccgcgcctgctggcagatagaggtagttcaccga
3   ccagcacggctcacacgggctcgttgggtgataacagacg
21  1111100100111011001010010110111100010110
30  1010101011001100100011011111001110010010
30  1110100100000000001010111001001100000100
0   1000001010101000001010010101000100101110
8   1010101110010001010110010101010110111000
13  1010000010010010001010111100101010001000
35  1101110010011000010001000011101011010100
9   1010011101111110100000000000100010010100
39  1010101111110011000010011100010001000010
36  1110100000101001110111011110011010101000

Je voulais un code qui soit rapide à implémenter mais :

  • la limite de 1000 octets n'est pas terrible pour une routine généraliste.
  • j'utilise qsort qui n'est pas optimal par rapport à mon radix sort pour des données de taille importante.
  • J'utilise gets ce qui est déconseillé !
  • On n'a pas à dupliquer la ligne de données, en compliquant un peu le code ça devrait pouvoir marcher.
  • Le buffer Rows est de trop il faut trouver une astuce pour ne pas l'utiliser il prend une mémoire folle.

Tout ceci sera fixé plus loin, pour l'instant ce qui nous intéresse c'est d'avoir un outil de test à utiliser en conjonction avec d'autres compresseurs maison.

La transformation inverse

Maintenant que l'exécutable précédent (que j'ai appelé bwt) est fait et fonctionne, comment retrouver à partir de sa sortie les données originales ?

Yep, I'm a geek !


14 juillet 2007

Extraction automatique de .torrents

Deux programmes (PHP et Bash) pour parcourir tout ThePirateBay et choper tous ses torrents sans s'embèter. Le principe étant que le script Shell/Bash commande un script PHP qui lui, se charge de tout le travail de reconnaissance de chaînes et d'extraction d'URLs. Ces deux scripts sont censés s'exécuter depuis la ligne de commande, un navigateur ne nous sert à rien ici.

#!/bin/bash

#"The Pirate Bay"-specific
BaseURL="http://thepiratebay.org"
ListPagePattern='/<a[ \n\r\t]*href="(.*tor\/[^">]*)[^>]*>(.*)<\/a>/'
TorrentPagePattern='/<a href="(.*\.torrent)">.*<\/a>/'
OtherPagePattern='/<a href="(\/brwsearch[^>"]*page=[0-9]+)"/'
FirstPage=brwsearch.php\?b\=1\&c\=501

./extract.php $BaseURL $FirstPage --get-pages "$OtherPagePattern" | sort | uniq > pages.list

pageIndex=0
for page in `cat pages.list`; do
    PageURL=page$pageIndex.html
    wget $BaseURL$page -O $PageURL      #Download de la page du site en question
    ./extract.php $BaseURL $PageURL --extract-torrents "$ListPagePattern" "$TorrentPagePattern" > page$pageIndex.list
    #
    for url in `cat page$pageIndex.list`; do
        grep $url torrents.list.done > /dev/null
        [[ $? == 0 ]] && echo "$url est déjà mis à télécharger" && continue;
        echo "On doit télécharger $url"
        dcop ktorrent KTorrent openTorrentSilently "$url";
        echo $url >> torrents.list.done
    done
    exit 12
    #
    ((pageIndex++))
done

Script shell qui appelle le PHP pour déterminer ce qui reste à télécharger
#!/usr/local/bin/php -q
<?
//Extrait d'une page toutes les sous-pages contenant un torrent et pour chaque sous-page, stocke l'URL du torrent dans un fichier local.
//
//Syntaxe :
//
//./extract.php <BaseURL> <FirstListPage> <ListPagePattern> <TorrentPagePattern> <OtherPagePattern>
//
//1 : BaseURL, l'URL de base du site visé, préfixe de toutes les adresses internes.
//2 : adresse d'une des pages contenant les liste d'éléments à extraire.
//3 : pattern repérant l'URL d'une page descriptive d'un torrent sur une page-liste.
//4 : pattern repérant l'URL d'un fichier torrent sur une page descriptive.
//5 : pattern repérant les URL des autres pages-listes à partir de celle passée en paramètre 2.
//
//Exemples d'utilisation :
//
//The Pirate Bay :
//./extract.php http://www.thepiratebay.org brwsearch.php\?b\=1\&c\=501 '/<a[ \n\r\t]*href="(.*tor\/[^">]*)[^>]*>(.*)<\/a>/' '/<a href="(.*\.torrent)">.*<\/a>/' '/<a href="(\/brwsearch[^>"]*page=[0-9]+)"/'

//TODO : penser à l'option --debug pour afficher des choses...
//DONE : faire deux options, --get-pages et --extract-torrents pour différencier les deux types d'appels de ce script

function    displayArray(&$arr)
{
    $Output = "<ul>\n";
    foreach($arr as $key=>$val)
        $Output .= "<li>$key : ".(is_array($val) ? displayArray($val) : $val)."</li>\n";
    return $Output."</ul>\n";
}
    
    $Args =& $_SERVER["argv"];
    if (count($Args) == 1)      die("Rien...");
    $BaseURL = isset($Args[1]) ? $Args[1] : "";
    $SrcFile = isset($Args[2]) ? $Args[2] : "";
    if (!strlen($SrcFile))      die("Pas d'URL de fichier source à analyser.\n");
    if (!file_exists($SrcFile))     die("Pas de fichier source à analyser.\n");
    $Contents = file_get_contents($SrcFile);
    if ($Debug) echo "Taille du fichier : ".strlen($Contents)."\n";
    //
    $Action = isset($Args[3]) ? $Args[3] : "";
    if (!strlen($Action))       die("Aucune action passée en paramètre.\n");
    switch ($Action)        {
        case    "--get-pages":
            $PatternPages = isset($Args[4]) ? $Args[4] : "";
            break;
        case    "--extract-torrents":
            $Pattern = isset($Args[4]) ? $Args[4] : "";
            $Pattern2 = isset($Args[5]) ? $Args[5] : "";
            if (!strlen($Pattern) || !strlen($Pattern2))    die("Extraction de torrents : il manque des patterns...\n");
            break;
        default:    die("Action inconnue : $Action.\n");
    }
    //Sous-pages
    if (strlen($PatternPages))      {
        $SubPages = array();
        $NbSubPages = preg_match_all($PatternPages, $Contents, $SubPages);
        //echo "Nb de matches : $NbSubPages\n";
        foreach($SubPages[1] as $PageURL)       echo $PageURL."\n";
        //echo displayArray($SubPages);
        die();
    }
    //Extraction de torrents
    $Matches = array();
    if ($Debug)     echo "Pattern = $Pattern\n";
    $NbMatches = preg_match_all($Pattern, $Contents, $Matches);
    if ($Debug)     echo "NbMatches = $NbMatches\n";
    //echo displayArray($Matches);
    $SubMatches = array();
    for($i=0; $i<$NbMatches; $i++)      {
//      echo $Matches[1][$i]."\n";
        if ($Debug) echo $Matches[2][$i]."\n";
        $DownloadPage = file_get_contents($BaseURL.$Matches[1][$i]);
        if (!$DownloadPage)     echo "Problème !\n";
        //Tentative de détection de pages erronées...
    if (strlen($DownloadPage) < 100)    {
        continue;
        echo "$DownloadPage\n";
    }
//      echo "Taille de la page téléchargée : ".strlen($DownloadPage)." octets\n";
        $Ret = preg_match($Pattern2, $DownloadPage, $SubMatches);
        $TorrentURL = $SubMatches[1];
        if ($Debug) echo "Torrent : ";
        echo (strlen($TorrentURL) ? $TorrentURL : "FALSE")."\n";
//      echo displayArray($SubMatches);
    }

?>
Moteur d'analyse de page qui extrait la hiérarchie liste-page-torrent

Aspirateur de site utilisant Fusker

Au départ était Fusker, qui agrégeait des sites épars et permettait leur consultation depuis un lieu unique. A partir d'un site de ce type on peut très facilement, en couplant Bash et PHP, rapatrier ces images localement. Voici :

#!/bin/bash

BaseURL="http://kladblog.funwithbabes.com/"
IndexURL=$BaseURL"index.php?offset=0"
for page in `wget -q -O- $IndexURL | grep "special=preview" | awk 'BEGIN{FS="<a href=\"[^\"]*\">.*</a>"} {URL=substr($0, index($0, $1)+length($1)+9); print substr(URL, 1, index(URL, "\"")-1)}'`; do
    page=$BaseURL$page
    pixList=$(wget -q -O- $page | php -r '$Contents=file_get_contents("php://stdin"); preg_match_all("@You are looking at <b>(http://[^<]*)</b>@", $Contents, $Matches); $Nb=count($Matches[1]); if ($Nb != 1) die("Oups"); $ToFusk=$Matches[1][0]; if (!preg_match("@(http://[^\[]*)\[[^\]]*\](.*)@", $ToFusk, $Matches)) die("Oups2\n"); $FP=$Matches[1]; $SP=$Matches[2]; $NbPix=preg_match_all("@(".$FP."[^<:]*).*<img src=\"\\1\"@", $Contents, $Matches); if (!$NbPix) die("Oups3"); $DirName=str_replace("/","",str_replace("http://","",$FP)); mkdir($DirName); echo $DirName."\n";  foreach($Matches[1] as $Item) echo $Item."\n";' 2>/dev/null)
    DirName=""
    for pix in $pixList; do
        if [[ ${#DirName} -eq 0 ]]; then
            DirName=$pix
            echo "Dossier : $DirName"
        else
            echo "Image : "$pix
            wget -P $DirName -nd -q $pix
        fi
     done
done

En changeant la déclaration de la variable IndexURL on peut rapatrier toutes les pages restantes. Ceci peut se faire automatiquement, mais je me suis arrêté là :).

C'est sûr que ça n'est pas ni du grand code ni du code propre mais ça fait le boulot. Il ne faut pas avoir envie de revenir dessus au bout de 6 mois c'est tout...

AJAX dans Konqueror / Safari


22 mai 2007

Du XML dans Konqueror

Pour charger un document XML dans Konqueror et le manipuler avec le DOM, il faut au préalable créer le document et charger le texte XML. Comme suit dans cet exemple JS :

    var Doc = "<"."?xml version='1.0' encoding='utf-8'?".">\
    <root2>\
        <child1>Contenu1</child1>\
        <child2>Contenu2\
            <child21>Contenu du 21</child21>\
        Fin du contenu 2\
        </child2>\
        <child3>Contenu3</child3>\
        <child4>Contenu4</child4>\
    </root2>"
    //
    var XMLDoc = document.implementation.createDocument();
    XMLDoc.loadXML(Doc);
    var Root = XMLDoc.documentElement;</code>

            <p>A partir de là on a un objet JS (Root) qui représente la version DOM de notre document XML. Si vous ne me croyez pas, essayez d'utiliser la fonction suivante qui parse le DOM en question :</p>
            <code legend='A appeler comme suit : getNodeHierarchy(Root, 0)'>function    getNodeHierarchy(node, level)
{
    var Output = typeof node+"-"+level+" : "+node.nodeName+", "+NodeTypesDisplay[node.nodeType]+", "+node.nodeValue+"\n";
    if (!node.childNodes.length)        return Output
    node = node.firstChild
    do { Output += getNodeHierarchy(node, level+1) } while (node = node.nextSibling);
    return Output
}

La fonction précédente avec notre XML d'exemple affichera :

object-0 : root, Node, null
object-1 : #text, Texte,

object-1 : child1, Node, null
object-2 : #text, Texte, On
object-1 : #text, Texte,

object-1 : child2, Node, null
object-2 : #text, Texte, Contenu2

object-2 : child21, Node, null
object-3 : #text, Texte, Contenu du 21
object-2 : #text, Texte,
Fin du contenu 2

object-1 : #text, Texte,

object-1 : child3, Node, null
object-2 : #text, Texte, Con
object-2 : b, Node, null
object-3 : #text, Texte, tenu3
object-1 : #text, Texte,

object-1 : child4, Node, null
object-2 : #text, Texte, Contenu4
object-1 : #text, Texte,

Jusque là tout va bien, mais comment nourrir un script avec un XML dynamique, i.e. que l'on ne peut pas hardcoder et que l'on peut même recharger plusieurs fois au cours du déroulement de l'application ? C'est très simple : on intègre une iframe dans la page web, on règle l'attribut src de cette iframe à une URL correspondant au fichier et en utilisant l'évènement onLoad de l'iframe on peut recopier peu ou prou les trois lignes qui crée le document XML et qui charge les données. Seul problème, si on essaye, Konqueror va transformer le XML en document HTML pour le rendre (c'est une IFrame après tout) alors plutôt que d'essayer tout de suite de faire des XSL pour pallier à ça, on construit une petite passerelle PHP qui va servir un fichier en transformant les < en &lt; et les > en &gt;. J'ai presque honte de faire ça mais ça fonctionne très bien, vous allez voir. Voici déjà la passerelle PHP qui sert les fichiers XML transformés :

<?
    if (!isset($_GET["file"]))      die("Pas de paramètre qui indique un fichier à servir");
    $FilePath = $_GET["file"];
    while (true)        {
        if (strpos("/", \$FilePath) !== false)      die("Interdit");
        if (file_exists($FilePath))     break;
        $Temp = rawurldecode($FilePath);
        if ($Temp == $FilePath)     die("Fichier introuvable");
    }
    $Contents = file_get_contents($FilePath);
    echo str_replace("<", "&lt;", str_replace(">", "&gt;", $Contents));
?"."></code>
            <p>Ainsi, en utilisant cette passerelle et en interceptant l'évènement onLoad de l'iframe avec la fonction JS suivante on obtient le même résultat que précédemment mais sans avoir hardcodé le XML :</p>
            <code>function ReceiveDatas()
{
    var Input = document.getElementById("inputXML")
    if (!Input.src.length)      return
    var XMLContent = Input.contentDocument.body.innerHTML
    XMLContent = XMLContent.replace(/&amp;lt;/g, "&lt;").replace(/&amp;gt;/g, "&gt;")
    //
    var XMLDoc = document.implementation.createDocument();
    XMLDoc.loadXML(XMLContent)
    var Root = XMLDoc.documentElement
    alert(getNodeHierarchy(Root, 0))
}?>

Et ça y est, Konqueror peut charger et manipuler du XML comme un grand. Maintenant qu'on a trouvé une méthode pour charger un XML "à la main", pour les navigateurs équipés passons à...

L'utilisation des xmlHttpRequest

Proximité des villes françaises


17 décembre 2006

SIG - Les villes de France et leurs proximité

J'ai pour but d'analyser les 38052 communes de France par rapport à une ville "pivot", et parmi celles qui comptent plus de 5000 habitants, d'en déduire une liste triée des villes les plus proches. Bien évidemment les données sont stockées dans une base de données (MySQL pour ne pas la nommer), que j'ai reprise depuis ce site très intéressant et la volonté ici est d'être le plus performant possible, d'où mon choix du C++.

Tout d'abord il me faut présenter la structure de la base de données, dans un pseudo-langage de description qui m'est propre mais simple à comprendre :

<component name='erpCity' basedOn="qbase" language="FR" fullName="Villes" Gender="female" tableName='erpCities'>
    <fields>
        <shorttext name='inseeCode' formHeader='Code INSEE'/>
        <shorttext name='name' formHeader='Nom de la ville'/>
        <float name='latitude' formHeader='Latitude (en radians)'/>
        <float name='longitude' formHeader='Longitude (en radians)'/>
        <shorttext name='zipCode' formHeader='Code postal'/>
        <integer name='pop' formHeader='Population'/>
        <float name='density' formHeader='Densité de population'/>
    </fields>
</component>

L'insertion de ces données dans une base MySQL se fait de façon directe en PHP :

<html>
<head>
    <title>Création de la base de données des villes</title>
    <meta http-equiv='Content-Type' value='test/html; charset=utf-8'/>
</head>
<body>
<?

include_once "./dir.php";
include_once $gIncludeDir."/__helpers.php";
include_once "component.php";

    $Datas = file_get_contents("villes.csv");
    $FieldNames = array("Insee"=>"inseeCode", "Nom"=>"name",
                        "LatitudeRadian"=>"latitude", "LongitudeRadian"=>"longitude",
                        "CodePostal"=>"zipCode", "NombreHabitants"=>"pop", "Densite"=>"density");
    $Lines = explode("\n", $Datas);
    $NbLines = count($Lines);
    echo "Nombre de communes : $NbLines<br/>\n";
    $Comp = new Component("city");
    $Comp->CreateTable();       //On réinitialise la table à chaque fois
    $GlobalRet = true;
    for($i=0; $i<$NbLines; $i++)        {
        if (!$i)        continue;       //On ignore la première ligne
        $Values = explode(";", $Lines[$i]);
        //
        $Query = "insert into qt_erpCities (id, inseeCode, name, latitude, longitude, zipCode, pop, density) values (0, \"$Values[0]\", \"$Values[1]\", $Values[2], $Values[3], \"$Values[4]\", $Values[5], $Values[6]);";
        if (!($Ret = DB_ExecQuery($Query)))     echo "Problème d'insertion : <blockquote>".mysql_error()."</blockquote>\n";
        $GlobalRet &= $Ret;
    }
    if (!$GlobalRet)        echo "<p>Il y a eu au moins un problème d'insertion.</p>";

?>

</body>
</html>

L'exécutable qui tournera sur le serveur utilise un tri Radix et un wrapper MySQL pour simplifier l'écriture :

class   CityRanks : public Application
{
    struct  City
    {
        float   latitude, longitude;
        float   distance;
    };
    public:
        CityRanks()     {       }
        virtual     int run()
        {
            int     i;
            //On parse les paramètres de la ligne de commandes
            int     NbParamsToParse = _argc-1;
            if (NbParamsToParse < 2)        return EXIT_FAILURE;
            if (NbParamsToParse > 2)        NbParamsToParse = 2;
            int*    Params = new int[2];
            for(i=0; i<NbParamsToParse; i++)        sscanf(_argv[i+1], "%d", &Params[i]);
            //Paramètres de la ligne de commande :
            int Pivot = Params[0];
            int NbResults = Params[1];
            //
            MySQL*  DB = new MySQL();
            City**  Cities = new City*[1700];
            char**  CityNames = new char*[1700];
            memset(CityNames, 0, sizeof(char*)*1700);
            int     NbCities = 0;
            //
            DB->serverInit();
            if (DB->init() && DB->connect("Nom de la base de données", "Nom de l'utilisateur", "Mot de passe de l'utilisateur"))        {
                if (DB->query("select name,latitude,longitude from qt_erpCities where pop>=5000 group by inseeCode order by pop desc;"))        {
                    City*   CurCity = NULL;
                    MYSQL_ROW   Row;
                    while (DB->fetchRow(Row))       {
                        CurCity = Cities[NbCities] = new City;
                        string_affectCopy(&CityNames[NbCities], Row[0]);
                        sscanf(Row[1], "%f", &CurCity->latitude);
                        sscanf(Row[2], "%f", &CurCity->longitude);
                        NbCities++;
                    }
                    DB->freeResult();
                }   else    return EXIT_FAILURE;
            }   else    return EXIT_FAILURE;
            //Je me suis inspiré de http://en.wikipedia.org/wiki/Great-circle_distance
            float   EarthRadius = 6372.795f;
            float   PivotLongitude = Cities[Pivot]->longitude;
            for(i=0; i<NbCities; i++)       {
                //On calcule la distance entre la ville <i> et la ville témoin/pivot
                float   Lat1 = Cities[i]->latitude;
                float   Lat2 = Cities[Pivot]->latitude;
                float   dLat = Lat1 - Lat2;
                float   dLong = Cities[i]->longitude - PivotLongitude;
                float   SinLat = sin(dLat/2);
                float   SinLong = sin(dLong/2);
                float   dSigma = 2.0f * asin(sqrt(SinLat*SinLat+cos(Lat1)*cos(Lat2)*SinLong*SinLong));
                Cities[i]->distance = EarthRadius * dSigma;
            }
            //On trie les résultats en fonction de la distance
            //On prépare le tableau à trier
            int*    Distances = new int[NbCities*2];
            for(i=0; i<NbCities; i++)       {
                Distances[2*i] = (int) Cities[i]->distance;
                Distances[2*i+1] = i;       //La clé
            }
            int*    Result = new int[NbCities*2];
            Radix_sort(Distances, NbCities, 2, Result);
            printf("Distance par rapport à %s :\n", CityNames[Pivot]);
            for(i=1; i<NbResults+1; i++)        printf("%s : %d km\n", CityNames[Result[2*i+1]], Result[2*i]);
            Radix_release();
            ReleaseArray(Distances);
            ReleaseArray(Result);
            //
            Release(DB);
            for(i=0; i<NbCities; i++)       Release(Cities[i]);
            ReleaseArray(Cities);
            for(i=0; i<NbCities; i++)       string_release(&CityNames[i]);
            ReleaseArray(CityNames);
            //
            ReleaseArray(Params);
            //
            return EXIT_SUCCESS;
        }

};

FFW_MAINENTRY(CityRanks);

Le temp moyen d'exécution sur un Sempron 2200+, avec 768Mo de RAM, est de l'ordre de 120ms, sachant que plus de 99% (si, si) de ce temps est consacré à l'accès à la DB. A noter que je voulais avoir un exécutable le plus petit possible (on pourra certainement faire mieux que moi, mais je suis déjà très content !). Avec la commande ci-dessous, ce programme prend 7860 octets :

strip --strip-unneeded -R .comment -R .gnu.version cityquery

Pour exécuter la requête depuis un navigateur, qui est le but de cette entrée, le script PHP suivant se chargera du travail (qui n'est pas bien lourd) :

<html>
<head>
    <title>Requête de distance géographiques</title>
    <meta http-equiv='Content-Type' value='test/html; charset=utf-8'/>
</head>
<body>
<?
    $Pivot = isset($_GET["pivot"]) ? $_GET["pivot"] : "";
    $NbResults = isset($_GET["nbResults"]) ? $_GET["nbResults"] : 30;
    if (!strlen($Pivot) || !$NbResults)     die("");
    if (!is_numeric($Pivot)  || !is_numeric($NbResults))        die("");
    if ($Pivot > 38052 || $Pivot <= 0 || $NbResults > 100 || $NbResults <=0)        die("");
    $Output = shell_exec("/usr/local/bin/cityquery $Pivot $NbResults");
    echo str_replace("\n", "<br/>\n", $Output);
?>
</body>
</html>

On pourra consulter le résultat de tout ceci sur les images ci-dessous, en attendant que je donne accès à ce script directement depuis ce site (ça ne serait peut-être pas très prudent) :

Des scripts utiles en Bash


10 octobre 2006

Zone de détente pour stocker tous les scripts qu'il m'est arrivé de faire pour le plaisir (si, si, c'est vrai), pour le travail ou pour répondre à un besoin bien précis. La plupart ne doivent pas être optimisés et doivent bien exposer mes manques de connaissance globaux de Bash/Linux, mais, comme on dit en Californie du moment que ça marche.

Télécharger une collection de Mod/XM/S3M

Si, comme moi, vous aimez les modules Amiga de la belle époque, ce petit script va vous plaire : il rapatrie l'ensemble de la collection du site Amiga Module Preservation et ceci sans s'embêter. �a me permet d'illustrer l'intérêt de la conjonction de sed et awk (ou la version de GNU, gawk) pour écrire des scripts très courts qui peuvent faciliter la vie (et entre autre les tâches d'admin).

Pour l'instant je remplis un fichier (modlist) avec les URLs de tous les modules, il reste quelques lignes à écrire pour les télécharger effectivement.

#!/bin/bash

#Ou comment choper tous les modules Amiga qu'on veut en un seul coup
#TODO : décompresser les gz on the fly
#TODO : fournir simplement la liste des fichiers

RootURL=\"http://amp.dascene.net/modules/\"
wget -O amp.download  \$RootURL

#On déduit les dossiers à choper
for folder in `cat amp.download | sed -n 's/^.*<a href=\"\([^\"]*\)\">\1.*$/\1/p'`; do
    :>artistslist
    wget -O amp.download.tmp $RootURL/$folder
    cat amp.download.tmp | sed -n 's/^.*<a href=\"\([^\"]*\)\">\\1.*\$/\1/p' | awk '{ print "'$folder'\"$0 }' >> artistslist
    for artist in `cat artistslist`; do
        :>modlist
        wget -O artist.page $RootURL$artist
        cat artist.page | sed -n 's/^.*<a href=\"\([^\"\/?]\+\)\">.*$/\1/p' | awk '{ print "'$RootURL$artist'"$0 }' >> modlist
        for module in `cat modlist`; do
            Path=${module%/*}
            Artist=${Path##*/}
            ModName=${module##*/}
            mkdir -p ${Artist:0:1}$Artist
            wget -P ${Artist:0:1}$Artist/ $module
        done
    done
done

rm -f amp.download artist.page modlist amp.download.tmp
Le résultat de ce script est un fichier modlist de 4 454 104 octets, la taille totale des mods téléchargés arrive plus tard... Il faudrait faire la même chose pour ftp://ftp.modland.com/pub/, leur serveur a l'air bien foireux et c'est très pénible ne serait-ce que de le visiter. La même chose pour http://it.aminet.net/~aminet/dirs/tree_mods.html. Et bien sûr ensuite faire un script qui supprime les doublons des trois site :)

Nettoyage des noms des modules

Une fois les modules précédents téléchargés, on se rend compte que les habitudes de nommage de fichiers ne correspondent pas à ceux en vigueur actuellement, on peut facilement les renommer avec le script suivant :

Dir="Jester"; for item in $Dir/MOD*; do NewName=$(echo `basename ${item}` | awk 'BEGIN{FS="."} {print $2"."tolower($1)}'); mv "$item" "$Dir/$NewName"; done
Le dossier et l'extension sont en dur, mais c'est un exemple

Commandes Bash intéressantes

Ne riez pas mais ça faisait un bout de temps que je voulais avoir la possibilité de trier des fichiers par taille (ou tout autre critère) mais sans avoir jamais vraiment cherché. C'est fait, et c'est très facile.

//Trier la sortie de ls -l (aka ll) en fonction la taille des fichiers (descendant).
ls -l | sort -nrk 5,5
//Pour les trier dans l'ordre ascendant il faut virer le 'r' de sort.

//Extension : renvoie les 4 plus gros fichiers (il faut virer la 1ère ligne de ls -l qui est la taille totale).
ls -l | awk 'BEGIN{i=0}{if (i++) print $0}' | sort -rnk 5,5 | tail -n 4
//Pour les 4 plus petits, il faut virer le 'r' de sort.

//Mais on peut arranger le bloc awk par une commande sed (c'est très original...)
ls -l | sed -n '1!p' | sort -rnk 5,5 | tail -n 4
//C'est équivalent : on dit à sed de faire le contraire d'imprimer (flag p) la ligne 1.

//Si on veut trier par date, le tri par nombre (-n) ne va plus il faut trier les chaînes directement (-d), ce qui donne :
ls -l | sed -n '1!p' | sort -rdk 6,6 | tail -n 4
//Et on trie alors par date croissante (les plus récents en dernier)

Petite application : imaginons que l'on ait un ensemble de dossiers contenant chacun plusieurs fichiers (par exemple des PDF de docs amassés dans plusieurs domaines), comment avoir les fichiers triés du plus petit au plus gros ? Réponse :

for file in */*; do [[ -f $file ]] && echo `ls -l "$file"`; done | sort -nrk 5,5
Bien penser au guillemets qui encadre $file sinon un fichier ayant un nom avec des espaces va provoquer un bordel...

La version deux ci-dessous de ce oneliner peut paraître plus compliquée mais une fois sauvée dans un script et placée dans le PATH elle convient à tout ce dont j'ai besoin. Elle permet d'utiliser l'option -0 de la commande ls qui gère tous les types de noms de fichiers.

find . -name "*pdf" -print0 | xargs -0 ls -l | awk '{ O=""; for (i=8; i<=NF; i++) O=O$i; print $5" "O;}' | sort -rnk1,1

Si on appelle le script précédent findex on peut ainsi de chercher tous les fichiers PDF et de les ordonner à partir du répertoire courant et dans tous ses sous-répertoires en une seule ligne intuitive : findex . pdf.

Tagage, classement et indexation automatiques de données textuelles

Je cherche à calculer une empreinte d'un texte à partir des mots qui le constitue.

cat <mon_fichier> | sed -e "s/''/\n/g" | sed -e 's/ /\n/g' -e 's/[=:;,\{\}"\$<>&|#\*\(\)\\\/\-\?\+\.]/ /g' | sed -e 's/ /\n/g' | sort | uniq -c | sort -n

En ignorant la casse, en filtrant les mots inutiles (les plus courants etc), en les triant par taille et en n'en gardant que 10, on commence à pouvoir tagguer automatiquement un bloc de texte.

cat <mon_fichier> | sed -e \"s/\'\`/\\n/g\" | sed -e 's/ /\\n/g' -e 's/[=:;,\{\}\"\\$<>&|#\*\(\)\\\/\-\?\+\.]/ /g' | sed -e 's/ /\\n/g' | tr 'A-Z' 'a-z' | sort | uniq -c | sort -n | awk -f filter.awk | sort -nk 3,3 | tail | awk '{print $2}'

Voilà le résultat avec un bout de descriptif de Awk :

consecutive
declaration
differences
fundamental
programming
additionally
conceptually
corresponding
superficially
one-dimensional
Ca peut sembler peu intéressant mais si on affecte manuellement une série de tags à quelques éléments on peut avoir un tagging automatique pour les documents ultérieurs. Il faut pouvoir laisser la possibilité de changer ces affectations au besoin Page spéciale d'extraction de torrents (obligatory pr0n page)

LDD extended

Je ne sais pas vraiment si ce script est utile : peut-être ldd fait-il déjà tout ce travail, mais je n'en ai pas l'impression. Toujours est-il que je voulais obtenir le résultat de ldd pour un exécutable et ensuite pour chacune des librairies renvoyées. D'où ce script qui m'a bien énervé, surtout pour le passage de tableau à la fonction contains...

#!/bin/bash

function    displayHelp
{
    echo "Extension de récursive de <ldd> :"
    echo "affiche toutes les librairies en dépendance d'une autre ou d'un exécutable"
    echo "et affiche les dépendances des dépendances ad finitum."
    echo
    echo "Syntaxe :"
    echo
    echo "    $0 <exelibs>+|--help|--version"
    echo
    echo "Options :"
    echo
    echo "<exelibs>+ : suite d'exécutables ou de librairies dont on veut les dépendances."
    echo
    echo "--help : cette page d'aide."
}

#Vérifie que ${1} n'est pas contenu dans le tableau dont le nom est passé en second paramètre
function    contains
{
    Value="${1}"
    ArrName=${2}
    Arr=\${"$ArrName"[@]}; Arr=`eval echo $Arr`
    Temp=( ${Arr[@]/$Value/XXX$Value} )
    Verif=( ${Arr[@]} )
    Str=${Temp[@]};     Len1=${#Str}
    Str=${Verif[@]};    Len2=${#Str}
    [[ $Len1 != $Len2 ]] && return 0;
    return 1
}

NbParams=${#*}
[[ $NbParams == 0 ]] && echo "Pas de paramètres : il faut le chemin d'une librairie ou d'un exécutable." && echo && displayHelp && exit 1;

while [[ true ]];   do
    [[ ${#*} == 0 ]] && break;
    CurBinary=${1}
    shift
    [[ $CurBinary == "--help" ]] && displayHelp && continue;
    echo "Examen de $CurBinary :"
    [[ ! -f $CurBinary ]] && echo "Le fichier n'existe pas !" && continue;
    unset Todo; unset Handled
    Todo[0]=$CurBinary
    PassIndex=0
    while [[ ${#Todo[@]} != 0 ]];   do
    #   echo "Passe $PassIndex : il reste "${#Todo[@]}" élément(s) à examiner"
        for lib in ${Todo[@]}; do
            List=( `ldd $lib | awk '{if ($1!~"linux-gate" && $1!~"statically") { if (!length($3)) print $1"\n"$1; else print $1"\n"$3}}'` )
            let "nbLibs = -1 + ${#List[@]} / 2"
            for i in `seq 0 $nbLibs`; do
                LibName=${List[((i*2))]}
                LibPath=${List[((2*i+1))]}
                contains $LibPath Handled;  Ret1=$?
                contains $LibPath Todo;     Ret2=$?
                if [[ $Ret1 -eq 1 ]];   then
                    if [[ $Ret2 -eq 1 ]];   then
                        Todo[${#Todo[@]}]=$LibPath          #On l'ajoute au tableau todo
                    fi
                fi
            done
            newArray=( ${Todo[@]%$lib} ); unset Todo; Todo=( ${newArray[@]} )
            contains $lib Handled;  Ret=$?
            if [[ $Ret -eq 1 ]];    then    Handled[${#Handled[@]}]=$lib;   fi
        done
        ((PassIndex++))
    done

    let "MaxItemsIndex = ${#Handled[@]} -1"
    for i in `seq 1 $MaxItemsIndex`; do echo ${Handled[$i]}; done
done

Copie de CDs

Un petit script sans aucune prétention mais que j'apprécie beaucoup pour m'aider à copier mes cds sur un disque dur sans avoir à utiliser de souris ou de commandes répétitives. Il crée un dossier par CD, place tout à l'intérieur, modifie les droits d'écriture et sort le CD du lecteur en me disant si des erreurs se sont produites. Je l'appelle généralement depuis l'endroit ou je veux stocker les données avec le commande : grabcd /media/cdrom

#!/bin/bash

[[ ${#} -ne 1 ]] && echo "J'attends la partition source..." && exit 1;
Src="${1}"

ErrorLog="./errors.log"
rm -f $ErrorLog

mount $Src 2> /dev/null

let NbFolders=`ls -ld cd* 2> /dev/null | wc -l`+1
Dest=cd$NbFolders
mkdir $Dest

cp -vr $Src/* $Dest/ 2>$ErrorLog
chmod -R u+w $Dest/

[[ -s $ErrorLog ]] && echo "Des erreurs de lecture se sont produites :" && cat $ErrorLog;

  eject

Taux de changes

Pour obtenir le taux de conversion du dollar face à l'euro (en gros combien il faut de dollars pour avoir un euro) il suffit d'utiliser ce script :

wget -q -O- www.federalreserve.gov/releases/h10/Update/ | awk '{if ($2=="MEMBERS" && $3=="EURO") print $NF}'

J'aime beaucoup l'option de wget -O- pour envoyer la ressource HTTP téléchargée sur stdout, très pratique !

(Dé)Compression de plusieurs dossiers (archives)

Parcours de tous les dossiers du répertoire courant et crée une archive BZ2 pour chacun en indiquant la progression totale (attention aux dossiers contenant des espaces...).

fList=`find -maxdepth 1 -type d | tail -n+2`; nb=0; for item in $fList; do echo -n "Dossier : $item..."; tar cjf "$item.tar.bz2" "$item"/*; ((nb++)); echo $(echo "scale=1; 100*$nb/`echo "$fList" | wc -l`" | bc)" %"; done

Ajout automatique de domaines à un serveur DNS

Voici un script autosuffisant (pas besoin de ressource externe) pour ajouter ou modifier un domaine auprès de Bind. �a évite de s'embêter à éditer et/ou copier-coller des fichiers, ça fait tout tout seul.
Bien sûr rien de compliqué mais au moins c'est disponible.

#!/bin/bash

function    displayHelp
{
    echo "Ajoute un domaine à Bind et en option relance ce serveur."
    echo && echo "Syntaxe :"
    echo "  $0 [--add <domain>] [--label <label>] [--restart] [--help]"
    echo && echo "Options :"
    echo && echo "  --add <domain> : ajoute le domaine <domain> à la liste des zones gérées par Bind."
    echo && echo "  --label <txt> : ajoute le champ texte <txt> à la zone DNS (via une ligne TXT)."
    echo && echo "  --restart : redémarre le serveur de nom pour qu'il prenne en compte toutes ses zones."
    echo && echo "  --help : affiche cette aide (équivalent à appeler ce programme sans aucun paramètre)."
    echo && echo "###TODO : reste à modifier le fichier principal de configuration de Bind."
    echo && echo "(c) Qitools 2007."
}

function    createNamedFile
{
    domainName=$1
    domainHost=$2
    hostIP=$3
    label=$4
    #Création d'un fichier temporaire contenant la template de la zone
    TempFileName=mktemp
    echo "; The zone file for the %domainName% domain
\$TTL 3D
%domainName%.    IN      SOA     ns.%domainName%. postmaster.%domainName%. (
                        %date%
                        2M
                        1M
                        1W
                        1H )
                TXT     \"%label%\"
                IN      NS      ns.%domainName%.
                IN      NS      ns.ovh.net.
                MX      10      mail.%domainName%.

localhost           IN      A       127.0.0.1
ns                  IN      A       %domainIP%
mail                IN      A       %domainIP%
%domainName%.       IN      A       %domainIP%
www                 IN      CNAME   %domainHost%.
" > $TempFileName
    #Transformation de la template
    cat $TempFileName | sed "s/%domainName%/$domainName/g" | sed "s/%domainIP%/$hostIP/g" | sed "s/%domainHost%/$domainHost/g" > $TempFileName.1
    if [[ ${#label} != 0 ]]; then
        cat $TempFileName.1 | sed "s/%label%/$label/g" > $TempFileName.2
    else
        cat $TempFileName.1 | sed "/%label%/d" > $TempFileName.2
    fi
    #Gestion épineuse de la date...
    ZoneFile=/var/named/zones/$domainName
    ZoneDir=`dirname $ZoneFile`
    mkdir -p $ZoneDir
    if [[ -f $ZoneFile ]]; then
        OldDate=`cat $ZoneFile | grep "$domainName.*IN.*SOA" --after-context=1 | tail -n +2 | awk '{print $1}'`; OldRev=${OldDate:8}
        #On nettoie la chaîne des 0 en préfixe sinon le let suivant ne fonctionnera pas
        OldRev=`echo $OldRev | sed "s/^0*//g"`
        let OldRev++
        OldRev=`printf "%02d\n" $OldRev`
        ZoneDate="`date +%Y%m%d`$OldRev"
    else
        ZoneDate="`date +%Y%m%d`01"
    fi
    #on reprend la date existante et on incrémente la révision
    cat $TempFileName.2 | sed "s/%date%/$ZoneDate/g" > $ZoneFile
    #Arrivé ici on peut ajouter quelque chose au fichier /etc/named.conf si ça n'est pas déjà fait...
    MainBindConf="/etc/named.conf"
    #Soit on ajoute un bloc de zone soit on réécrit le bloc correct (on ne sait jamais)
}

RestartNamed=false
[[ ${#} == 0 ]] && displayHelp && exit 0;
while [[ ${#} -gt 0 ]]; do
    Param=$1
    case $Param in
        "--help")   displayHelp && exit 0;;
        "--add")    shift; Domain=$1;;
        "--label")  shift; ZoneLabel=$1;;
        "--restart")    RestartNamed=true;;
        ".*")       echo "Option <$Param> inconnue !";;
    esac
    shift
done

#On nettoie le nom de domaine...
Domain=`echo $Domain | tr [A-Z] [a-z] | sed "s/ //g" | sed "s/\///g"`
#On vérifie qu'on est bien root
[[ `id -u` != 0 ]] && echo "Ce script nécessite d'être en root pour fonctionner." && exit 3;
#Vérifier que named tourne bien ou du moins qu'il existe...
NamedRunning=`ps -Af | grep [n]amed | head -n 1 | awk '{print $1}'`
if [[ $NamedRunning != "named" ]]; then
    #Le serveur Bind ne tourne pas, on va vérifier qu'il existe quand même sur la machine
    NamedPath=`which named`
    [[ $? == 1 ]] && echo "Le serveur de nom Bind n'existe pas, le script ne sert à rien sur cette machine." && exit 4;
fi

[[ $RestartNamed -eq "false" && ${#Domain} -eq 0 && ${#MainDNSServer} -eq 0 ]] && echo "Aucune option sélectionnée." && exit 2;

CurrentHost=`hostname`
CurrentHostIP=`host $CurrentHost | sed '/alias/d' | sed '/NXDOMAIN/d' | head -n 1 | awk '{print $4}'`
[[ ${#CurrentHostIP} -eq 0 ]] && echo "Aucune adresse IP trouvée pour le domaine <$CurrentHost>. Vérifiez votre entrée et recommencez." && exit 1;

createNamedFile $Domain $CurrentHost $CurrentHostIP "$ZoneLabel"

if [[ $RestartNamed == "true" ]]; then
    /etc/init.d/named stop 1>/dev/null
    /etc/init.d/named start 1>/dev/null
fi

Récupérer ses mots de passe Windows

Une petite note pour me souvenir de la façon dont on récupère les mots de passe d'une install de Windows sur le même PC qu'un Linux.
Ici j'utilise (K)Ubuntu, mais la logique est suffisamment simpliste pour être adapté sur n'importe quelle distribution.
En tous cas ça m'a permis de récupérer facilement mon mot de passe root - pardon Administrator - que j'avais oublié, n'ayant pas démarré Windows depuis un bon bout de temps...

#Installe les softs qui peuvent manquer
apt-get install bkhive samdump2 john
#On récupère la SYSKEY
bkhive /media/hda1/WINNT/system32/config/system syskey
#On utilise la SYSKEY pour décrypter les hash des mots de passe
samdump2 /media/hda1/WINNT/system32/config/SAM syskey > hashes
#On cherche les mots de passe dont on connaît les hash
john hashes

J'ai extrait ces infos de cette page intéressante. D'autres informations peuvent être obtenues depuis la page de John The Ripper, logiciel utilisé pour réellement cracker les mots de passe.

ImageMagick sympathique

Je devais faire des manipulations sur des images et forcément le réflexe est d'utiliser ImageMagick ! Le problème était que j'avais une petite centaine de feuilles papier dont les numéros en bas de page étaient incorrects et qui devait produire un PDF regroupant deux pages d'origine par page. J'ai créé deux dossiers, un dossier jpg/ pour les fichiers nettoyés et un autre doubles/ pour les images finales. Le script est :

i=1; j=0; k=0; for item in *png; do echo $item; convert "$item" -fill white -draw 'rectangle 484,3256,2184,3516' -fill black -font "Times-Bold" -pointsize 60 -draw "text 1300,3400 {$i}" -quality 80 "jpg/page_"$j"_"$k".jpg"; ((i++)); ((k++)); [[ $k == 2 ]] && k=0 && ((j++)); done

for i in `find product/*jpg | awk 'match($0,"page_([0-9]+)",a) {print a[1]}' | sort -nk1,1 | uniq`; do PicIndex=`echo "0"$i | awk '{print substr($0, length($0)-1, 2)}'`; echo $i; montage "jpg/page_"$i"_0.jpg" "jpg/page_"$i"_1.jpg" -geometry +2+2 "doubles/page_"$PicIndex".jpg"; done

convert -monitor doubles/*jpg -monitor -compress jpeg dest.pdf

La première ligne efface le numéro de page existant et le remplace par quelque chose de standardisé et en profite pour nommer les fichiers produits de manière à préparer l'étape suivante. Etape qui justement, sur la deuxième ligne, crée des images contenant des groupes de deux images combinées. La dernière ligne générant le fichier PDF voulu.

Commandes Ubuntu pour la gestion des paquets

Quelques commandes pour se faciliter la vie sous Debian/Ubuntu :

Repair:dpkg --configure -a
Reconfigure:dpkg-reconfigure pkg (-a)
Fix:apt-get -f install (pkg)
Nuke:dpkg --force-remove-reinstreq -r pkg
Corrupt deb:rm /var/cache/apt/archives/pkg
Hack dpkg:rm /var/lib/dpkg/info/pkg*

Mon fichier sources.list

# 
deb cdrom:[Ubuntu-Server 6.10 _Edgy Eft_ - Release i386 (20061025.1)]/ edgy main restricted

deb http://fr.archive.ubuntu.com/ubuntu/ edgy main restricted
deb-src http://fr.archive.ubuntu.com/ubuntu/ edgy main restricted

## Major bug fix updates produced after the final release of the
## distribution.
deb http://fr.archive.ubuntu.com/ubuntu/ edgy-updates main restricted
deb-src http://fr.archive.ubuntu.com/ubuntu/ edgy-updates main restricted

deb http://fr.archive.ubuntu.com/ubuntu edgy-security main restricted
deb http://fr.archive.ubuntu.com/ubuntu edgy-proposed main restricted
deb-src http://fr.archive.ubuntu.com/ubuntu edgy-security main restricted
deb-src http://fr.archive.ubuntu.com/ubuntu edgy-proposed main restricted

deb http://fr.archive.ubuntu.com/ubuntu edgy universe multiverse
deb http://fr.archive.ubuntu.com/ubuntu edgy-updates universe multiverse
deb http://fr.archive.ubuntu.com/ubuntu edgy-security universe multiverse
deb http://fr.archive.ubuntu.com/ubuntu edgy-proposed universe multiverse
deb-src http://fr.archive.ubuntu.com/ubuntu edgy universe multiverse
deb-src http://fr.archive.ubuntu.com/ubuntu edgy-updates universe multiverse
deb-src http://fr.archive.ubuntu.com/ubuntu edgy-security universe multiverse
deb-src http://fr.archive.ubuntu.com/ubuntu edgy-proposed universe multiverse

deb http://archive.canonical.com/ubuntu edgy-commercial main

deb http://kubuntu.org/packages/kde-latest edgy main
deb-src http://kubuntu.org/packages/kde-latest edgy main

deb http://kubuntu.org/packages/koffice-latest edgy main
deb-src http://kubuntu.org/packages/koffice-latest edgy main

deb http://kubuntu.org/packages/amarok-latest edgy main
deb-src http://kubuntu.org/packages/amarok-latest edgy main

deb http://medibuntu.sos-sts.com/repo/ edgy free non-free
deb-src http://medibuntu.sos-sts.com/repo/ edgy free non-free

deb http://ubuntu.beryl-project.org edgy all
deb-src http://ubuntu.beryl-project.org edgy all
deb http://gandalfn.club.fr/ubuntu/ edgy stable
deb-src http://gandalfn.club.fr/ubuntu/ edgy stable

deb http://ubuntu.beryl-project.org/ edgy main

deb http://www.albertomilone.com/drivers/edgy/latest/32bit binary/

## Uncomment the following two lines to add software from the 'universe'
## repository.
## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
## team, and may not be under a free licence. Please satisfy yourself as to
## your rights to use the software. Also, please note that software in
## universe WILL NOT receive any review or updates from the Ubuntu security
## team.
# deb http://fr.archive.ubuntu.com/ubuntu/ edgy universe
# deb-src http://fr.archive.ubuntu.com/ubuntu/ edgy universe

## Uncomment the following two lines to add software from the 'backports'
## repository.
## N.B. software from this repository may not have been tested as
## extensively as that contained in the main release, although it includes
## newer versions of some applications which may provide useful features.
## Also, please note that software in backports WILL NOT receive any review
## or updates from the Ubuntu security team.
# deb http://fr.archive.ubuntu.com/ubuntu/ edgy-backports main restricted universe multiverse
# deb-src http://fr.archive.ubuntu.com/ubuntu/ edgy-backports main restricted universe multiverse


deb http://security.ubuntu.com/ubuntu edgy-security main restricted
deb-src http://security.ubuntu.com/ubuntu edgy-security main restricted
# deb http://security.ubuntu.com/ubuntu edgy-security universe
# deb-src http://security.ubuntu.com/ubuntu edgy-security universe
Accueil1 2 3