Bash:连接两个 csv 文件中的数据

Bash:连接两个 csv 文件中的数据

我有两个 csv 文件,其中包含各种用户数据;他们共享一个共同的字段(用户名)。

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

出于审计目的,我想使用 Bash 循环 A,与 B 交叉检查帐户是否被锁定,在这种情况下,我可以将用户邮寄到 A 中的邮件地址。

awk -F";"

允许我跳过A;这很简单 - 但当我尝试对 B 进行交叉检查循环时我不知所措。

答案1

使用awk,首先从第二个文件中读取帐户被锁定的用户的用户名,然后从第一个文件中提取这些用户的电子邮件地址(然后希望他们不需要登录即可阅读他们的电子邮件):

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

这假设两个文件中每个用户名周围都有相同数量的空格。如果不是这样,您可以在使用的-F ' *; *'分隔符中包含任何空格字符awk。它还假设;数据中没有嵌入字符。

NR是当前记录整体的记录(行)号,并且FNR是相同的数字,但在当前文件内。如果NR == FNR,那么我们将从命令行 ( B.csv) 上给出的第一个文件中读取。 NF是当前记录中的字段(列)数,$NF是最后一个字段中的数据(并且$1是第一个字段中的数据)。

上面的代码使用关联数组/哈希,以names从第一个文件 ( ) 读取的锁定用户的用户名为键B.csv。如果是该数组中的键,则为$1 in namestrue 。$1

将其放入循环中:

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

或类似的规定。在循环中以这种方式读取电子邮件地址将删除它们周围的所有空格。上面的循环不发送电子邮件,但打印需要发送到的地址。删除#之前的内容mail(并在 中写入一些表单电子邮件template-email.txt)以实际发送电子邮件(但您可能想要以不同的方式执行此操作)。


使用csvkit:

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

CSVkit 提供了用于处理 CSV 文件的 CSV 解析工具。如果您的 CSV 数据不“简单”,即如果它使用 CSV 规则来引用嵌入字符等,则需要这样做。;上面的管道将

  1. 根据用户名连接两个文件(空格很重要)。
  2. 提取被锁定用户的数据(此时0遗嘱已更改为False管道中的这一点)。
  3. 提取电子邮件地址。
  4. 删除 CSV 标头(使用最后一个sed命令)。

答案2

使用专门的工具来执行这样的任务(也称为数据库):

# 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解决方案:

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

答案3

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

答案4

首先,如果分隔符周围确实有空格,则需要在脚本中删除它们,就像 @RoVo 所说。 sed 命令将为您完成此操作。

其次,您基本上希望有一个 while 循环读取固定 fileA 中的每一行,并获取用户名和电子邮件地址,以及可选的用户全名。然后您想要检查固定文件 B 中该用户的状态。

类似下面的小循环应该可以帮助您开始:

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

相关内容