次の形式のList.csv
という名前のcsvファイルがあります。
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
MODEL
列を見ると、モデルの名前を解釈しないいくつかの値が含まれています。これらの値と対応するモデル名を含む別のファイルmodel-list.csv
を作成しました。それは次のようになります:
Manufacturer,Value,Model Name
Lenovo, 4089AZ8, ThinkCentre
Lenovo, 4089A76, ThinkCentre
HP, Z800, HP Z800 Workstation
List.csv
ファイルの値を、model-list.csv
にある対応するモデル名に置き換えたい。 List.csv
ファイルには2900以上のアイテムがあり、model-list.csv
ファイルには約150のアイテムがあるため、次のようなbashスクリプトを使用してこれを実現することを計画していました。
#!/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"
上記のスクリプトを実行すると、"$outfile"
には、List.csv
と比較して、40〜50の追加エントリが含まれます。
スクリプトに問題がありますか?
これにはawk
を使用できます。
awk -F',|, ' 'NR==FNR{a[$2]=$3} NR>FNR{$8=a[$8];print}' OFS=',' "$file2" "$file1"
これは、model-list.csvを読み取り、すべてのモデルとその説明を文字列インデックス配列(例:a["Z800"] == "HP Z800 Workstation"
)。次に、リストデータを読み取り、各モデルを配列の説明文字列に置き換えます。
説明:
-F',|, '
-これは、正規表現パターンを使用してフィールド区切り記号を設定します。この場合、フィールド区切り記号は、単一のコンマ、または単一のコンマと単一のスペースのいずれかになります。NR==FNR{a[$2]=$3}
-NRはawk内部変数で、プログラムの開始以降に読み取られた行の総数を追跡します。 FNRも同様ですが、読み込まれたcurrent fileの行数を追跡します。そう NR==FNR
はawkイディオムで、「これが最初に読み取られるファイルの場合」であり、関連するアクションはa[$2]=$3
は、フィールド3の値を配列a
に保存します。文字列インデックスはフィールド2の値に設定されます。NR>FNR{$8=a[$8];print}'
-以前と同様ですが、今回は最初に読み取られるファイル以外のファイルのみを操作します。各行について、フィールド8の値をインデックスとして使用して配列内の値を検索し、次にフィールド8を配列値に再度割り当てます。最後に、行全体が印刷されます。OFS=',' "$file2" "$file1"
-出力フィールドの区切り文字をコンマ(デフォルトはスペース)に設定し、指定された順序で2つのファイルを読み込みます。いくつかのメモ:
cat
)の無用な使用は避けてください 。 _grep ",$ModelNo," "$file1"
_を実行できます。while IFS=, read -r _ ModelNo ModelName _
_を実行すると、awk
行を回避できます。my_command <<< "$variable"
_の代わりに_echo "$variable" | my_command
_を実行できます。`my_command`
_の代わりに$(my_command)
を使用する必要があります。grep -F
_はリテラル文字列を検索します。grep
の終了コードをチェックして、何かが見つかったかどうかを確認できます。ファイルサイズを確認するよりも高速です。Bashでは、bashのバージョンが4以上であると仮定すると、 連想配列 を使用してこれを非常に簡単に行うことができます。
#!/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")
List.csv
にはmodel-list.csv
があり、Model Name
にはModel
があるため、投稿した特定のList.csv
の1行目で失敗します。これは、最初の行で${models[$model]}
が一致しないことを意味します。これを修正するには、いずれかのファイルのヘッダーを編集してフィールド名が同じになるようにするか、このバージョンを代わりに使用します。
#!/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")
これは、ファイルが表示されているのと同じくらい単純で、allフィールドがコンマで定義されており、フィールドにコンマを含めることができないことを前提としています。
もちろん、Perlではこれをもっと簡単に行うことができます:
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
は、各入力行を自動的に,
配列に分割する-a
で使用されるフィールド区切り文字(ここでは@F
の後に0個以上の空白文字が続く)を設定します。-l
は、各行の終わりにある\n
の自動削除をオンにし、暗黙の\n
を各print
ステートメントに追加します。-n
は、入力ファイルを1行ずつ読み取り、-e
で渡されたスクリプトをそれに適用することを意味します。$k{$F[1]}=$F[2]
:これにより、has %k
が入力されます。各行の2番目のフィールドはキーで、値は3番目のフィールドです。これはmodel-list.csv
にのみ関連しますが、List.csv
に対しても実行されます。 List.csv
に、model-list.csv
の2番目のフィールドとしても存在する8番目のフィールドが含まれない限り、これは安全に無視できます。next if $#F < 4
:この行のフィールドが4つ未満の場合、次の行を読み取ります。これは、最後のprint
がmodel-list.csv
の行を出力しないためです。s/$F[7]/$k{$F[7]}/; print
:現在の行の8番目のフィールドを、そのフィールドの%k
ハッシュに格納されているものに置き換え、行を出力します。