bashであるファイルの値を別のファイルの値に置き換える

bashであるファイルの値を別のファイルの値に置き換える

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.csvList.csvmodel-list.csv

#!/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

スクリプトに問題がありますか?

答え1

次の目的で使用できます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は似ていますが、行数を記録します。現在のファイル読んだこと。NR==FNR「これが最初に読み取るファイルの場合」を意味するawkイディオムの場合も同様です。a[$2]=$3フィールド3の値を配列に格納し、a文字列インデックスをフィールド2の値に設定します。
  • NR>FNR{$8=a[$8];print}'- 以前と似ていますが、今回は最初に読み取ったファイルではなく、ファイルにのみ機能します。各行について、フィールド 8 の値をインデックスとして使用して配列の値を検索し、フィールド 8 を配列値に再割り当てします。最後に、行全体が印刷されます。
  • OFS=',' "$file2" "$file1"- 出力フィールド区切り文字をコンマ(デフォルトは空白)に設定し、指定された順序で2つのファイルを読み込みます。

答え2

いくつかの注意:

  • Bashはデータベースシミュレーションにとってひどい言語です。このタスクにリレーショナルデータベースを使用できませんか?
  • 避ける役に立たない目的cat。あなたはできますgrep ",$ModelNo," "$file1"
  • while IFS=, read -r _ ModelNo ModelName _キューを避けることができますawk
  • Bashではmy_command <<< "$variable"echo "$variable" | my_command
  • 読みやすくするために代わりに使用する必要があります$(my_command)`my_command`
  • grep -Fリテラル文字列が検索されます。
  • 終了コードを確認して、grep何が見つかるかを確認できます。ファイルサイズを確認するよりも高速です。

答え3

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") 

指示:

  1. List.csvmodel-list.csvhas Model Namewhere List.csvhasなので、公開した特定のコンテンツの最初の行では失敗しますModel。これは${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") 
    
  2. これは、ファイルが示されているように単純であると仮定します。みんなフィールドはコンマで定義され、どのフィールドにもコンマを含めることはできません。


もちろん、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各入力行を配列に自動的に分割するために使用されるフィールド区切り文字(ここでは、,0個以上の空白文字が続く)を設定します。-a@F
  • -l\n各行末の自動削除をオンにし、\n各文にprint暗黙の内容を追加します。
  • -n入力ファイルを1行ずつ読み、-eここに渡されたスクリプトを適用することを意味します。
  • $k{$F[1]}=$F[2]%k:各行の2番目のフィールドがキーで、値が3番目のフィールドである場所が入力されます。これは関連しているmodel-list.csvだけでなく、ランニングにも当てはまりますList.csvList.csv2番目のフィールドとしても表示される8番目のフィールドが含まれていない限り、このフィールドは無視してかまいません。model-list.csv
  • next if $#F < 4:この行にフィールドが4つ未満の場合は、次の行をお読みください。これはprint、次の行が印刷されないためです。model-list.csv
  • s/$F[7]/$k{$F[7]}/; print:現在の行の8番目のフィールドをハッシュに保存されている内容に置き換えて、%kその行を印刷します。

関連情報