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")
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 Name
où List.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")
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
-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:
cat
. Vous pouvez faire grep ",$ModelNo," "$file1"
. while IFS=, read -r _ ModelNo ModelName _
pour éviter les lignes awk
. my_command <<< "$variable"
au lieu de echo "$variable" | my_command
echo "$variable" | my_command
. $(my_command)
au lieu de `my_command`
pour la lisibilité. grep -F
searchra les strings littérales. grep
pour voir s'il a trouvé quelque chose. Cela devrait être plus rapide que la vérification de la taille du file.