Rlocation des valeurs d'un file par les valeurs d'un autre file dans bash

J'ai un file csv nommant List.csv au format suivant:

 Location,IP Address,Host Name,Domain,Domain Name, User Name,Manufacturer,Model,System Type, Serial Number, Operating System,RAM (GB),Processor Type,Processor Frequency H1,xx.xx.xx.xx,PC1,domain.com,DOMAIN,User1,LENOVO,4089AZ8,X86-based PC,L90RA96,Microsoft Windows 7 Professional ,2,Pentium(R) Dual-Core CPU E5800,3.20GHz H3,xx.xx.xx.xx,PC2,domain.com,DOMAIN,User2,LENOVO,4089AZ8,X86-based PC,L906W3P,Microsoft Windows 7 Professional ,2,Pentium(R) Dual-Core CPU E5800,3.20GHz H2,xx.xx.xx.xx,PC3,domain.com,DOMAIN,User3,LENOVO,4089A76,X86-based PC,L929410,Microsoft Windows 7 Professional ,2,Pentium(R) Dual-Core CPU E5400,2.70GHz H2,xx.xx.xx.xx,PC4,domain.com,DOMAIN,User4,Hewlett-Packard,Z800,x64-based PC,SGH007QT16,Microsoft Windows 7 Professional ,12,Intel(R) Xeon(R) CPU W5590,3.33GHz 

Si vous regardez la colonne MODEL , elle porte certaines valeurs qui n'interprètent pas le nom du model. J'ai créé un autre file model-list.csv qui porte l'information desdites valeurs et les noms de model correspondants. Cela ressemble à quelque chose comme:

 Manufacturer,Value,Model Name Lenovo, 4089AZ8, ThinkCentre Lenovo, 4089A76, ThinkCentre HP, Z800, HP Z800 Workstation 

Je veux que les valeurs dans le file List.csv soient remplacées par le nom de model correspondant présent dans model-list.csv . Comme il y a plus de 2900 éléments dans List.csv et environ 150 éléments dans le file model.csv , je prévoyais d'y parvenir en utilisant un script bash qui est le suivant:

 #!/bin/bash file1="List.csv" file2="model-list.csv" outfile="List_out.csv" stagingfile="List-staging.csv" rm -f "$outfile" "$stagingfile" while read line do ModelNo=`echo "$line"|awk -F',' '{print $2}'` ModelName=`echo "$line"|awk -F',' '{print $3}'` cat "$file1"|grep ",$ModelNo," > "$stagingfile" if [ -s "$stagingfile" ] then while read line1 do NewLine=`echo "$line1"|sed "s/,${ModelNo},/,${ModelName},/g"` echo "$NewLine" >> "$outfile" done < "$stagingfile" rm -f "$stagingfile" fi done < "$file2" 

Lorsque le script ci-dessus est exécuté, le "$outfile" contient près de 40 à 50 inputs supplémentaires par rapport au List.csv .

Quelque chose ne va pas avec le script?

Vous pouvez utiliser awk pour cela:

 awk -F',|, ' 'NR==FNR{a[$2]=$3} NR>FNR{$8=a[$8];print}' OFS=',' "$file2" "$file1" 

Ceci lit le file model-list.csv, stockant tous les templates et leurs descriptions dans un tableau indexé par des strings (par exemple, a["Z800"] == "HP Z800 Workstation" ). Ensuite, il lit datatables de la list, en remplaçant chaque model par la string de description du tableau.

