1718 lines
		
	
	
		
			57 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
			
		
		
	
	
			1718 lines
		
	
	
		
			57 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
 | 
						|
## fonctions pour awk
 | 
						|
##@cooked nocomments
 | 
						|
##@require base
 | 
						|
uprovide awk
 | 
						|
urequire base
 | 
						|
 | 
						|
__AWKDEF_HELP="\
 | 
						|
Les variables données en arguments sont définies dans une section BEGIN{}. Si
 | 
						|
une valeur ne ressemble pas à une définition de variable, l'analyse des
 | 
						|
variables s'arrête et le reste des arguments est inséré tel quel.
 | 
						|
 | 
						|
Normalement, les variables définies sont scalaires, avec une syntaxe de la forme
 | 
						|
name=value pour une chaine, ou name:int=value pour une valeur entière.
 | 
						|
Dans la forme name=value, si la valeur ne contient que des chiffres, alors elle
 | 
						|
est considérée comme entière.
 | 
						|
 | 
						|
Il est aussi possible d'utiliser la syntaxe awk_array[@]=bash_array pour
 | 
						|
initialiser le tableau awk_array, qui contiendra toute les valeurs du tableau
 | 
						|
nommé bash_array, avec les indices de 1 à N, N étant le nombre d'éléments du
 | 
						|
tableau bash_array. La variable awk_array_count est aussi initialisée, et
 | 
						|
contient le nombre d'éléments du tableau.
 | 
						|
La syntaxe simplifiée array[@] est équivalente à array[@]=array
 | 
						|
Il existe une autre syntaxe 'awk_array[@]=<' qui permet de spécifier les valeurs
 | 
						|
du tableau, une par ligne, e.g:
 | 
						|
    $'values[@]=<\nvalue1\nvalue2'
 | 
						|
pour un tableau values qui contiendra deux valeurs: value1 et value2
 | 
						|
 | 
						|
Les fonctions suivantes sont définies:
 | 
						|
 | 
						|
num(s)
 | 
						|
    si s ne contient que des chiffres, retourner la valeur numérique associée,
 | 
						|
    sinon retournée la valeur inchangée
 | 
						|
 | 
						|
ord(s)
 | 
						|
    retourner le code ASCII du premier caractère de la chaine s. seuls les codes
 | 
						|
    de 32 à 127 sont supportés
 | 
						|
 | 
						|
hex(i)
 | 
						|
    retourner la représentation hexadécimale du nombre i
 | 
						|
 | 
						|
qhtml(s)
 | 
						|
    remplacer respectivement dans la chaine s les valeurs &, \", > et < par
 | 
						|
    &, ", > et <
 | 
						|
    L'alias quote_html(s) existe pour compatibilité
 | 
						|
 | 
						|
unquote_html(s)
 | 
						|
    faire le contraire de qhtml(s)
 | 
						|
 | 
						|
