我有一个 csv 文件,其命名List.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
.由于文件中有 2900 多个项目,文件List.csv
中有大约 150 个项目model-list.csv
,我计划使用 bash 脚本来实现此目的,如下所示:
#!/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 类似,但记录了行数当前文件已读过的内容。 awk 习惯用法也是如此NR==FNR
,意思是“如果这是要读取的第一个文件”,相关操作是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")
注意事项:
List.csv
这将在您发布的 特定内容的第一行失败,因为model-list.csv
hasModel Name
whereList.csv
hasModel
。这意味着${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")
这假设您的文件如您所显示的那样简单,即全部字段由逗号定义,并且任何字段都不能包含逗号。
在 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
设置字段分隔符(此处 a,
后跟 0 个或多个空白字符),用于-a
自动将每个输入行拆分到@F
数组中。-l
打开自动删除\n
每行末尾的 ,并\n
为每个print
语句添加隐式。-n
意味着逐行读取输入文件并应用传递给-e
它的任何脚本。$k{$F[1]}=$F[2]
:这会填充 has%k
,其中每行的第二个字段是键,值是第三个字段。这仅与 相关,model-list.csv
但也将针对 运行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
,并打印该行。