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.csv.List.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입력 파일을 한 줄씩 읽고 -e여기에 전달된 스크립트를 적용하는 것을 의미합니다.
  • $k{$F[1]}=$F[2]%k: 각 행의 두 번째 필드가 키이고 값이 세 번째 필드인 곳이 채워집니다 . 이는 관련이 있을 model-list.csv뿐 아니라 running 에도 적용됩니다 List.csv. List.csv두 번째 필드로도 나타나는 8번째 필드가 포함되지 않는 한 이 필드는 무시해도 됩니다.model-list.csv
  • next if $#F < 4: 이 행에 필드가 4개 미만인 경우 다음 행을 읽어보세요. 이는 print다음 줄이 인쇄되지 않기 때문입니다.model-list.csv
  • s/$F[7]/$k{$F[7]}/; print: 현재 줄의 8번째 필드를 해시에 저장된 내용으로 바꾸고 %k해당 줄을 인쇄합니다.

관련 정보