Explication:

  • -F',|, ' – cela définit le séparateur de champs en utilisant un motif regex, dans ce cas, le séparateur de champs sera soit une simple virgule, soit une seule virgule et un seul espace.
  • NR==FNR{a[$2]=$3} – NR est une variable interne awk qui suit le nombre total de lignes lues depuis le début du programme. FNR est similaire, mais garde une trace du nombre de lignes du file en cours qui ont été lues. Donc NR==FNR est un idiome awk qui signifie "si c'est le premier file à lire", et l'action associée est a[$2]=$3 qui enregistre la valeur du champ 3 dans le tableau a , avec l'index de string étant fixé à la valeur du champ 2.
  • NR>FNR{$8=a[$8];print}' – semblable au précédent, mais cette fois-ci ne fonctionne que sur des files autres que le premier à lire. Pour chaque ligne, nous utilisons la valeur du champ 8 comme indice pour searchr la valeur dans le tableau, puis réaffectons le champ 8 à la valeur du tableau. Enfin, toute la ligne est imprimée.
  • OFS=',' "$file2" "$file1" – Définit le séparateur de champs de sortie à une virgule (par défaut est l'espace), puis lit dans 2 files dans l'ordre spécifié.

En bash, en supposant une version bash> = 4, vous pouvez le faire très facilement en utilisant des arrays associatifs :

 #!/usr/bin/env bash ## declare models as an associative array declare -A models ## read the 1st file, load the Value => Model pair ## pairs into the models array. Note that I'm setting bash's ## Input Field Separator ($IFS) to comma (,) and that I first pass ## the file through sed to remove the spaces after the commas. ## For more on why I'm using <() instead of a pipe, see ## http://stackoverflow.com/q/9985076/1081936 while IFS=, read -r man val mod; do models["$val"]="$mod" done < <(sed 's/, /,/g' "$1") ## Read the second file. I am defining 9 variables, 8 for ## the first 8 fields, up to the model and $rest for the rest of ## the fields, up to the end of the line. while IFS=',' read -r loc ip host dom dnam user manu model rest; do printf "%s,%s,%s,%s,%s,%s,%s,%s,%s\n" "$loc" "$ip" "$host" "$dom" \ "$dnam" "$user" "$manu" "${models[$model]}" "$rest"; done < <(sed 's/, /,/g' "$2") 

Avertissements:

  1. Cela échouera sur la première ligne de la List.csv spécifique List.csv vous avez posté car model-list.csv a le Model NameList.csv a le Model . Cela signifie qu'il n'y aura pas de correspondance pour ${models[$model]} sur la première ligne. Vous pouvez résoudre ce problème en modifiant l'en-tête de l'un des files afin que les noms de champs soient identiques ou en utilisant cette version à la place:

     #!/usr/bin/env bash declare -A models while IFS=, read -r man val mod; do models["$val"]="$mod" done < <(sed 's/, /,/g' "$1") ## Set up a counter to hold the line numbers c=0; while IFS=',' read -r loc ip host dom dnam user manu model rest; do ## Increment the line number (( c++ )); ## If this is the 1st line, print if [ "$c" -eq "1" ]; then printf "%s,%s,%s,%s,%s,%s,%s,%s,%s\n" "$loc" "$ip" "$host" "$dom" \ "$dnam" "$user" "$manu" "$model" "$rest"; else printf "%s,%s,%s,%s,%s,%s,%s,%s,%s\n" "$loc" "$ip" "$host" "$dom" \ "$dnam" "$user" "$manu" "${models[$model]}" "$rest"; fi done < <(sed 's/, /,/g' "$2") 
  2. Cela suppose que votre file est aussi simple que vous l'avez montré, que tous les champs sont définis par des virgules et qu'aucun champ ne peut contenir de virgules.


En Perl cela pourrait bien sûr être fait beaucoup plus simplement:

 perl -F',\s*' -lane '$k{$F[1]}=$F[2]; next if $#F < 4; s/$F[7]/$k{$F[7]}/; print' model-list.csv List.csv 

Explication

  • -F définit le délimiteur de champ (ici a , suivi de 0 ou plus de caractères blancs) qui est utilisé avec -a qui sépare automatiquement chaque ligne d'input dans le tableau @F .
  • -l active la suppression automatique du \n à la fin de chaque ligne et ajoute un \n à chaque instruction d' print .
  • -n signifie lire le file d'input ligne par ligne et appliquer tout script passé avec -e .
  • $k{$F[1]}=$F[2] : cela remplit le has %k où le 2ème champ de chaque ligne est la key et la valeur est le 3ème champ. Ceci n'est pertinent que pour le model-list.csv mais sera aussi exécuté pour List.csv . Cela peut être ignoré aussi longtime que List.csv ne contiendra jamais un 8ème champ qui est également présent en tant que 2ème champ dans model-list.csv
  • next if $#F < 4 : lit la ligne suivante si celle-ci contient less de 4 champs. Il en est ainsi car l' print finale print pas les lignes de model-list.csv
  • s/$F[7]/$k{$F[7]}/; print s/$F[7]/$k{$F[7]}/; print : remplacez le 8ème champ de la ligne courante par ce qui est stocké dans le hash %k pour ce champ et imprimez la ligne.

Quelques notes:

  • Bash est un langage terrible pour l'émulation de database. Êtes-vous sûr de ne pas pouvoir utiliser une database relationnelle pour cela?
  • Évitez les utilisations inutiles du cat . Vous pouvez faire grep ",$ModelNo," "$file1" .
  • Vous pouvez faire while IFS=, read -r _ ModelNo ModelName _ pour éviter les lignes awk .
  • Dans Bash, vous pouvez faire my_command <<< "$variable" au lieu de echo "$variable" | my_command echo "$variable" | my_command .
  • Vous devriez utiliser $(my_command) au lieu de `my_command` pour la lisibilité.
  • grep -F searchra les strings littérales.
  • Vous pouvez vérifier le code de sortie de grep pour voir s'il a trouvé quelque chose. Cela devrait être plus rapide que la vérification de la taille du file.