qval(s)
 | 
						|
    quoter une valeur pour le shell. la valeur est entourée de quotes, e.g:
 | 
						|
        qval(\"here, \\\"there\\\" and 'everywhere'.\")
 | 
						|
        --> 'here, \"there\" and '\\''everywhere'\\''.'
 | 
						|
    L'alias quote_value(s) existe pour compatibilité
 | 
						|
 | 
						|
qsval(s)
 | 
						|
    comme qval() mais ajouter un espace avant la valeur quotée. ceci permet de
 | 
						|
    construire facilement une ligne de commande, e.g.:
 | 
						|
        print \"mycmd\" qsval(arg1) qsval(arg2)
 | 
						|
 | 
						|
qvals()
 | 
						|
    quoter les valeurs \$1..\$NF pour les passer comme argument sur la ligne de
 | 
						|
    commande avec eval. e.g.:
 | 
						|
        print \"mycmd \" qvals()
 | 
						|
    La ligne qui est affichée pourra être évaluée avec eval dans le shell.
 | 
						|
    L'alias quoted_values(s) existe pour compatibilité
 | 
						|
 | 
						|
qsvals(s)
 | 
						|
    comme qvals() mais ajouter un espace avant la valeur quotée. ceci permet de
 | 
						|
    construire facilement une ligne de commande, e.g.:
 | 
						|
        print \"mycmd\" qsvals()
 | 
						|
 | 
						|
qsubrepl(s)
 | 
						|
    quoter une valeur pour l'argument r des fonctions sub() et gsub(). Les
 | 
						|
    caractères suivants sont mis en échappement: \\ &
 | 
						|
    L'alias quote_subrepl(s) existe pour compatibilité
 | 
						|
 | 
						|
qgrep(s)
 | 
						|
    quoter une valeur pour un pattern *simple* de grep. Les caractères suivants
 | 
						|
    sont mis en échappement: \\ . [ ^ \$ *
 | 
						|
    L'alias quote_grep(s) existe pour compatibilité
 | 
						|
 | 
						|
qegrep(s)
 | 
						|
    quoter une valeur pour un pattern *étendu* de grep. Les caractères suivants
 | 
						|
    sont mis en échappement: \\ . [ ^ \$ ? + * ( ) | {
 | 
						|
    L'alias quote_egrep(s) existe pour compatibilité
 | 
						|
 | 
						|
qsql(s)
 | 
						|
    quoter une valeur pour un script sql. la valeur est entourée de quotes, e.g:
 | 
						|
        quote_sql(\"hello'there\")
 | 
						|
        --> 'hello''there'
 | 
						|
    L'alias quote_sql(s) existe pour compatibilité
 | 
						|
 | 
						|
unquote_mysqlcsv(s)
 | 
						|
    Analyser une valeur exportée de MySQL avec mysqlcsv. Les transformations
 | 
						|
    suivantes sont effectuées:
 | 
						|
        \\n   --> <newline>
 | 
						|
        \\t   --> <tab>
 | 
						|
        \\0   --> <caractère NUL>
 | 
						|
        \\\\  --> \\
 | 
						|
 | 
						|
mkindices(values, indices)
 | 
						|
    créer le tableau indices qui contient les indices du tableau values, de 1 à N,
 | 
						|
    et retourner la valeur N. Il faudra utiliser les valeurs de cette manière:
 | 
						|
        count = mkindices(values, indices)
 | 
						|
        for (i = 1; i <= count; i++) {
 | 
						|
            value = values[indices[i]]
 | 
						|
            ...
 | 
						|
        }
 | 
						|
    cette fonction nécessite gnuawk
 | 
						|
 | 
						|
array_new(dest)
 | 
						|
    créer un nouveau tableau vide dest
 | 
						|
 | 
						|
array_newsize(dest, size)
 | 
						|
    créer un nouveau tableau de taille size, rempli de chaines vides
 | 
						|
 | 
						|
array_copy(dest, src)
 | 
						|
    faire une copie d'un tableau. Cette fonction nécessite gnuawk, puisqu'elle
 | 
						|
    utilise mkindices().
 | 
						|
 | 
						|
array_getlastindex(src)
 | 
						|
    Retourner l'index du dernier élément du tableau src
 | 
						|
 | 
						|
array_add(dest, value)
 | 
						|
    Ajouter un élément dans dest, avec l'index array_getlastindex(dest)+1
 | 
						|
 | 
						|
array_deli(dest, i)
 | 
						|
    Supprimer l'élément d'index i dans le tableau dest.
 | 
						|
    Les index des éléments après i sont corrigés en conséquence.
 | 
						|
    Cette fonction assume que les indices du tableau commencent à 1 et n'ont pas
 | 
						|
    de \"trous\". Si i==0, cette fonction est un NOP.
 | 
						|
 | 
						|
array_del(dest, value[, ignoreCase])
 | 
						|
    Supprimer *tous* les éléments du tableau dest dont la valeur est value. Les
 | 
						|
    indexes des valeurs sont trouvées avec key_index(), puis les valeurs sont
 | 
						|
    supprimées avec array_deli()
 | 
						|
    ignoreCase permet de spécifier que la recherche de la valeur se fait en
 | 
						|
    ignorant la casse.
 | 
						|
 | 
						|
array_extend(dest, src)
 | 
						|
    Ajouter les éléments de src dans dest, en commençant avec l'index
 | 
						|
    array_getlastindex(dest)+1
 | 
						|
 | 
						|
array_fill(dest)
 | 
						|
    remplir le tableau avec \$1..\$NF
 | 
						|
 | 
						|
array_getline(src)
 | 
						|
    avec le tableau array contenant N éléments, initialise \$1..\$N avec les valeurs
 | 
						|
    du tableau
 | 
						|
 | 
						|
array_appendline(src)
 | 
						|
    avec le tableau array contenant N éléments, initialise \$(NF+1)..\$(NF+N) avec
 | 
						|
    les valeurs du tableau
 | 
						|
 | 
						|
in_array(value, values[, ignoreCase])
 | 
						|
    tester si le tableau values contient la valeur value, en ne tenant
 | 
						|
    éventuellement pas compte de la casse.
 | 
						|
 | 
						|
key_index(value, values[, ignoreCase])
 | 
						|
    trouver l'index de value dans le tableau values, en ne tenant éventuellement
 | 
						|
    pas compte de la casse. Retourner 0 si la valeur n'a pas été trouvée
 | 
						|
 | 
						|
array2s(values, prefix, sep, suffix, noindices)
 | 
						|
    convertir un tableau en chaine pour affichage. attention! les valeurs sont
 | 
						|
    affichés dans un ordre arbitraire. noindices, s'il vaut 1, supprime
 | 
						|
    l'affichage des indices du tableau. prefix (qui vaut par défaut \"[\") est
 | 
						|
    ajouté avant la chaine, suffix (qui vaut par défaut \"]\") après, et sep (qui
 | 
						|
    vaut par défaut \",\") est utilisé pour séparer chaque valeur.
 | 
						|
 | 
						|
array2so(values, prefix, sep, suffix, noindices)
 | 
						|
    convertir un tableau en chaine pour affichage. Les valeurs sont affichés dans
 | 
						|
    l'ordre du tableau. Cette fonction nécessite gnuawk, puisqu'elle utilise
 | 
						|
    mkindices(). noindices, s'il vaut 1, supprime l'affichage des indices du
 | 
						|
    tableau. prefix est ajouté avant la chaine, suffix après, et sep (qui vaut par
 | 
						|
    défaut \",\") est utilisé pour séparer chaque valeur.
 | 
						|
 | 
						|
array_join(values, sep, prefix, suffix)
 | 
						|
    convertir un tableau en chaine pour affichage. Les valeurs sont affichés dans
 | 
						|
    l'ordre du tableau. Cette fonction nécessite gnuawk, puisqu'elle utilise
 | 
						|
    mkindices().
 | 
						|
    Il n'y a pas de valeur par défaut pour sep, prefix et suffix.
 | 
						|
 | 
						|
printto(s, output)
 | 
						|
    en fonction de la valeur de output, afficher la chaine s sur la destination
 | 
						|
    spécifiée.
 | 
						|
    output est de la forme...            faire....
 | 
						|
        \"\"                               print s
 | 
						|
        \"dest\"                           print s >\"dest\"
 | 
						|
        \">dest\"                          print s >\"dest\"
 | 
						|
        \">>dest\"                         print s >>\"dest\"
 | 
						|
    XXX les formes suivantes sont désactivées pour le moment:
 | 
						|
        \"|dest\"                          print s |\"dest\"
 | 
						|
        \"|&dest\"                         print s |&\"dest\"
 | 
						|
 | 
						|
find_line(input, field, value)
 | 
						|
    retourner la première ligne du fichier input dont le champ \$field vaut
 | 
						|
    value. Retourner une chaine vide si la ligne n'a pas été trouvée.
 | 
						|
 | 
						|
merge_line(input, field, key)
 | 
						|
    équivaut à:
 | 
						|
        \$0 = \$0 FS find_line(input, field, \$key)
 | 
						|
    La ligne courante n'est pas modifiée si la ligne correspondante dans input
 | 
						|
    n'est pas trouvée.
 | 
						|
 | 
						|
array_parsecsv2(fields, line, nbfields, colsep, qchar, echar)
 | 
						|
array_parsecsv(fields, line, nbfields[, colsep, qchar, echar])
 | 
						|
    analyser une ligne au format csv, et initialiser le tableau fields aux valeurs
 | 
						|
    des champs trouvés. Si nbfields est spécifié, c'est le nombre de champs
 | 
						|
    minimum que doit avoir le tableau résultat. Le tableau est complété avec des
 | 
						|
    chaines vides au besoin. Le tableau commence à l'index 1.
 | 
						|
    Consulter array_formatcsv2 pour la signification de colsep, qchar et echar.
 | 
						|
    array_parsecsv(fields, line, nbfields) est équivalent à
 | 
						|
        array_parsecsv2(fields, line, nbfields, ",", "\"", "")
 | 
						|
 | 
						|
parsecsv(line)
 | 
						|
    équivaut à:
 | 
						|
        array_parsecsv(fields, line)
 | 
						|
        array_getline(fields)
 | 
						|
 | 
						|
getlinecsv(file)
 | 
						|
    obtient une ligne par getline, l'analyse, puis initialise \$1..\$N avec les
 | 
						|
    valeurs trouvées. equivaut à:
 | 
						|
        getline
 | 
						|
        parsecsv(\$0)
 | 
						|
    Si file est spécifié, utiliser 'getline <file' au lieu de 'getline'
 | 
						|
 | 
						|
array_formatcsv2(fields, colsep, mvsep, qchar, echar)
 | 
						|
    construit une ligne au format csv avec les colonnes du tableau fields.
 | 
						|
    - colsep est le séparateur des colonnes, e.g \",\"
 | 
						|
    - mvsep est le séparateur des valeurs des colonnes (si une colonne contient
 | 
						|
      plusieurs valeurs, par exemple si elle transporte la valeur d'un attribut
 | 
						|
      multivalué). Cette information ne sert que pour décider s'il faut encadrer
 | 
						|
      la colonne avec qchar.
 | 
						|
    - qchar est le caractère à employer pour encadrer la valeur d'une colonne
 | 
						|
      si elle contient l'un des caractères colsep ou mvsep
 | 
						|
    - echar est le caractère à employer pour mettre en echappement le caractère
 | 
						|
      qchar dans la valeur d'une colonne. Si echar est vide, le caractère qchar
 | 
						|
      est doublé.
 | 
						|
 | 
						|
array_formatcsv(fields, output)
 | 
						|
    équivaut à
 | 
						|
        array_formatcsv2(fields, \",\", \";\", \"\\\"\", \"\")
 | 
						|
 | 
						|
array_printcsv(fields, output)
 | 
						|
    équivaut à
 | 
						|
        printto(array_formatcsv(fields), output)
 | 
						|
 | 
						|
get_formatcsv()
 | 
						|
    équivaut à
 | 
						|
        array_fill(fields)
 | 
						|
        return array_formatcsv(fields)
 | 
						|
 | 
						|
formatcsv()
 | 
						|
    équivaut à
 | 
						|
        \$0 = get_formatcsv()
 | 
						|
 | 
						|
printcsv(output)
 | 
						|
    équivaut à
 | 
						|
        array_fill(fields)
 | 
						|
        array_printcsv(fields, output)
 | 
						|
 | 
						|
array_findcsv(fields, input, field, value, nbfields)
 | 
						|
    initialiser le tableau fields avec la première ligne au format csv du fichier
 | 
						|
    input dont le champ \$field vaut value. Le tableau est toujours initialisé,
 | 
						|
    même si la ligne correspondante n'a pas été trouvée.
 | 
						|
    Retourner 1 si la ligne correspondante a été trouvée
 | 
						|
    Si nbfields est spécifié, c'est le nombre de champs minimum que doit avoir le
 | 
						|
    tableau résultat. Le tableau est complété avec des chaines vides au besoin. Le
 | 
						|
    tableau commence à l'index 1."
 | 
						|
 | 
						|
################################################################################
 | 
						|
 | 
						|
__AWKCSV_HELP="\
 | 
						|
Lancer un script awk pour traiter un flux csv
 | 
						|
Typiquement, l'option -e sera utilisée pour faire un traitement sur les
 | 
						|
données, e.g:
 | 
						|
    awkcsv -e '{ \$2 = toupper(\$2) }'
 | 
						|
pour mettre en majuscule le deuxième champ.
 | 
						|
 | 
						|
Analyse du flux en entrée:
 | 
						|
-s, --skip-lines nblines
 | 
						|
    Sauter nblines au début du flux
 | 
						|
-h, --parse-headers
 | 
						|
    Lire la liste des champs à partir de la première ligne non ignorée du flux.
 | 
						|
    Si la liste des champs est vide, cette option est implicitement activée.
 | 
						|
    Par contre, si une liste de champs est spécifiée et que le flux en entrée
 | 
						|
    contient une ligne d'en-têtes, il *faut* spécifier explicitement cette
 | 
						|
    option.
 | 
						|
--sepconf 'CQE'
 | 
						|
    Spécifier en une seule option les caractères de séparation des colonnes (C),
 | 
						|
    le caractère pour encadrer les chaines (Q) et le caractère d'échappement
 | 
						|
    (E). Pour chacun des caractères, s'il n'est pas spécifié, la valeur par
 | 
						|
    défaut est prise. CQE vaut par défaut ',\"'
 | 
						|
--colsep C
 | 
						|
--qchar Q
 | 
						|
--echar E
 | 
						|
    Spécifier les caractères pour l'analyse du flux csv. Seul le premier
 | 
						|
    caractère de respectivement C, Q et E est considéré.
 | 
						|
    - C est le séparateur des colonnes, e.g \",\"
 | 
						|
    - Q est le caractère employé pour encadrer la valeur d'une colonne si elle
 | 
						|
      contient le caractère C
 | 
						|
    - E est le caractère employé pour mettre en echappement le caractère Q dans
 | 
						|
      la valeur d'une colonne. Si E est vide, le caractère Q doit être doublé.
 | 
						|
 | 
						|
Flux en sortie:
 | 
						|
-n, --no-headers
 | 
						|
    Ne pas afficher les en-têtes. Par défaut, les en-têtes sont affichés.
 | 
						|
-z, --reset-fields
 | 
						|
    Commencer le script -e avec les champs vides. Il faut les copier
 | 
						|
    individuellement avec copyfield().
 | 
						|
 | 
						|
Scripts:
 | 
						|
-v var=value
 | 
						|
    Définir une variable pour le script awk
 | 
						|
-b before
 | 
						|
    Instructions à exécuter avant le début de l'analyse. Utiliser la variante
 | 
						|
    --rb pour remplacer les instructions par défaut, qui sont exécutées avant
 | 
						|
    les instructions de -b
 | 
						|
    Par défaut, il n'y a pas d'instructions -b et les instructions de --rb sont
 | 
						|
    d'analyser les en-têtes si -h est spécifié. Le tableau ORIGHEADERS contient
 | 
						|
    les en-têtes analysées en entrée. Le tableau HEADERS contient les en-têtes
 | 
						|
    spécifiées par l'utilisateur (copie de ORIGHEADERS si l'utilisateur de
 | 
						|
    spécifie pas d'en-têtes). Il est recommandé de ne pas modifier --rb
 | 
						|
 | 
						|
-e script
 | 
						|
    Instructions à exécuter pour chaque ligne en entrée. Utiliser la variante
 | 
						|
    --re pour remplacer les instructions par défaut qui sont exécutées avant les
 | 
						|
    instructions de -e
 | 
						|
    Par défaut, il n'y a pas d'instructions -e et les instructions de --re sont
 | 
						|
    d'analyser la ligne courante. Les champs sont disponibles dans \$1..\$NF et
 | 
						|
    dans le tableau ORIGFIELDS. Il est recommandé de ne pas modifier --re
 | 
						|
-m, --map FIELDMAP
 | 
						|
    Renommer les champs selon la liste FIELDMAP, qui contient des éléments de la
 | 
						|
    forme dest:src et séparés par des virgules. Le champ src doit exister dans
 | 
						|
    HEADERS, et dest ne doit pas exister dans HEADERS, sinon l'élément est
 | 
						|
    ignoré. Un script équivalent au suivant est ajouté aux instructions par
 | 
						|
    défaut --re:
 | 
						|
        '{ if (do_once(\"mapfields\")) mapfields(FIELDMAP) }'
 | 
						|
    Cette option annule l'option -z
 | 
						|
-c, --checkfields FIELDS
 | 
						|
    S'assurer que toutes les en-têtes spécifiées dans la liste FIELDS sont
 | 
						|
    présentes. Sinon, sortir du script avec le code de retour 1. Un script
 | 
						|
    équivalent au suivant est rajouté aux instructions par défaut --re:
 | 
						|
        '{ if (do_once(\"checkfields\") && !checkfields(FIELDS)) exit 1 }'
 | 
						|
    Ce traitement est effectué le cas échéant après le traitement --map
 | 
						|
    Cette option annule l'option -z
 | 
						|
--checkvalues FIELDS
 | 
						|
    S'assurer que toutes les valeurs des en-têtes spécifiées dans la liste
 | 
						|
    FIELDS sont non vides. Sinon, sortir du script avec le code de retour 1. Un
 | 
						|
    script équivalent au suivant est rajouté aux instructions par défaut --re:
 | 
						|
        '{ if (!checkvalues(FIELDS)) exit 1 }'
 | 
						|
    Ce traitement est effectué le cas échéant après le traitement --checkfields
 | 
						|
    Cette option annule l'option -z
 | 
						|
-k, --keep KEEPFIELDS
 | 
						|
    Garder les champs spécifiés. Les autres sont supprimés de la sortie.
 | 
						|
    KEEPFIELDS est une liste de champs séparés par des virgules, ou '*' pour
 | 
						|
    spécifier de garder tous les champs, ou '' pour ne garder aucun champ.
 | 
						|
    Si un champ de KEEPFIELDS n'existe pas, il est créé.
 | 
						|
    Cette option annule l'option -z
 | 
						|
--skip SKIPFIELDS
 | 
						|
    Exclure les champs spécifiés. SKIPFIELDS est une liste de champs séparés par
 | 
						|
    des virgules.
 | 
						|
    Pour les options --keep et --skip, un script équivalent au suivant est
 | 
						|
    rajouté aux instructions par défaut --re:
 | 
						|
        if (do_once(\"filterfields\")) {
 | 
						|
          build_skipfs(KEEPFIELDS, SKIPFIELDS, SKIPFS, ADDFS)
 | 
						|
          filterheaders(SKIPFS, ADDFS)
 | 
						|
        }
 | 
						|
        filterfields(SKIPFS, ADDFS)
 | 
						|
    Ce traitement est effectué le cas échéant après le traitement --checkvalues
 | 
						|
-a after
 | 
						|
    Instructions à exécuter après avoir traité la ligne en entrée. Utiliser la
 | 
						|
    variante --ra pour remplacer les instructions par défaut qui sont exécutées
 | 
						|
    avant les instructions de -a
 | 
						|
    Par défaut, il n'y a pas d'instructions --ra et les instructions de -a sont
 | 
						|
    d'afficher les en-têtes si -n n'est pas spécifié. Puis afficher les champs
 | 
						|
    \$1..\$NF
 | 
						|
 | 
						|
En fonctions des arguments, les variables skip_lines, parse_headers et
 | 
						|
show_headers sont définis. user_headers est un tableau contenant les champs
 | 
						|
spécifiés par l'utilisateur.
 | 
						|
 | 
						|
Les fonctions suivantes sont disponibles:
 | 
						|
do_once(key)
 | 
						|
    Retourner vrai s'il faut faire l'opération identifiées par key (qui vaut
 | 
						|
    par défaut \"default\"), qui ne devrait être faite qu'une seule fois.
 | 
						|
    Utiliser de cette manière:
 | 
						|
        if (do_once(key)) { ... }
 | 
						|
ogeth(field)
 | 
						|
ogeti(num)
 | 
						|
oget(field)
 | 
						|
    Gestion des valeurs originales.
 | 
						|
geth(field)
 | 
						|
sethi(num, value)
 | 
						|
seth(field, newfield)
 | 
						|
addh(field)
 | 
						|
delhi(num)
 | 
						|
delh(field)
 | 
						|
geti(num)
 | 
						|
get(field)
 | 
						|
seti(num, value)
 | 
						|
set(field, value)
 | 
						|
add(field, value)
 | 
						|
deli(num)
 | 
						|
del(field)
 | 
						|
    Gestion des valeurs courantes.
 | 
						|
comparevic(field1, value1, field2, value2, icfields)
 | 
						|
ocompareic(field1, field2, icfields)
 | 
						|
compareic(field1, field2, icfields)
 | 
						|
infields(field, fields)
 | 
						|
parseheaders()
 | 
						|
printheaders()
 | 
						|
resetfields()
 | 
						|
copyfield(field)
 | 
						|
copyfields(fields)
 | 
						|
copyall()
 | 
						|
mapfields(fieldmap)
 | 
						|
checkfields(fields[, missings])
 | 
						|
checkvalues(fields[, missings])
 | 
						|
build_skipfs(keepfields, skipfields, skipfs, addfs)
 | 
						|
filterheaders(skipfs, addfs)
 | 
						|
filterfields(skipfs, addfs)
 | 
						|
    Gestion avancée des valeurs. Pour les fonctions comparevic(), ocompareic(),
 | 
						|
    compareic(), infields(), copyfields(), checkfields(), checkvalues(),
 | 
						|
    mapfields(), build_skipfs(), les arguments sont une suite d'éléments séparés
 | 
						|
    par des virgules."
 | 
						|
 | 
						|
__AWKCSV_FUNCTIONS='
 | 
						|
BEGIN {
 | 
						|
  # Forcer ici le type tableau pour les variables ci-dessous. Sinon, leur
 | 
						|
  # utilisation dans une section BEGIN{} provoque une erreur fatale, parce
 | 
						|
  # qu"ils ne sont déclarés que dans des fonctions
 | 
						|
  array_new(HEADERS)
 | 
						|
  array_new(ORIGHEADERS)
 | 
						|
  array_new(ORIGFIELDS)
 | 
						|
  array_new(__DONE_ONCE)
 | 
						|
}
 | 
						|
function do_once(key) {
 | 
						|
  if (!key) key = "default"
 | 
						|
  if (__DONE_ONCE[key] != "") return 0
 | 
						|
  __DONE_ONCE[key] = 1
 | 
						|
  return 1
 | 
						|
}
 | 
						|
 | 
						|
function __geth(field, HEADERS,               nbfields, i) {
 | 
						|
  nbfields = array_len(HEADERS)
 | 
						|
  if (int(field) == field) {
 | 
						|
    field = int(field)
 | 
						|
    if (field >= 1 && field <= nbfields) return field
 | 
						|
    else return 0
 | 
						|
  }
 | 
						|
  field = tolower(field)
 | 
						|
  for (i = 1; i <= nbfields; i++) {
 | 
						|
    if (field == tolower(HEADERS[i])) {
 | 
						|
      return i
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return 0
 | 
						|
}
 | 
						|
function __geti(num, HEADERS) { if (num != 0) return HEADERS[num] }
 | 
						|
function __get(field, HEADERS) { return __geti(__geth(field, HEADERS), HEADERS) }
 | 
						|
 | 
						|
 | 
						|
function ogeth(field,                nbfields, i) {
 | 
						|
  nbfields = array_len(ORIGHEADERS)
 | 
						|
  if (int(field) == field) {
 | 
						|
    field = int(field)
 | 
						|
    if (field >= 1 && field <= nbfields) return field
 | 
						|
    else return 0
 | 
						|
  }
 | 
						|
  field = tolower(field)
 | 
						|
  for (i = 1; i <= nbfields; i++) {
 | 
						|
    if (field == tolower(ORIGHEADERS[i])) {
 | 
						|
      return i
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return 0
 | 
						|
}
 | 
						|
function ogeti(num) { if (num != 0) return ORIGFIELDS[num] }
 | 
						|
function oget(field) { return ogeti(ogeth(field)) }
 | 
						|
 | 
						|
function geth(field,                nbfields, i) {
 | 
						|
  nbfields = array_len(HEADERS)
 | 
						|
  if (int(field) == field) {
 | 
						|
    field = int(field)
 | 
						|
    if (field >= 1 && field <= nbfields) return field
 | 
						|
    else return 0
 | 
						|
  }
 | 
						|
  field = tolower(field)
 | 
						|
  for (i = 1; i <= nbfields; i++) {
 | 
						|
    if (field == tolower(HEADERS[i])) {
 | 
						|
      return i
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return 0
 | 
						|
}
 | 
						|
function sethi(num, value) { if (num != 0) HEADERS[num] = value }
 | 
						|
function seth(field, value) { sethi(geth(field), value) }
 | 
						|
function addh(field,                 num) {
 | 
						|
  num = geth(field)
 | 
						|
  if (num == 0) {
 | 
						|
    num = array_len(HEADERS) + 1
 | 
						|
    HEADERS[num] = field
 | 
						|
  }
 | 
						|
  return num
 | 
						|
}
 | 
						|
function delhi(num,         i, l) {
 | 
						|
  if (num == 0) return
 | 
						|
  array_deli(HEADERS, num)
 | 
						|
}
 | 
						|
function delh(field) { delhi(geth(field)) }
 | 
						|
function geti(num) { if (num != 0) return $num }
 | 
						|
function get(field) { return geti(geth(field)) }
 | 
						|
function seti(num, value) { if (num != 0) $num = value }
 | 
						|
function set(field, value) { seti(geth(field), value) }
 | 
						|
function add(field, value,           num, i, max) {
 | 
						|
  num = addh(field)
 | 
						|
  i = NF
 | 
						|
  max = array_len(HEADERS)
 | 
						|
  if (i < max) {
 | 
						|
    i = i + 1
 | 
						|
    while(i <= max) {
 | 
						|
      $i = ""
 | 
						|
      i = i + 1
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if ($num == "") {
 | 
						|
    $num = value
 | 
						|
  } else {
 | 
						|
    $num = $num ";" value
 | 
						|
  }
 | 
						|
}
 | 
						|
function deli(num,         i, j) {
 | 
						|
  if (num == 0) return
 | 
						|
  i = num
 | 
						|
  while (i < NF) {
 | 
						|
    j = i + 1
 | 
						|
    $i = $j
 | 
						|
    i = i + 1
 | 
						|
  }
 | 
						|
  NF = NF - 1
 | 
						|
}
 | 
						|
function del(field) { deli(geth(field)) }
 | 
						|
 | 
						|
function comparevic(field1, value1, field2, value2, icfields,           array) {
 | 
						|
  split(icfields, array, /,/)
 | 
						|
  if (in_array(field1, array, 1) || in_array(field2, array, 1)) {
 | 
						|
    return tolower(value1) == tolower(value2)
 | 
						|
  } else {
 | 
						|
    return value1 == value2
 | 
						|
  }
 | 
						|
}
 | 
						|
function ocompareic(field1, field2, icfields,           v1, v2, array) {
 | 
						|
  return comparevic(field1, oget(field1), field2, oget(field2), icfields)
 | 
						|
}
 | 
						|
function compareic(field1, field2, icfields,           v1, v2, array) {
 | 
						|
  return comparevic(field1, get(field1), field2, get(field2), icfields)
 | 
						|
}
 | 
						|
function infields(field, fields,           array) {
 | 
						|
  split(fields, array, /,/)
 | 
						|
  return in_array(field, array, 1)
 | 
						|
}
 | 
						|
function parseheaders() {
 | 
						|
  if (do_once("parse-headers")) {
 | 
						|
    array_parsecsv(ORIGHEADERS, $0)
 | 
						|
    if (!user_headers_count) array_copy(HEADERS, ORIGHEADERS)
 | 
						|
    next
 | 
						|
  }
 | 
						|
}
 | 
						|
function printheaders() {
 | 
						|
  if (do_once("show-headers")) {
 | 
						|
    array_printcsv(HEADERS)
 | 
						|
  }
 | 
						|
}
 | 
						|
function resetheaders() {
 | 
						|
  array_new(HEADERS)
 | 
						|
}
 | 
						|
function resetfields(                 nf) {
 | 
						|
  $0 = ""
 | 
						|
  nf = array_len(HEADERS)
 | 
						|
  $nf = ""
 | 
						|
}
 | 
						|
function copyfield(field) {
 | 
						|
  set(field, oget(field))
 | 
						|
}
 | 
						|
function copyfields(fields,           array) {
 | 
						|
  split(fields, array, /,/)
 | 
						|
  for (i = 1; i <= array_len(array); i++) {
 | 
						|
    copyfield(array[i])
 | 
						|
  }
 | 
						|
}
 | 
						|
function copyall() {
 | 
						|
  array_getline(ORIGFIELDS)
 | 
						|
}
 | 
						|
function checkfields(fields, missings,          array, r, field) {
 | 
						|
  array_new(missings)
 | 
						|
  split(fields, array, /,/)
 | 
						|
  r = 1
 | 
						|
  for (i = 1; i <= array_len(array); i++) {
 | 
						|
    field = array[i]
 | 
						|
    if (geth(field) == 0) {
 | 
						|
      array_add(missings, field)
 | 
						|
      r = 0
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return r
 | 
						|
}
 | 
						|
function checkvalues(fields, missings,          array, r, field) {
 | 
						|
  array_new(missings)
 | 
						|
  split(fields, array, /,/)
 | 
						|
  r = 1
 | 
						|
  for (i = 1; i <= array_len(array); i++) {
 | 
						|
    field = array[i]
 | 
						|
    if (geth(field) == 0 || !get(field)) {
 | 
						|
      array_add(missings, field)
 | 
						|
      r = 0
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return r
 | 
						|
}
 | 
						|
function mapfields(fieldmap,           mapitems, parts) {
 | 
						|
  split(fieldmap, mapitems, /,/)
 | 
						|
  for (i = 1; i <= array_len(mapitems); i++) {
 | 
						|
    split(mapitems[i], parts, /:/)
 | 
						|
    if (array_len(parts) != 2) continue
 | 
						|
    desti = geth(parts[1])
 | 
						|
    srci = geth(parts[2])
 | 
						|
    if (desti == 0 && srci != 0) {
 | 
						|
      HEADERS[srci] = parts[1]
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
function build_skipfs(keepfields, skipfields, skipfs, addfs,         keepfs, keepfs_count, tmpfields, headers_count, i, field, fieldi) {
 | 
						|
  array_new(skipfs)
 | 
						|
  array_new(addfs)
 | 
						|
  if (skipfields == "*") { skipfields = ""; keepfields = "" }
 | 
						|
  if (keepfields == "*" && skipfields == "") return 0
 | 
						|
  # construire la liste des champs à garder dans keepfs
 | 
						|
  split(keepfields, keepfs, /,/)
 | 
						|
  if (in_array("*", keepfs)) {
 | 
						|
    array_del(keepfs, "*")
 | 
						|
    array_extend(keepfs, HEADERS)
 | 
						|
  }
 | 
						|
  split(skipfields, tmpfields, /,/)
 | 
						|
  for (i in tmpfields) {
 | 
						|
    array_del(keepfs, tmpfields[i], 1)
 | 
						|
  }
 | 
						|
  keepfs_count = array_len(keepfs)
 | 
						|
  # puis construire la liste des champs à supprimer dans skipfs
 | 
						|
  headers_count = array_len(HEADERS)
 | 
						|
  for (i = 1; i <= headers_count; i++) {
 | 
						|
    field = HEADERS[i]
 | 
						|
    if (!in_array(field, keepfs, 1)) {
 | 
						|
      fieldi = geth(field)
 | 
						|
      if (i != 0) array_add(skipfs, fieldi)
 | 
						|
    }
 | 
						|
  }
 | 
						|
  asort(skipfs)
 | 
						|
  # puis construire la liste des champs à ajouter dans addfs
 | 
						|
  for (i = 1; i <= keepfs_count; i++) {
 | 
						|
    field = keepfs[i]
 | 
						|
    if (!in_array(field, HEADERS, 1)) {
 | 
						|
      array_add(addfs, field)
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return array_len(skipfs)
 | 
						|
}
 | 
						|
function filterheaders(skipfs, addfs,        skipfs_count, addfs_count) {
 | 
						|
  skipfs_count = array_len(skipfs)
 | 
						|
  for (i = skipfs_count; i >= 1; i--) {
 | 
						|
    array_deli(HEADERS, skipfs[i])
 | 
						|
  }
 | 
						|
  addfs_count = array_len(addfs)
 | 
						|
  for (i = 1; i <= addfs_count; i++) {
 | 
						|
    addh(addfs[i])
 | 
						|
  }
 | 
						|
}
 | 
						|
function filterfields(skipfs, addfs,         skipfs_count) {
 | 
						|
  skipfs_count = array_len(skipfs)
 | 
						|
  for (i = skipfs_count; i >= 1; i--) {
 | 
						|
    deli(skipfs[i])
 | 
						|
  }
 | 
						|
  addfs_count = array_len(addfs)
 | 
						|
  for (i = 1; i <= addfs_count; i++) {
 | 
						|
    add(addfs[i], "")
 | 
						|
  }
 | 
						|
}
 | 
						|
'
 | 
						|
function lawkcsv() {
 | 
						|
    local beforescript='{
 | 
						|
  if (user_headers_count && do_once("user-headers")) {
 | 
						|
    if (!parse_headers) array_copy(ORIGHEADERS, user_headers)
 | 
						|
    array_copy(HEADERS, user_headers)
 | 
						|
  }
 | 
						|
  if (parse_headers) parseheaders()
 | 
						|
}'
 | 
						|
    local append_beforescript=
 | 
						|
    local awkscript='{
 | 
						|
  array_parsecsv(ORIGFIELDS, $0, array_len(ORIGHEADERS))
 | 
						|
  if (reset_fields) { resetfields() } else { copyall() }
 | 
						|
}'
 | 
						|
    local append_awkscript=
 | 
						|
    local afterscript=
 | 
						|
    local append_afterscript='{
 | 
						|
  if (show_headers) printheaders()
 | 
						|
  printcsv()
 | 
						|
}'
 | 
						|
 | 
						|
    local -a args headers vars
 | 
						|
    local skip_lines=0 parse_headers=
 | 
						|
    local autosep=
 | 
						|
    local sepconf=',"' colsep=',' qchar='"' echar=
 | 
						|
    local show_headers=1 reset_fields=
 | 
						|
    local fieldmap checkfields checkvalues keepfields skipfields
 | 
						|
    if parse_opts \
 | 
						|
        -s:,--skip-lines: skip_lines= \
 | 
						|
        -h,--parse-headers parse_headers=1 \
 | 
						|
        --sepconf: '$autosep=sepconf; set@ sepconf' \
 | 
						|
        --colsep: '$autosep=indiv; set@ colsep' \
 | 
						|
        --qchar: '$autosep=indiv; set@ qchar' \
 | 
						|
        --echar: '$autosep=indiv; set@ echar' \
 | 
						|
        -n,--no-headers show_headers= \
 | 
						|
        --show-headers show_headers=1 \
 | 
						|
        -z,--reset-fields reset_fields=1 \
 | 
						|
        -v:,--var: vars \
 | 
						|
        --rb:,--rbe: '$set@ beforescript; append_beforescript=' \
 | 
						|
        -b:,--b:,--be:,--before-script: append_beforescript= \
 | 
						|
        --re: '$set@ awkscript; append_awkscript=' \
 | 
						|
        -e:,--e:,--awk-script:,--script: append_awkscript= \
 | 
						|
        -m:,--map:,--mapfields fieldmap= \
 | 
						|
        -c:,--checkfields: checkfields= \
 | 
						|
        --checkvalues: checkvalues= \
 | 
						|
        -k:,--keep:,--keepfields: keepfields= \
 | 
						|
        --skip:,--skipfields: skipfields= \
 | 
						|
        --ra:,--rae: '$set@ afterscript; append_afterscript=' \
 | 
						|
        -a:,--a:,--ae:,--after-script: append_afterscript= \
 | 
						|
        @ args -- "$@"; then
 | 
						|
        set -- "${args[@]}"
 | 
						|
    else
 | 
						|
        eerror "$args"
 | 
						|
        return 1
 | 
						|
    fi
 | 
						|
 | 
						|
    if [ "$autosep" == sepconf ]; then
 | 
						|
        colsep="${sepconf:0:1}"
 | 
						|
        qchar="${sepconf:1:1}"
 | 
						|
        echar="${sepconf:2:1}"
 | 
						|
        autosep=
 | 
						|
        if [ -n "$colsep" -o -n "$qchar" -o -n "$echar" ]; then
 | 
						|
            [ -n "$colsep" ] || colsep=','
 | 
						|
            [ -n "$qchar" ] || qchar='"'
 | 
						|
            # n'activer la configuration que si elle diffère de la configuration
 | 
						|
            # par défaut
 | 
						|
            [ "$colsep" != ',' -o "$qchar" != '"' -o -n "$echar" ] && autosep=1
 | 
						|
        fi
 | 
						|
    elif [ "$autosep" == indiv ]; then
 | 
						|
        autosep=1
 | 
						|
    fi
 | 
						|
    colsep="${colsep:0:1}"
 | 
						|
    qchar="${qchar:0:1}"
 | 
						|
    echar="${echar:0:1}"
 | 
						|
 | 
						|
    headers=()
 | 
						|
    while [ $# -gt 0 -a "$1" != "--" ]; do
 | 
						|
        array_add headers "$1"
 | 
						|
        shift
 | 
						|
    done
 | 
						|
    shift
 | 
						|
 | 
						|
    [ -n "${headers[*]}" ] || parse_headers=1
 | 
						|
 | 
						|
    if [ -n "$fieldmap" ]; then
 | 
						|
        awkscript="$awkscript"'{
 | 
						|
  if (do_once("mapfields")) { mapfields(fields2map) }
 | 
						|
}'
 | 
						|
        reset_fields=
 | 
						|
    fi
 | 
						|
    if [ -n "$checkfields" ]; then
 | 
						|
        awkscript="$awkscript"'{
 | 
						|
  if (do_once("checkfields")) { if (!checkfields(fields2check)) exit 1 }
 | 
						|
}'
 | 
						|
        reset_fields=
 | 
						|
    fi
 | 
						|
    if [ -n "$checkvalues" ]; then
 | 
						|
        awkscript="$awkscript"'{
 | 
						|
  if (!checkvalues(values2check)) exit 1
 | 
						|
}'
 | 
						|
        reset_fields=
 | 
						|
    fi
 | 
						|
    if [ -n "$skipfields" ]; then
 | 
						|
        [ -n "$keepfields" ] || keepfields="*"
 | 
						|
    fi
 | 
						|
    if [ -n "$keepfields" ]; then
 | 
						|
        awkscript="$awkscript"'{
 | 
						|
  if (do_once("filterfields")) {
 | 
						|
    build_skipfs(fields2keep, fields2skip, SKIPFS, ADDFS)
 | 
						|
    filterheaders(SKIPFS, ADDFS)
 | 
						|
  }
 | 
						|
  filterfields(SKIPFS, ADDFS)
 | 
						|
}'
 | 
						|
        reset_fields=
 | 
						|
    fi
 | 
						|
 | 
						|
    awkrun -f \
 | 
						|
        skip_lines:int="$skip_lines" parse_headers:int="$parse_headers" \
 | 
						|
        autosep:int="$autosep" colsep="$colsep" qchar="$qchar" echar="$echar" \
 | 
						|
        show_headers:int="$show_headers" reset_fields:int="$reset_fields" \
 | 
						|
        fields2map="$fieldmap" fields2check="$checkfields" values2check="$checkvalues" \
 | 
						|
        fields2keep="$keepfields" fields2skip="$skipfields" \
 | 
						|
        "user_headers[@]=headers" "${vars[@]}" \
 | 
						|
"$__AWKCSV_FUNCTIONS"'
 | 
						|
BEGIN {
 | 
						|
  if (autosep) {
 | 
						|
    DEFAULT_COLSEP = colsep
 | 
						|
    DEFAULT_QCHAR = qchar
 | 
						|
    DEFAULT_ECHAR = echar
 | 
						|
  }
 | 
						|
}
 | 
						|
NR <= skip_lines { next }
 | 
						|
'"$beforescript
 | 
						|
$append_beforescript
 | 
						|
$awkscript
 | 
						|
$append_awkscript
 | 
						|
$afterscript
 | 
						|
$append_afterscript
 | 
						|
" -- "$@"
 | 
						|
}
 | 
						|
 | 
						|
function cawkcsv() { LANG=C lawkcsv "$@"; }
 | 
						|
function awkcsv() { LANG=C lawkcsv "$@"; }
 | 
						|
 | 
						|
################################################################################
 | 
						|
 | 
						|
__GREPCSV_HELP="\
 | 
						|
Faire une recherche dans un flux csv, et afficher uniquement les lignes qui
 | 
						|
correspondent à l'expression.
 | 
						|
EXPR est une expression awk, e.g. 'field == \"value1\" || field == \"value2\"'
 | 
						|
 | 
						|
Analyse du flux en entrée:
 | 
						|
-s, --skip-lines nblines
 | 
						|
    Sauter nblines au début du flux
 | 
						|
-h, --parse-headers
 | 
						|
    Lire la liste des champs à partir de la première ligne non ignorée du flux.
 | 
						|
    Si la liste des champs est vide, cette option est implicitement activée.
 | 
						|
    Par contre, si une liste de champs est spécifiée et que le flux en entrée
 | 
						|
    contient un ligne d'en-têtes, il *faut* spécifier explicitement cette
 | 
						|
    option.
 | 
						|
--sepconf 'CQE'
 | 
						|
    Spécifier en une seule option les caractères de séparation des colonnes (C),
 | 
						|
    le caractère pour encadrer les chaines (Q) et le caractère d'échappement
 | 
						|
    (E). Pour chacun des caractères, s'il n'est pas spécifié, la valeur par
 | 
						|
    défaut est prise. CQE vaut par défaut ',\"'
 | 
						|
--colsep C
 | 
						|
--qchar Q
 | 
						|
--echar E
 | 
						|
    Spécifier les caractères pour l'analyse du flux csv. Seul le premier
 | 
						|
    caractère de respectivement C, Q et E est considéré.
 | 
						|
    - C est le séparateur des colonnes, e.g \",\"
 | 
						|
    - Q est le caractère employé pour encadrer la valeur d'une colonne si elle
 | 
						|
      contient le caractère C
 | 
						|
    - E est le caractère employé pour mettre en echappement le caractère Q dans
 | 
						|
      la valeur d'une colonne. Si E est vide, le caractère Q doit être doublé.
 | 
						|
 | 
						|
Flux en sortie:
 | 
						|
-n, --no-headers
 | 
						|
    Ne pas afficher les en-têtes. Par défaut, les en-têtes sont affichés.
 | 
						|
-q, --quiet
 | 
						|
    Ne pas afficher les lignes. Indiquer seulement si des lignes ont été
 | 
						|
    trouvées.
 | 
						|
 | 
						|
Scripts:
 | 
						|
-v var=value
 | 
						|
    Définir une variable pour le script awk
 | 
						|
-e script
 | 
						|
    Instructions à exécuter pour chaque ligne en entrée. Utiliser la variante
 | 
						|
    --re pour remplacer les instructions par défaut qui sont exécutées avant les
 | 
						|
    instructions de -e
 | 
						|
    Par défaut, il n'y a pas d'instructions -e et les instructions de --re sont
 | 
						|
    de créer des variables nommées d'après les colonnes du flux csv. Il est
 | 
						|
    recommandé de ne pas modifier --re
 | 
						|
    Note d'implémentation: pour des raisons d'optimisation, le traitement
 | 
						|
    effectué sur chaque ligne n'est pas aussi complet que celui de la fonction
 | 
						|
    awkcsv(): Les tableaux ORIGFIELDS et ORIGHEADERS sont disponible; par
 | 
						|
    contre, la ligne courante est au format csv non analysé. Utiliser la
 | 
						|
    fonction copyall() pour être dans les mêmes conditions que le script awkcsv."
 | 
						|
 | 
						|
function lgrepcsv() {
 | 
						|
    local -a args vars
 | 
						|
    local skip_lines= parse_headers=
 | 
						|
    local sepconf= colsep= qchar= echar=
 | 
						|
    local no_headers= quiet=
 | 
						|
    local awkscript=--use-default--
 | 
						|
    local append_awkscript=
 | 
						|
    if parse_opts \
 | 
						|
        -s:,--skip-lines: skip_lines= \
 | 
						|
        -h,--parse-headers parse_headers=1 \
 | 
						|
        --sepconf: sepconf= \
 | 
						|
        --colsep: colsep= \
 | 
						|
        --qchar: qchar= \
 | 
						|
        --echar: echar= \
 | 
						|
        -n,--no-headers no_headers=1 \
 | 
						|
        --show-headers no_headers= \
 | 
						|
        -q,--quiet quiet=1 \
 | 
						|
        -v:,--var: vars \
 | 
						|
        --re: '$set@ awkscript; append_awkscript=' \
 | 
						|
        -e:,--e:,--awk-script:,--script: append_awkscript= \
 | 
						|
        @ args -- "$@"; then
 | 
						|
        set -- "${args[@]}"
 | 
						|
    else
 | 
						|
        eerror "$args"
 | 
						|
        return 1
 | 
						|
    fi
 | 
						|
 | 
						|
    local expr="$1"; shift
 | 
						|
 | 
						|
    local -a inputfiles tmpfiles
 | 
						|
    local inputfile
 | 
						|
    while [ $# -gt 0 -a "$1" != "--" ]; do
 | 
						|
        if [ "$1" == "-" ]; then
 | 
						|
            ac_set_tmpfile inputfile
 | 
						|
            array_add tmpfiles "$inputfile"
 | 
						|
            cat >"$inputfile"
 | 
						|
        else
 | 
						|
            inputfile="$1"
 | 
						|
        fi
 | 
						|
        array_add inputfiles "$inputfile"
 | 
						|
        shift
 | 
						|
    done
 | 
						|
    shift
 | 
						|
    if [ "${#inputfiles[*]}" -eq 0 ]; then
 | 
						|
        ac_set_tmpfile inputfile
 | 
						|
        array_add tmpfiles "$inputfile"
 | 
						|
        cat >"$inputfile"
 | 
						|
        array_add inputfiles "$inputfile"
 | 
						|
    fi
 | 
						|
 | 
						|
    local -a headers
 | 
						|
    headers=("$@")
 | 
						|
    if [ -z "${headers[*]}" ]; then
 | 
						|
        parse_headers=1
 | 
						|
        array_from_lines headers "$(awkrun -f lskip:int="$skip_lines" 'NR <= lskip { next }
 | 
						|
{
 | 
						|
  count = array_parsecsv(__fields, $0)
 | 
						|
  for (i = 1; i <= count; i++) {
 | 
						|
    print __fields[i]
 | 
						|
  }
 | 
						|
  exit
 | 
						|
}' -- "${inputfiles[@]}")"
 | 
						|
    fi
 | 
						|
    if [ "$awkscript" == --use-default-- ]; then
 | 
						|
        awkscript=
 | 
						|
        for header in "${headers[@]}"; do
 | 
						|
            awkscript="$awkscript
 | 
						|
$header = oget(\"$header\")"
 | 
						|
        done
 | 
						|
    fi
 | 
						|
    local grepscript="\
 | 
						|
BEGIN { ec = 1 }
 | 
						|
{
 | 
						|
  $awkscript
 | 
						|
  $append_awkscript
 | 
						|
  if ($expr) {
 | 
						|
    ec = 0
 | 
						|
    if (quiet) exit
 | 
						|
    printheaders()
 | 
						|
    print
 | 
						|
  }
 | 
						|
}
 | 
						|
END { exit ec }"
 | 
						|
 | 
						|
    lawkcsv ${skip_lines:+-s "$skip_lines"} ${parse_headers:+-h} \
 | 
						|
        ${sepconf:+--sepconf "$sepconf"} ${colsep:+--colsep "$colsep"} ${qchar:+--qchar "$qchar"} ${echar:+--echar "$echar"} \
 | 
						|
        ${no_headers:+--no-headers} -v quiet:int=$quiet \
 | 
						|
        --re '{array_parsecsv(ORIGFIELDS, $0, array_len(ORIGHEADERS))}' -a "$grepscript" \
 | 
						|
        -- "${headers[@]}" -- "${inputfiles[@]}"
 | 
						|
    local r=$?
 | 
						|
 | 
						|
    ac_clean "${tmpfiles[@]}"
 | 
						|
    return $r
 | 
						|
}
 | 
						|
 | 
						|
function cgrepcsv() { LANG=C lgrepcsv "$@"; }
 | 
						|
function grepcsv() { LANG=C lgrepcsv "$@"; }
 | 
						|
 | 
						|
################################################################################
 | 
						|
 | 
						|
__AWKFSV2CSV_HELP="\
 | 
						|
Transformer un flux fsv (colonnes à largeurs fixes) en csv
 | 
						|
 | 
						|
Chaque argument doit être de la forme [-]header:size. La colonne sera incluse
 | 
						|
dans le fichier en sortie, sauf si elle est précédée de -
 | 
						|
 | 
						|
Analyse du flux en entrée:
 | 
						|
-s, --skip-lines nblines
 | 
						|
    Sauter nblines au début du flux
 | 
						|
-r, --no-trim
 | 
						|
    Ne pas trimmer les valeurs à droite.
 | 
						|
 | 
						|
Flux en sortie:
 | 
						|
-n, --no-headers
 | 
						|
    Ne pas afficher les en-têtes. Par défaut, les en-têtes sont affichés."
 | 
						|
 | 
						|
function lawkfsv2csv() {
 | 
						|
    local -a args headersizes
 | 
						|
    local skip_lines=0 trim_values=1 show_headers=1
 | 
						|
    if parse_opts \
 | 
						|
        -s:,--skip-lines: skip_lines= \
 | 
						|
        -r,--no-trim trim_values= \
 | 
						|
        -n,--no-headers show_headers= \
 | 
						|
        --show-headers show_headers=1 \
 | 
						|
        @ args -- "$@"; then
 | 
						|
        set -- "${args[@]}"
 | 
						|
    else
 | 
						|
        eerror "$args"
 | 
						|
        return 1
 | 
						|
    fi
 | 
						|
 | 
						|
    local -a headers starts sizes
 | 
						|
    local headersize header i size
 | 
						|
    i=1
 | 
						|
    while [ $# -gt 0 -a "$1" != "--" ]; do
 | 
						|
        headersize="$1"
 | 
						|
        shift
 | 
						|
 | 
						|
        splitpair "$headersize" header size
 | 
						|
        [ -n "$header" ] || {
 | 
						|
            eerror "header est requis"
 | 
						|
            return 1
 | 
						|
        }
 | 
						|
        [ -n "$size" ] || size=1
 | 
						|
        if [ "${header#-}" == "$header" ]; then
 | 
						|
            array_add headers "$header"
 | 
						|
            array_add starts "$i"
 | 
						|
            array_add sizes "$size"
 | 
						|
        fi
 | 
						|
        i="$(($i + $size))"
 | 
						|
    done
 | 
						|
    shift
 | 
						|
 | 
						|
    awkrun -f \
 | 
						|
        skip_lines:int="$skip_lines" trim_values:int="$trim_values" show_headers:int="$show_headers" \
 | 
						|
        headers[@] starts[@] sizes[@] \
 | 
						|
        "$__AWKCSV_FUNCTIONS"'
 | 
						|
BEGIN {
 | 
						|
  if (show_headers) {
 | 
						|
    print array_formatcsv(headers)
 | 
						|
  }
 | 
						|
}
 | 
						|
NR <= skip_lines { next }
 | 
						|
{
 | 
						|
  line = $0
 | 
						|
  $0 = ""
 | 
						|
  for (i = 1; i <= headers_count; i++) {
 | 
						|
    value = substr(line, starts[i], sizes[i])
 | 
						|
    if (trim_values) {
 | 
						|
      sub(/^ */, "", value)
 | 
						|
      sub(/ *$/, "", value)
 | 
						|
    }
 | 
						|
    $i = value
 | 
						|
  }
 | 
						|
  formatcsv()
 | 
						|
  print
 | 
						|
}
 | 
						|
' -- "$@"
 | 
						|
}
 | 
						|
 | 
						|
function cawkfsv2csv() { LANG=C lawkfsv2csv "$@"; }
 | 
						|
function awkfsv2csv() { LANG=C lawkfsv2csv "$@"; }
 | 
						|
 | 
						|
################################################################################
 | 
						|
 | 
						|
__MERGECSV_HELP="\
 | 
						|
Fusionner deux fichiers csv en faisant la correspondance sur la valeur d'un
 | 
						|
champ, qui est la clé
 | 
						|
 | 
						|
-h, --parse-headers
 | 
						|
    Lire la liste des champs à partir de la première ligne non ignorée des flux.
 | 
						|
    Si cette option est spécifiée (ce qui est le cas par défaut), les champs
 | 
						|
    spécifiés avec les options -k, --lk et --rk peuvent être les noms effectifs
 | 
						|
    des champs. Sinon, les champs ne peuvent être que numériques.
 | 
						|
-n, --numkeys
 | 
						|
    Ne pas analyser la première ligne pour les noms des champs. Les champs
 | 
						|
    spécifiés ne peuvent être que numériques.
 | 
						|
--lskip nblines
 | 
						|
--rskip nblines
 | 
						|
    Sauter nblines au début du flux, respectivement pour left et pour right
 | 
						|
--lheaders FIELDS
 | 
						|
--rheaders FIELDS
 | 
						|
    Spécifier les noms des champs à utiliser *en sortie*, respectivement pour
 | 
						|
    left et pour right. Les champs doivent être séparés par des virgules.
 | 
						|
    Si ces valeurs ne sont pas spécifiées, prendre les champs lus avec l'option
 | 
						|
    --parse-headers. Sinon, ne pas afficher d'en-têtes en sortie.
 | 
						|
--lprefix prefix
 | 
						|
--rprefix prefix
 | 
						|
    Spécifier un préfixe à rajouter automatiquement aux champs à utiliser *en
 | 
						|
    sortie*. Le prefix est rajouté que les champs soient déterminés par
 | 
						|
    --parse-headers ou spécifiés par --{l,r}headers. La correspondance des
 | 
						|
    champs avec --lk et --rk est faite sur les noms des champs *avant* qu'ils
 | 
						|
    soient modifiés par cette option.
 | 
						|
--lk, --lkey FIELD
 | 
						|
--rk, --rkey FIELD
 | 
						|
    Spécifier le champ utilisé pour faire la correspondance entre les deux flux,
 | 
						|
    respectivement pour left et pour right.
 | 
						|
    Si --parse-headers n'est pas spécifié, ces valeurs doivent être numériques.
 | 
						|
    La valeur par défaut est 1.
 | 
						|
-k, --key FIELD
 | 
						|
    Spécifier le champ utilisé pour faire la correspondance sur les deux
 | 
						|
    flux. Equivalent à '--lk FIELD --rk FIELD'
 | 
						|
    La correspondance des champs avec --lk et --rk est faite sur les noms des
 | 
						|
    champs *avant* qu'ils soient modifiés le cas échéant par --lprefix et/ou
 | 
						|
    --rprefix
 | 
						|
-i
 | 
						|
    Faire la correspondance de la valeur des champs sans tenir compte de la
 | 
						|
    casse
 | 
						|
 | 
						|
Après la fusion des deux fichiers csv, il est possible de faire une sélection
 | 
						|
des lignes selon certains critères:
 | 
						|
 | 
						|
-s SELECT
 | 
						|
    Spécifier un type de sélection à faire. SELECT peut valoir:
 | 
						|
    none, n
 | 
						|
        C'est l'action par défaut; toutes les lignes sont affichées
 | 
						|
    inner-join, j
 | 
						|
        N'inclure dans le résultat que les lignes pour lesquelles il y a une
 | 
						|
        correspondance sur les clés.
 | 
						|
        L'option -J est synonyme de '-s inner-join'
 | 
						|
    left-join, l
 | 
						|
        Inclure dans le résultat les lignes pour lesquelles il y a une
 | 
						|
        correspondance sur les clés, et les lignes de left pour lesquelles il
 | 
						|
        n'y a pas de correspondance. Les lignes de right sans correspondance ne
 | 
						|
        sont pas affichées.
 | 
						|
        L'option -L est synonyme de '-s left-join'
 | 
						|
    right-join, r
 | 
						|
        Inclure dans le résultat les lignes pour lesquelles il y a une
 | 
						|
        correspondance sur les clés, et les lignes de right pour lesquelles il
 | 
						|
        n'y a pas de correspondance. Les lignes de left sans correspondance ne
 | 
						|
        sont pas affichées.
 | 
						|
        L'option -R est synonyme de '-s right-join'
 | 
						|
    left-only, lo
 | 
						|
        N'inclure dans le résultat que les lignes de left pour lesquelles il n'y
 | 
						|
        a pas de correspondance dans right.
 | 
						|
    right-only, ro
 | 
						|
        N'inclure dans le résultat que les lignes de right pour lesquelles il n'y
 | 
						|
        a pas de correspondance dans left.
 | 
						|
 | 
						|
Les options suivantes sont des actions qu'il est possible d'effectuer après la
 | 
						|
sélection des lignes. Les noms des champs spécifiés dans ces options sont les
 | 
						|
noms en sortie, en tenant compte de l'analyse et du traitement effectués par les
 | 
						|
options --parse-headers, --{l,r}headers et --{l,r}prefix.
 | 
						|
 | 
						|
--copy, --lcopyf FIELDS
 | 
						|
    Copier les champs spécifiés de right vers left. Les champs spécifiés sont
 | 
						|
    séparés par des virgules et sont de la forme [dest:]src
 | 
						|
--rcopyf FIELDS
 | 
						|
    Copier les champs spécifiés de left vers right. Les champs spécifiés sont
 | 
						|
    séparés par des virgules et sont de la forme [dest:]src
 | 
						|
--lkeepf FIELDS
 | 
						|
    Garder les champs de left spécifiés. Les autres sont supprimés de la sortie.
 | 
						|
    FIELDS est une liste de champs séparés par des virgules, ou '*' pour
 | 
						|
    spécifier de garder tous les champs ou '' pour ne garder aucun champ.
 | 
						|
    Par défaut, avec '-s right-only', FIELDS vaut '', sinon FIELDS vaut '*'
 | 
						|
--keep, --rkeepf FIELDS
 | 
						|
    Garder les champs de right spécifiés. Les autres sont supprimés de la
 | 
						|
    sortie. FIELDS est une liste de champs séparés par des virgules, ou '*' pour
 | 
						|
    spécifier de garder tous les champs, ou '' pour ne garder aucun champ.
 | 
						|
    Par défaut, avec '-s left-only', FIELDS vaut '', sinon FIELDS vaut '*'
 | 
						|
--lskipf FIELDS
 | 
						|
--rskipf FIELDS
 | 
						|
    Exclure les champs de left (resp. de right) spécifiés. FIELDS est une liste
 | 
						|
    de champs séparés par des virgules."
 | 
						|
 | 
						|
: "${__MERGECSV_DEBUG:=}"
 | 
						|
function lmergecsv() {
 | 
						|
    # Fusionner sur la sortie standard les deux fichiers csv $1 et $2. La clé du
 | 
						|
    # fichier $1 est spécifiée par l'option --lkey et vaut 1 par défaut. La clé
 | 
						|
    # du fichier $2 est spécifiée par l'option --rkey et vaut 1 par défaut. Les
 | 
						|
    # valeurs des clés ne doivent pas faire plus de 64 caractères de long.
 | 
						|
    eval "$(utools_local)"
 | 
						|
    local parse_headers=auto
 | 
						|
    local ignore_case=
 | 
						|
    local lskip=0 lkey=1 lheaders= lprefix=
 | 
						|
    local rskip=0 rkey=1 rheaders= rprefix=
 | 
						|
    local select=none
 | 
						|
 | 
						|
    local postproc=auto
 | 
						|
    local lcopyf= rcopyf=
 | 
						|
    local lkeepf=--NOT-SET-- rkeepf=--NOT-SET--
 | 
						|
    local lskipf= rskipf=
 | 
						|
    parse_opts "${PRETTYOPTS[@]}" \
 | 
						|
        -h,--parse-headers parse_headers=1 \
 | 
						|
        -n,--numkeys parse_headers= \
 | 
						|
        --lskip: lskip= \
 | 
						|
        --lkey:,--lk: lkey= \
 | 
						|
        --lheaders:,--lh: lheaders= \
 | 
						|
        --lprefix:,--lp: lprefix= \
 | 
						|
        --rskip: rskip= \
 | 
						|
        --rkey:,--rk: rkey= \
 | 
						|
        --rheaders:,--rh: rheaders= \
 | 
						|
        --rprefix:,--rp: rprefix= \
 | 
						|
        -i,--ignore-case ignore_case=1 \
 | 
						|
        -k:,--key: '$set@ lkey; set@ rkey' \
 | 
						|
        -s:,--select: select= \
 | 
						|
        -J select=inner-join \
 | 
						|
        -L select=left-join \
 | 
						|
        -R select=right-join \
 | 
						|
        --lcopyf:,--copy: '$set@ lcopyf; postproc=1' \
 | 
						|
        --rcopyf: '$set@ rcopyf; postproc=1' \
 | 
						|
        --lkeepf: '$set@ lkeepf; postproc=1' \
 | 
						|
        --rkeepf:,--keep: '$set@ rkeepf; postproc=1' \
 | 
						|
        --lskipf: '$set@ lskipf; postproc=1' \
 | 
						|
        --rskipf: '$set@ rskipf; postproc=1' \
 | 
						|
        @ args -- "$@" && set -- "${args[@]}" || die "$args"
 | 
						|
 | 
						|
    local lfile="$1"
 | 
						|
    local rfile="$2"
 | 
						|
    [ -f "$lfile" -a -f "$rfile" ] || {
 | 
						|
        [ -f "$lfile" ] || eerror "$lfile: fichier introuvable"
 | 
						|
        [ -f "$rfile" ] || eerror "$rfile: fichier introuvable"
 | 
						|
        return 1
 | 
						|
    }
 | 
						|
 | 
						|
    local padding="----------------------------------------------------------------"
 | 
						|
    local padlen=${#padding}
 | 
						|
 | 
						|
    [ "$parse_headers" == "auto" ] && parse_headers=1
 | 
						|
    if [ -n "$parse_headers" ]; then
 | 
						|
        [ -n "$lheaders" ] || lheaders="$(<"$lfile" awkrun lskip:int="$lskip" 'NR <= lskip { next } { print; exit }')"
 | 
						|
        [ -n "$rheaders" ] || rheaders="$(<"$rfile" awkrun lskip:int="$lskip" 'NR <= lskip { next } { print; exit }')"
 | 
						|
    fi
 | 
						|
 | 
						|
    # faire le fichier de travail pour lfile
 | 
						|
    local tmpleft
 | 
						|
    ac_set_tmpfile tmpleft "" __mergecsv_left "" __MERGECSV_DEBUG
 | 
						|
    <"$lfile" awkrun -f \
 | 
						|
        padding="$padding" padlen:int="$padlen" \
 | 
						|
        parse_headers:int="$parse_headers" ignore_case:int="$ignore_case" \
 | 
						|
        lskip:int="$lskip" lkey="$lkey" \
 | 
						|
        "$__AWKCSV_FUNCTIONS"'
 | 
						|
NR <= lskip { next }
 | 
						|
parse_headers && do_once("parse-headers") {
 | 
						|
  array_parsecsv(HEADERS, $0)
 | 
						|
  lkey = geth(lkey)
 | 
						|
  if (!lkey) lkey = 1
 | 
						|
  next
 | 
						|
}
 | 
						|
{
 | 
						|
  oline = $0
 | 
						|
  parsecsv($0)
 | 
						|
  keyvalue = substr($lkey, 1, padlen)
 | 
						|
  if (ignore_case) keyvalue = tolower(keyvalue)
 | 
						|
  print keyvalue substr(padding, 1, padlen - length(keyvalue)) "1" oline
 | 
						|
}
 | 
						|
' | csort -su >"$tmpleft"
 | 
						|
 | 
						|
    # faire le fichier de travail pour rfile
 | 
						|
    local tmpright
 | 
						|
    ac_set_tmpfile tmpright "" __mergecsv_right "" __MERGECSV_DEBUG
 | 
						|
    <"$rfile" awkrun -f \
 | 
						|
        padding="$padding" padlen:int="$padlen" \
 | 
						|
        parse_headers:int="$parse_headers" ignore_case:int="$ignore_case" \
 | 
						|
        rskip:int="$rskip" rkey="$rkey" \
 | 
						|
        "$__AWKCSV_FUNCTIONS"'
 | 
						|
NR <= rskip { next }
 | 
						|
parse_headers && do_once("parse-headers") {
 | 
						|
  array_parsecsv(HEADERS, $0)
 | 
						|
  rkey = geth(rkey)
 | 
						|
  if (!rkey) rkey = 1
 | 
						|
  next
 | 
						|
}
 | 
						|
{
 | 
						|
  oline = $0
 | 
						|
  parsecsv($0)
 | 
						|
  keyvalue = substr($rkey, 1, padlen)
 | 
						|
  if (ignore_case) keyvalue = tolower(keyvalue)
 | 
						|
  print keyvalue substr(padding, 1, padlen - length(keyvalue)) "2" oline
 | 
						|
}
 | 
						|
' | csort -su >"$tmpright"
 | 
						|
 | 
						|
    # calculer les options de post-traitement
 | 
						|
    case "$select" in
 | 
						|
    none|n|"") select=none;;
 | 
						|
    inner-join|inner|join|j) select=inner-join;;
 | 
						|
    left-join|left|l) select=left-join;;
 | 
						|
    right-join|right|r) select=right-join;;
 | 
						|
    left-only|lo) select=left-only;;
 | 
						|
    right-only|ro) select=right-only;;
 | 
						|
    *)
 | 
						|
        ewarn "$select: valeur de --select invalide. Elle sera ignorée"
 | 
						|
        select=none
 | 
						|
        ;;
 | 
						|
    esac
 | 
						|
    if [ "$postproc" == "auto" ]; then
 | 
						|
        case "$select" in
 | 
						|
        left-only) lkeepf="*"; rkeepf=""; postproc=1;;
 | 
						|
        right-only) lkeepf=""; rkeepf="*"; postproc=1;;
 | 
						|
        *) lkeepf="*"; rkeepf="*"; postproc=;;
 | 
						|
        esac
 | 
						|
    fi
 | 
						|
    if [ -n "$postproc" ]; then
 | 
						|
        if [ "$lkeepf" == "--NOT-SET--" ]; then
 | 
						|
            case "$select" in
 | 
						|
            right-only) lkeepf=;;
 | 
						|
            *) lkeepf="*";;
 | 
						|
            esac
 | 
						|
        fi
 | 
						|
        if [ "$rkeepf" == "--NOT-SET--" ]; then
 | 
						|
            case "$select" in
 | 
						|
            left-only) rkeepf=;;
 | 
						|
            *) rkeepf="*";;
 | 
						|
            esac
 | 
						|
        fi
 | 
						|
        local -a tmpfields fields
 | 
						|
        local field src dest
 | 
						|
        if [ -n "$lcopyf" ]; then
 | 
						|
            array_split tmpfields "$lcopyf" ,
 | 
						|
            fields=()
 | 
						|
            for field in "${tmpfields[@]}"; do
 | 
						|
                splitpair "$field" dest src
 | 
						|
                [ -n "$src" ] || src="$dest"
 | 
						|
                array_add fields "$dest:$src"
 | 
						|
            done
 | 
						|
            lcopyf="$(array_join fields ,)"
 | 
						|
        fi
 | 
						|
        if [ -n "$rcopyf" ]; then
 | 
						|
            array_split tmpfields "$rcopyf" ,
 | 
						|
            fields=()
 | 
						|
            for field in "${tmpfields[@]}"; do
 | 
						|
                splitpair "$field" dest src
 | 
						|
                [ -n "$src" ] || src="$dest"
 | 
						|
                array_add fields "$dest:$src"
 | 
						|
            done
 | 
						|
            rcopyf="$(array_join fields ,)"
 | 
						|
        fi
 | 
						|
        if [ "$lskipf" == "*" ]; then
 | 
						|
            lskipf=
 | 
						|
            lkeepf=
 | 
						|
        fi
 | 
						|
        if [ -n "$lskipf" ]; then
 | 
						|
            [ "$lkeepf" == "*" ] && lkeepf="$lheaders"
 | 
						|
            array_split fields "$lkeepf" ,
 | 
						|
            array_split tmpfields "$lskipf" ,
 | 
						|
            for field in "${tmpfields[@]}"; do
 | 
						|
                array_del fields "$field"
 | 
						|
            done
 | 
						|
            lkeepf="$(array_join fields ,)"
 | 
						|
        fi
 | 
						|
        if [ "$rskipf" == "*" ]; then
 | 
						|
            rskipf=
 | 
						|
            rkeepf=
 | 
						|
        fi
 | 
						|
        if [ -n "$rskipf" ]; then
 | 
						|
            [ "$rkeepf" == "*" ] && rkeepf="$rheaders"
 | 
						|
            array_split fields "$rkeepf" ,
 | 
						|
            array_split tmpfields "$rskipf" ,
 | 
						|
            for field in "${tmpfields[@]}"; do
 | 
						|
                array_del fields "$field"
 | 
						|
            done
 | 
						|
            rkeepf="$(array_join fields ,)"
 | 
						|
        fi
 | 
						|
    fi
 | 
						|
    local -a lcopyfs rcopyfs lkeepfs rkeepfs
 | 
						|
    array_split lcopyfs "$lcopyf" ,
 | 
						|
    array_split rcopyfs "$rcopyf" ,
 | 
						|
    array_split lkeepfs "$lkeepf" ,
 | 
						|
    array_split rkeepfs "$rkeepf" ,
 | 
						|
 | 
						|
    # fusionner les deux fichiers
 | 
						|
    local tmpmerged
 | 
						|
    ac_set_tmpfile tmpmerged "" __mergecsv_merged "" __MERGECSV_DEBUG
 | 
						|
    csort -s -k 1,$(($padlen + 1)) "$tmpleft" "$tmpright" >"$tmpmerged"
 | 
						|
 | 
						|
    <"$tmpmerged" awkrun -f \
 | 
						|
        padlen:int="$padlen" \
 | 
						|
        parse_headers:int="$parse_headers" ignore_case:int="$ignore_case" \
 | 
						|
        lheaderscsv="$lheaders" lkey="$lkey" lprefix="$lprefix" \
 | 
						|
        rheaderscsv="$rheaders" rkey="$rkey" rprefix="$rprefix" \
 | 
						|
        select="$select" postproc:int="$postproc" \
 | 
						|
        lcopyfs[@] rcopyfs[@] \
 | 
						|
        lkeepfs[@] rkeepfs[@] \
 | 
						|
        "$__AWKCSV_FUNCTIONS"'
 | 
						|
function lgeth(field,                nbfields, i) {
 | 
						|
  nbfields = array_len(lheaders)
 | 
						|
  if (int(field) == field) {
 | 
						|
    field = int(field)
 | 
						|
    if (field >= 1 && field <= nbfields) return field
 | 
						|
    else return 0
 | 
						|
  }
 | 
						|
  field = tolower(field)
 | 
						|
  for (i = 1; i <= nbfields; i++) {
 | 
						|
    if (field == tolower(lheaders[i])) {
 | 
						|
      return i
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return 0
 | 
						|
}
 | 
						|
function rgeth(field,                nbfields, i) {
 | 
						|
  nbfields = array_len(rheaders)
 | 
						|
  if (int(field) == field) {
 | 
						|
    field = int(field)
 | 
						|
    if (field >= 1 && field <= nbfields) return field
 | 
						|
    else return 0
 | 
						|
  }
 | 
						|
  field = tolower(field)
 | 
						|
  for (i = 1; i <= nbfields; i++) {
 | 
						|
    if (field == tolower(rheaders[i])) {
 | 
						|
      return i
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return 0
 | 
						|
}
 | 
						|
function copyf(lfields, rfields,        i, fs, vs, l, r) {
 | 
						|
  for (i = 1; i <= lcopyfs_count; i++) {
 | 
						|
    fs = lcopyfs[i]
 | 
						|
    match(fs, /(.*):(.*)/, vs)
 | 
						|
    l = vs[1]; l = lgeth(l)
 | 
						|
    r = vs[2]; r = rgeth(r)
 | 
						|
    if (l && r) {
 | 
						|
      lfields[l] = rfields[r]
 | 
						|
    }
 | 
						|
  }
 | 
						|
  for (i = 1; i <= rcopyfs_count; i++) {
 | 
						|
    fs = rcopyfs[i]
 | 
						|
    match(fs, /(.*):(.*)/, vs)
 | 
						|
    l = vs[2]; l = lgeth(l)
 | 
						|
    r = vs[1]; r = rgeth(r)
 | 
						|
    if (l && r) {
 | 
						|
      rfields[r] = lfields[l]
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
function keepf(lfields, rfields,        i) {
 | 
						|
  for (i = lskipfs_count; i >= 1; i--) {
 | 
						|
    array_deli(lfields, lskipfs[i])
 | 
						|
  }
 | 
						|
  for (i = rskipfs_count; i >= 1; i--) {
 | 
						|
    array_deli(rfields, rskipfs[i])
 | 
						|
  }
 | 
						|
}
 | 
						|
function printmerged(lline, rline, nocopy,        linecsv, tmplinecsv) {
 | 
						|
  if (lline != "") array_parsecsv(lfields, lline, array_len(lheaders))
 | 
						|
  else array_newsize(lfields, array_len(lheaders))
 | 
						|
  if (rline != "") array_parsecsv(rfields, rline, array_len(rheaders))
 | 
						|
  else array_newsize(rfields, array_len(rheaders))
 | 
						|
  if (postproc) {
 | 
						|
    if (!nocopy) copyf(lfields, rfields)
 | 
						|
    keepf(lfields, rfields)
 | 
						|
  }
 | 
						|
  linecsv = array_formatcsv(lfields)
 | 
						|
  tmplinecsv = array_formatcsv(rfields)
 | 
						|
  if (tmplinecsv != "") {
 | 
						|
    if (linecsv != "") linecsv = linecsv ","
 | 
						|
    linecsv = linecsv tmplinecsv
 | 
						|
  }
 | 
						|
  print linecsv
 | 
						|
}
 | 
						|
BEGIN {
 | 
						|
  if (parse_headers) {
 | 
						|
    array_parsecsv(lheaders, lheaderscsv)
 | 
						|
    lheaders_count = array_len(lheaders)
 | 
						|
    if (lprefix != "") {
 | 
						|
      for (i = 1; i <= lheaders_count; i++) {
 | 
						|
        lheaders[i] = lprefix lheaders[i]
 | 
						|
      }
 | 
						|
      lheaderscsv = array_formatcsv(lheaders)
 | 
						|
    }
 | 
						|
    lkey = lgeth(lkey)
 | 
						|
    if (!lkey) lkey = 1
 | 
						|
 | 
						|
    array_parsecsv(rheaders, rheaderscsv)
 | 
						|
    rheaders_count = array_len(rheaders)
 | 
						|
    if (rprefix != "") {
 | 
						|
      for (i = 1; i <= rheaders_count; i++) {
 | 
						|
        rheaders[i] = rprefix rheaders[i]
 | 
						|
      }
 | 
						|
      rheaderscsv = array_formatcsv(rheaders)
 | 
						|
    }
 | 
						|
    rkey = rgeth(rkey)
 | 
						|
    if (!rkey) rkey = 1
 | 
						|
  }
 | 
						|
  LEFT = 1
 | 
						|
  RIGHT = 2
 | 
						|
  hasleft = 0
 | 
						|
 | 
						|
  # quelle sélection effectuer?
 | 
						|
  selectjoin = select ~ /none|inner-join|left-join|right-join/
 | 
						|
  selectleft = select ~ /none|left-join|left-only/
 | 
						|
  selectright = select ~ /none|right-join|right-only/
 | 
						|
 | 
						|
  # liste des indexes de champs a supprimer
 | 
						|
  array_new(lskipfs)
 | 
						|
  if (!in_array("*", lkeepfs)) {
 | 
						|
    for (i = 1; i <= lheaders_count; i++) {
 | 
						|
      field = lheaders[i]
 | 
						|
      if (!in_array(field, lkeepfs)) {
 | 
						|
        fieldi = lgeth(field)
 | 
						|
        if (i != 0) array_add(lskipfs, fieldi)
 | 
						|
      }
 | 
						|
    }
 | 
						|
    asort(lskipfs)
 | 
						|
  }
 | 
						|
  lskipfs_count = array_len(lskipfs)
 | 
						|
 | 
						|
  array_new(rskipfs)
 | 
						|
  if (!in_array("*", rkeepfs)) {
 | 
						|
    for (i = 1; i <= rheaders_count; i++) {
 | 
						|
      field = rheaders[i]
 | 
						|
      if (!in_array(field, rkeepfs)) {
 | 
						|
        fieldi = rgeth(field)
 | 
						|
        if (i != 0) array_add(rskipfs, fieldi)
 | 
						|
      }
 | 
						|
    }
 | 
						|
    asort(rskipfs)
 | 
						|
  }
 | 
						|
  rskipfs_count = array_len(rskipfs)
 | 
						|
 | 
						|
  if (parse_headers) {
 | 
						|
    # afficher les en-têtes après traitement de lkeepfs et rkeepfs, parce que
 | 
						|
    # printmerged() a besoin de lskipfs et rskipfs
 | 
						|
    printmerged(lheaderscsv, rheaderscsv, 1)
 | 
						|
  }
 | 
						|
}
 | 
						|
function readleft() {
 | 
						|
  lprefix = substr($0, 1, padlen)
 | 
						|
  lwhich = substr($0, padlen + 1, 1)
 | 
						|
  if (lwhich == "1") lwhich = LEFT; else lwhich = RIGHT
 | 
						|
  lline = substr($0, padlen + 2)
 | 
						|
  hasleft = 1
 | 
						|
}
 | 
						|
function readright() {
 | 
						|
  rprefix = substr($0, 1, padlen)
 | 
						|
  rwhich = substr($0, padlen + 1, 1)
 | 
						|
  if (rwhich == "1") rwhich = LEFT; else rwhich = RIGHT
 | 
						|
  rline = substr($0, padlen + 2)
 | 
						|
}
 | 
						|
function right2left() {
 | 
						|
  lprefix = rprefix
 | 
						|
  lwhich = rwhich
 | 
						|
  lline = rline
 | 
						|
  hasleft = 1
 | 
						|
}
 | 
						|
!hasleft {
 | 
						|
  readleft()
 | 
						|
  next
 | 
						|
}
 | 
						|
{
 | 
						|
  readright()
 | 
						|
  if (lprefix == rprefix && lwhich == LEFT && rwhich == RIGHT) {
 | 
						|
    if (selectjoin) printmerged(lline, rline)
 | 
						|
    hasleft = 0
 | 
						|
    next
 | 
						|
  } else {
 | 
						|
    if (lwhich == LEFT && selectleft) {
 | 
						|
      printmerged(lline, "")
 | 
						|
    } else if (lwhich == RIGHT && selectright) {
 | 
						|
      printmerged("", lline)
 | 
						|
    }
 | 
						|
    right2left()
 | 
						|
    next
 | 
						|
  }
 | 
						|
}
 | 
						|
END {
 | 
						|
  if (hasleft) {
 | 
						|
    if (lwhich == LEFT && selectleft) {
 | 
						|
      printmerged(lline, "")
 | 
						|
    } else if (lwhich == RIGHT && selectright) {
 | 
						|
      printmerged("", lline)
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
'
 | 
						|
 | 
						|
    ac_clean "$tmpleft" "$tmpright" "$tmpmerged"
 | 
						|
    return 0
 | 
						|
}
 | 
						|
 | 
						|
function cmergecsv() { LANG=C lmergecsv "$@"; }
 | 
						|
function mergecsv() { LANG=C lmergecsv "$@"; }
 | 
						|
 | 
						|
################################################################################
 | 
						|
 | 
						|
__SORTCSV_HELP="\
 | 
						|
Trier un fichier csv sur la valeur d'un champ
 | 
						|
 | 
						|
--skip nblines
 | 
						|
    Sauter nblines au début du flux
 | 
						|
-h, --parse-headers
 | 
						|
    Lire la liste des champs à partir de la première ligne non ignorée des flux.
 | 
						|
    Si cette option est spécifiée (ce qui est le cas par défaut), le champ
 | 
						|
    spécifié avec l'option -k peut être le nom effectif du champ. Sinon, le
 | 
						|
    champ ne peut être que numérique.
 | 
						|
--numkeys
 | 
						|
    Ne pas analyser la première ligne pour les noms des champs. Les champs
 | 
						|
    spécifiés ne peuvent être que numériques.
 | 
						|
-k, --key FIELD
 | 
						|
    Spécifier le champ utilisé pour le tri. Si --parse-headers n'est pas
 | 
						|
    spécifié, cette valeur doit être numérique. La valeur par défaut est 1.
 | 
						|
--no-headers
 | 
						|
    Ne pas afficher les en-têtes en sortie.
 | 
						|
 | 
						|
-n, --numeric-sort
 | 
						|
    Comparer selon la valeur numérique du champ
 | 
						|
-i, -f, --ignore-case
 | 
						|
    Comparer sans tenir compte de la casse
 | 
						|
-r, --reverse
 | 
						|
    Inverser l'ordre de tri
 | 
						|
-s, --stable
 | 
						|
    Stabiliser le tri en inhibant la comparaison de dernier recours
 | 
						|
-u, --unique
 | 
						|
    Ne garder que la première occurence de plusieurs entrées identiques
 | 
						|
    rencontrées. Note: la correspondance se fait sur toute l'entrée, pas
 | 
						|
    uniquement sur la valeur de la clé.
 | 
						|
-o, --output OUTPUT
 | 
						|
    Ecrire le résultat dans OUTPUT au lieu de la sortie standard"
 | 
						|
 | 
						|
: "${__SORTCSV_DEBUG:=}"
 | 
						|
function lsortcsv() {
 | 
						|
    # Trier le fichier csv $1. La clé du tri est spécifiée par l'option -k et
 | 
						|
    # vaut 1 par défaut. Les valeurs des clés ne doivent pas faire plus de 64
 | 
						|
    # caractères de long.
 | 
						|
    eval "$(utools_local)"
 | 
						|
    local skip=0 parse_headers=auto key=1 show_headers=1
 | 
						|
    local numeric_sort= ignore_case= reverse_sort= stable_sort= unique_sort= output=
 | 
						|
    parse_opts "${PRETTYOPTS[@]}" \
 | 
						|
        --skip: skip= \
 | 
						|
        -h,--parse-headers parse_headers=1 \
 | 
						|
        --numkeys parse_headers= \
 | 
						|
        -k:,--key: key= \
 | 
						|
        --no-headers show_headers= \
 | 
						|
        --show-headers show_headers=1 \
 | 
						|
        -n,--numeric-sort numeric_sort=1 \
 | 
						|
        -i,-f,--ignore-case ignore_case=1 \
 | 
						|
        -r,--reverse reverse_sort=1 \
 | 
						|
        -s,--stable stable_sort=1 \
 | 
						|
        -u,--unique unique_sort=1 \
 | 
						|
        -o:,--output: output= \
 | 
						|
        @ args -- "$@" && set -- "${args[@]}" || die "$args"
 | 
						|
 | 
						|
    local input="$1"
 | 
						|
    if [ -z "$input" -o "$input" == "-" ]; then
 | 
						|
        input=/dev/stdin
 | 
						|
    elif [ ! -f "$input" ]; then
 | 
						|
        eerror "$input: fichier introuvable"
 | 
						|
        return 1
 | 
						|
    fi
 | 
						|
 | 
						|
    local padding="----------------------------------------------------------------"
 | 
						|
    local padlen=${#padding}
 | 
						|
    local headers
 | 
						|
 | 
						|
    local -a tmpfiles
 | 
						|
 | 
						|
    [ "$parse_headers" == "auto" ] && parse_headers=1
 | 
						|
    if [ -n "$parse_headers" -a -z "$headers" ]; then
 | 
						|
        if [ "$input" == /dev/stdin ]; then
 | 
						|
            # Si on lit depuis stdin, il faut faire une copie du flux dans un
 | 
						|
            # fichier temporaire pour calculer les en-têtes
 | 
						|
            local tmpinput
 | 
						|
            ac_set_tmpfile tmpinput "" __sortcsv_input0 "" __SORTCSV_DEBUG
 | 
						|
            array_add tmpfiles "$tmpinput"
 | 
						|
            cat >"$tmpinput"
 | 
						|
            input="$tmpinput"
 | 
						|
        fi
 | 
						|
        headers="$(<"$input" awkrun skip:int="$skip" 'NR <= skip { next } { print; exit }')"
 | 
						|
    fi
 | 
						|
 | 
						|
    # faire le fichier de travail
 | 
						|
    local tmpinput
 | 
						|
    ac_set_tmpfile tmpinput "" __sortcsv_input "" __SORTCSV_DEBUG
 | 
						|
    array_add tmpfiles "$tmpinput"
 | 
						|
    <"$input" >"$tmpinput" awkrun -f \
 | 
						|
        padding="$padding" padlen:int="$padlen" \
 | 
						|
        skip:int="$skip" parse_headers:int="$parse_headers" \
 | 
						|
        key="$key" \
 | 
						|
        "$__AWKCSV_FUNCTIONS"'
 | 
						|
NR <= skip { next }
 | 
						|
parse_headers && do_once("parse-headers") {
 | 
						|
  array_parsecsv(HEADERS, $0)
 | 
						|
  key = geth(key)
 | 
						|
  if (!key) key = 1
 | 
						|
  next
 | 
						|
}
 | 
						|
{
 | 
						|
  oline = $0
 | 
						|
  parsecsv($0)
 | 
						|
  keyvalue = substr($key, 1, padlen)
 | 
						|
  print keyvalue substr(padding, 1, padlen - length(keyvalue)) oline
 | 
						|
}
 | 
						|
'
 | 
						|
 | 
						|
    # trier le fichier de travail
 | 
						|
    local tmpsorted
 | 
						|
    ac_set_tmpfile tmpsorted "" __sortcsv_sorted "" __SORTCSV_DEBUG
 | 
						|
    array_add tmpfiles "$tmpsorted"
 | 
						|
    args=(# arguments de sort
 | 
						|
        ${numeric_sort:+-n} ${ignore_case:+-f}
 | 
						|
        ${reverse_sort:+-r} ${stable_sort:+-s}
 | 
						|
        ${unique_sort:+-u}
 | 
						|
    )
 | 
						|
    csort -k 1,$(($padlen + 1)) "${args[@]}" <"$tmpinput" >"$tmpsorted"
 | 
						|
 | 
						|
    # résultat
 | 
						|
    [ -n "$output" ] || output=/dev/stdout
 | 
						|
    <"$tmpsorted" >"$output" awkrun -f \
 | 
						|
        padlen:int="$padlen" \
 | 
						|
        headerscsv="$headers" show_headers:int="$show_headers" \
 | 
						|
        '
 | 
						|
BEGIN { if (show_headers) print headerscsv }
 | 
						|
{ print substr($0, padlen + 1) }
 | 
						|
'
 | 
						|
 | 
						|
    ac_clean "${tmpfiles[@]}"
 | 
						|
    return 0
 | 
						|
}
 | 
						|
 | 
						|
function csortcsv() { LANG=C lsortcsv "$@"; }
 | 
						|
function sortcsv() { LANG=C lsortcsv "$@"; }
 |