Bash: associa dados de dois arquivos csv

3

Eu tenho dois arquivos csv que contêm vários dados do usuário; eles compartilham um campo comum (nome de usuário).

file A:
username ; Fullname ; mail
Bob      ; Bob Hope ; [email protected]

file B:
username ; LastLogonTime  ; AccountStatus (locked=0 or unlocked=1)
Bob      ; 2018-10-01 etc.; 0

Para fins de auditoria, eu quero usar o Bash para fazer um loop em A, cruzar com B se a conta está bloqueada e, nesse caso, posso enviar o usuário para o endereço de e-mail em A

awk -F";"

me permite passar por cima A; isso é fácil - mas estou perdido quando tento fazer o loop de verificação cruzada sobre B.

    
por DavDav 02.10.2018 / 12:53

4 respostas

5

Usando awk , primeiro leia os nomes de usuário dos usuários cuja conta está bloqueada no segundo arquivo e, em seguida, extraia os endereços de email deles do primeiro arquivo (então espere que eles não precisem fazer login para ler seus emails ):

awk -F ';' 'NR == FNR && $NF == 0    { names[$1] }
            NR != FNR && $1 in names { print $NF }' B.csv A.csv

Isso pressupõe que cada nome de usuário tenha uma quantidade igual de espaço em branco ao redor deles em ambos os arquivos. Se não for assim, você pode usar -F ' *; *' para incluir quaisquer caracteres de espaço no delimitador que awk esteja usando. Ele também assume que não há caracteres ; incorporados nos dados.

NR é o número de registro (linha) do registro atual como um todo e FNR é o mesmo número, mas dentro do arquivo atual. Se NR == FNR , então estamos lendo o primeiro arquivo fornecido na linha de comando ( B.csv ). NF é o número de campos (colunas) no registro atual e $NF são os dados no último campo (e $1 são os dados no primeiro campo).

O código acima usa um array / hash associativo, names , digitado nos nomes de usuários bloqueados, lidos a partir do primeiro arquivo ( B.csv ). O $1 in names será verdadeiro se $1 for uma chave nessa matriz.

Colocando isso em um loop:

awk -F ';' 'NR == FNR && $NF == 0    { names[$1] }
            NR != FNR && $1 in names { print $NF }' B.csv A.csv |
while read addr; do
    printf 'Would send an email to "%s"\n' "$addr"
    #mail -s 'Account locked' "$addr" <template-email.txt
done

Ou algo nesse sentido. Ler os endereços de e-mail dessa forma no loop excluiria qualquer espaço em branco ao redor deles. O loop acima não envia e-mails, mas imprime os endereços que precisam ser enviados. Remova o # antes de mail (e escreva algum e-mail de formulário em template-email.txt ) para realmente enviar um e-mail (mas você pode querer fazê-lo de maneira diferente).

Usando csvkit :

csvjoin -d ';' -c 1 A.csv B.csv |
csvgrep -c 5 -m False |
csvcut -S -c 3 | sed 1d

O CSVkit fornece ferramentas de análise de CSV para trabalhar com arquivos CSV. Isso seria necessário se os dados CSV não forem "simples", ou seja, se usar regras CSV para citar os caracteres ; incorporados, etc. O canal acima será

  1. Junte os dois arquivos nos nomes de usuários (os espaços em branco são significativos).
  2. Extraia os dados dos usuários que estão bloqueados (o 0 será alterado para False neste ponto do pipeline).
  3. Extraia os endereços de e-mail.
  4. Remova o cabeçalho CSV (usando o último comando sed ).
por 02.10.2018 / 14:14
-1

Primeiro, se você tiver espaços ao redor do delimitador, precisará removê-los no script, como o @RoVo disse. Os comandos sed farão isso por você.

Em segundo lugar, você basicamente quer ter um loop while lendo em cada linha a partir do arquivo fixo A, e obtendo o nome de usuário e endereço de e-mail e, opcionalmente, o nome completo do usuário. Você então quer verificar o status daquele usuário no arquivo fixoB.

Algo como o pequeno loop a seguir você deve começar:

#!/bin/bash

# Remove spaces around delimiter
sed -i.fixed 's/[       ]*\;[   ]*/\;/g' fileA
sed -i.fixed 's/[       ]*\;[   ]*/\;/g' fileB

# Read in each line from the fixed fileA
while read l; do

  # Skip the header line
  [[ ${l} =~ ^username ]] && continue

  # Get the user from the line that was read in.
  u=$(echo ${l} | awk -F\; '{print $1}')

  # Get the lock status for that user from the fixed fileB
  l=$(awk -F\; -v u=${u} '{if ($1 == u) {print $3}}' fileB.fixed)

  # Echo out the 2 fields.
  echo ${u}=${l}

  # Other stuff can go here.
done <fileA.fixed

exit 0

Espero que isso ajude

    
por 02.10.2018 / 14:09
-1

Use uma ferramenta especializada para executar tarefas como esta (também: um banco de dados):

# Remove spaces around the field separator
sed -i.fixed 's/ *\; */\;/g' a
sed -i.fixed 's/ *\; */\;/g' b

# Add to sqlite database
echo -e '.separator ";"\n.import a.fixed a' | sqlite3 db.sqlite
echo -e '.separator ";"\n.import b.fixed b' | sqlite3 db.sqlite

# Select whatever you need
echo -e 'select a.username,a.mail,b."AccountStatus (locked=0 or unlocked=1)" from a join b on a.username = b.username;' | sqlite3 db.sqlite

awk solution:

users=( $(awk -F";" 'NR>1{print $1";"$3}' a) )
for u in "${users[@]}"; do
    username=$(echo "$u" | cut -d';' -f1)
    mail=$(echo "$u" | cut -d';' -f2)
    awk -v "u=$username" -v "m=$mail" -F';' 'NR>1 { if ($3 == 0) print "User "u" ("m") is locked"; }' b
done
    
por 02.10.2018 / 13:56
-1
#!/bin/bash 

cat fileA.txt | sed 1d | while IFS=';' read -r line; do #read fileA.txt starting with line #2
name=$(echo $line | awk '{print $1}') #find names in each line/column 1 of the table 
lock_status=$(grep $name fileB.txt | awk '{print $5}') # find lock/unlock status in fileB.txt

    if [[ "$lock_status" -eq 0 ]];then 

    echo "Locked: To mail the user : replace echo by the command mail";

        else

    echo "unlocked";
     fi
done
    
por 02.10.2018 / 14:22

Tags