我有两个 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 names
true 。$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 规则来引用嵌入字符等,则需要这样做。;
上面的管道将
- 根据用户名连接两个文件(空格很重要)。
- 提取被锁定用户的数据(此时
0
遗嘱已更改为False
管道中的这一点)。 - 提取电子邮件地址。
- 删除 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