web-dev-qa-db-ja.com

あるファイルの値を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.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の追加エントリが含まれます。

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

6
Mandar Shinde

これには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つのファイルを読み込みます。
9
Josh Jolly

いくつかのメモ:

  • 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の終了コードをチェックして、何かが見つかったかどうかを確認できます。ファイルサイズを確認するよりも高速です。
4
l0b0

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.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") 
    
  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つ未満の場合、次の行を読み取ります。これは、最後のprintmodel-list.csvの行を出力しないためです。
  • s/$F[7]/$k{$F[7]}/; print:現在の行の8番目のフィールドを、そのフィールドの%kハッシュに格納されているものに置き換え、行を出力します。
3
terdon