Substituindo os valores em um arquivo pelos valores em outro arquivo no bash

4

Estou recebendo um nome de arquivo csv List.csv no seguinte formato:

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

Se você observar a coluna MODEL , ela está exibindo alguns valores que não interpretam o nome do modelo. Eu criei outro arquivo, model-list.csv , que contém esses valores e seus nomes de modelo correspondentes. Parece algo como:

Manufacturer,Value,Model Name
Lenovo, 4089AZ8, ThinkCentre
Lenovo, 4089A76, ThinkCentre
HP, Z800, HP Z800 Workstation

Eu quero que os valores no arquivo List.csv sejam substituídos pelo nome do modelo correspondente presente em model-list.csv . Como há mais de 2900 itens no arquivo List.csv e cerca de 150 itens no arquivo model-list.csv , eu estava planejando fazer isso usando um script bash, que é o seguinte:

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

Quando o script acima é executado, o "$outfile" contém quase 40-50 entradas adicionais em comparação com o List.csv .

Algo errado com o roteiro?

    
por Mandar Shinde 25.04.2014 / 13:35

3 respostas

7

Você pode usar awk para isso:

awk -F',|, ' 'NR==FNR{a[$2]=$3} NR>FNR{$8=a[$8];print}' OFS=',' "$file2" "$file1"

Isso lê model-list.csv, armazenando todos os modelos e suas descrições em uma matriz indexada por string (por exemplo, a["Z800"] == "HP Z800 Workstation" ). Em seguida, ele lê os dados da lista, substituindo cada modelo pela string de descrição da matriz.

Explicação:

  • -F',|, ' - define o separador de campos usando um padrão regex, neste caso o separador de campos será uma vírgula única ou uma única vírgula e um único espaço.
  • NR==FNR{a[$2]=$3} - NR é uma variável interna awk que registra o número total de linhas lidas desde o início do programa. O FNR é similar, mas mantém o controle do número de linhas do arquivo atual que foram lidas. Portanto, NR==FNR é um idioma do awk que significa "se este for o primeiro arquivo a ser lido", e a ação associada é a[$2]=$3 , que salva o valor do campo 3 no array a , com o índice de strings sendo definido para o valor do campo 2.
  • NR>FNR{$8=a[$8];print}' - semelhante ao anterior, mas desta vez opera somente em arquivos diferentes do primeiro a ser lido. Para cada linha, usamos o valor do campo 8 como o índice para procurar o valor na matriz e, em seguida, reatribuímos o campo 8 ao valor da matriz. Finalmente, toda a linha é impressa.
  • OFS=',' "$file2" "$file1" - define o separador do campo de saída para uma vírgula (o padrão é o espaço) e, em seguida, lê dois arquivos na ordem especificada.
por 25.04.2014 / 14:56
2

No bash, assumindo uma versão bash > = 4, você pode fazer isso com muita facilidade usando matrizes associativas :

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

Advertências:

  1. Isso falhará na primeira linha do List.csv específico que você postou porque model-list.csv tem Model Name em que List.csv tem Model . Isso significa que não haverá correspondência para ${models[$model]} na primeira linha. Você pode corrigir isso editando o cabeçalho de um dos arquivos para que os nomes dos campos sejam idênticos ou usando esta versão:

    #!/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. Isso pressupõe que seu arquivo é tão simples quanto você mostra, que os campos todos são definidos por vírgulas e que nenhum campo pode conter vírgulas.

Em Perl, isso poderia ser feito de maneira muito mais simples:

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 

Explicação

  • -F define o delimitador de campo (aqui um , seguido por 0 ou mais caracteres de espaço em branco) que é usado com -a , que divide automaticamente cada linha de entrada na matriz @F .
  • -l ativa a remoção automática de \n no final de cada linha e também adiciona uma \n implícita a cada instrução print .
  • -n significa ler o arquivo de entrada linha a linha e aplicar qualquer script que tenha passado com -e para ele.
  • $k{$F[1]}=$F[2] : preenche o %k onde o segundo campo de cada linha é a chave e o valor é o terceiro campo. Isso só é relevante para o model-list.csv , mas também será executado para List.csv . Isso pode ser ignorado com segurança, contanto que List.csv nunca contenha um oitavo campo que também esteja presente como um segundo campo em model-list.csv
  • next if $#F < 4 : leia a próxima linha se esta tiver menos de 4 campos. Isso ocorre porque o% final print não imprime as linhas de model-list.csv
  • s/$F[7]/$k{$F[7]}/; print : substitua o oitavo campo da linha atual pelo que estiver armazenado no hash %k desse campo e imprima a linha.
por 26.04.2014 / 02:33
2

Algumas notas:

  • Bash é uma linguagem terrível para emulação de banco de dados. Tem certeza de que não é possível usar um banco de dados relacional para isso?
  • Evite usos inúteis de cat . Você pode fazer grep ",$ModelNo," "$file1" .
  • Você pode fazer while IFS=, read -r _ ModelNo ModelName _ para evitar as linhas awk .
  • No Bash, você pode fazer my_command <<< "$variable" em vez de echo "$variable" | my_command .
  • Você deve usar $(my_command) em vez de 'my_command' para facilitar a leitura.
  • grep -F pesquisará sequências literais.
  • Você pode verificar o código de saída de grep para ver se encontrou algo. Isso deve ser mais rápido do que verificar o tamanho do arquivo.
por 25.04.2014 / 13:55