如何使用 bash 脚本将 csv 文件中的两个字段(而不是一个)打印到输出?

如何使用 bash 脚本将 csv 文件中的两个字段(而不是一个)打印到输出?

我过去没有经常使用 bash 来编写 bash 脚本,而我目前正在使用 bash 脚本进行阅读。该文件包含许多以 csv 格式存储的字段。下面的第一个脚本将收集文件中的所有 ip;然而,我也在努力收集知识产权另一个字段称为网络。。有谁知道我能实现这个目标吗?

files=`ls | grep data_batch_`
for file in ${files[@]}
do
  cat ${file} | cut -d , -f2 | grep -v "IP" > data_${file}
done

我尝试过添加布尔运算符,但没有成功。还尝试了更多管道。我不经常使用 bash,所以我可能会遗漏一些语法或不明白为什么这是不允许的?

    files=`ls | grep data_batch`
for file in ${files[@]}
do
  cat ${file} | cut -d , -f2 | cut -d, -f3 | grep -v "IP" && "Network" > data_${file}
done

由于某种原因,当我这样做时,它似乎会覆盖知识产权值与网络值,而不是同时存储它们。本质上,我想做的就是将两个字段而不是一个字段打印到一个文件中,但我不确定如何实现他的解决方案。任何提示也会有帮助。

我想要的输出是存储在文件中的 ip 地址值和网络值。目前我得到的只是IP。下面是所需的输出。

1.1.1.1
Network5

答案1

您的脚本存在很多问题:

files=`ls | grep data_batch_`
for file in ${files[@]}
do
  cat ${file} | cut -d , -f2 | grep -v "IP" > data_${file}
done
  1. 不要解析 ls

  2. 不要使用反引号。代替使用$()。它做同样的事情,但不会破坏引用并且可以嵌套。

  3. files您在循环中使用它for,就像它是一个数组一样,但它不是一个数组。您将其定义为标量字符串( 的输出ls | grep ...)。如果要定义数组,则需要使用括号,例如

    这定义files为一个字符串:

    $ files=$(echo 1 2 3)
    $ declare -p files
    declare -- files="1 2 3"
    

    虽然这将其定义为数组:

    $ files=( $(echo 1 2 3) )
    $ declare -p files
    declare -a files=([0]="1" [1]="2" [2]="3")
    

    或者,您可以使用mapfile(又名readarray):

     $ mapfile -t files < <(printf "%s\n" 1 2 3)
     $ declare -p files
     declare -a files=([0]="1" [1]="2" [2]="3")
    
  4. 用双引号引用您的变量扩展。使用花括号是不是引用的替代品。看为什么我的 shell 脚本会因为空格或其他特殊字符而卡住?$VAR 与 ${VAR} 以及引用或不引用出于原因。

  5. 在第二个脚本中,您将管道输出cut -d, -f2into cut -d, -f3。那是行不通的。

    第一个cut仅输出一个字段(字段 2)。第二个cut将输出完全相同,因为它的输入中只有一个字段(或者没有字段,因为没有逗号),而你告诉它输出不存在的字段 3. 尝试运行echo 1,2,3 | cut -d, -f2然后运行echo 1,2,3 | cut -d, -f2 | cut -d, -f3,你会看到两个命令的输出是相同的:2

    要使用 输出两个字段cut -f,请列出它们并用逗号分隔。例如:

    cut -d, -f2,3
    

    顺便说一句,您还可以使用 指定字段范围-,例如,如果您想输出字段 2 到 5,则可以使用:cut -d, -f2-5。看man cut

  6. 我不知道这是否是一个问题,但这是需要注意的事情。您的脚本将 stdout 重定向到与输入文件同名的输出文件,但前缀为data_.因此,如果您的输入文件是,data_batch_1.csv那么您的输出文件将为data_data_batch_1.csv.

    这可能正是你想要的,在这种情况下这不是问题 - 但这意味着如果你再次运行脚本,文件 glob 将匹配你的原始输入文件第一次运行生成的输出文件......导致文件名类似于data_data_data_batch_1.csv.您可能需要考虑对输出文件使用不同的命名约定,或者将它们写入不同的目录。


无论如何,这些都是问题所在。这里有一些解决方案......尝试更多类似这样的事情:

for file in *data_batch_*; do
  cut -d, -f2,3 "$file" | grep -v IP > "data_$file"
done

如果您确实想使用文件名数组,则可以使用mapfileandfind-print0.例如

mapfile -t -d '' files < <(find . -maxdepth 1 -type f -name '*data_batch_*' -print0)
for file in "${files[@]}"; do
   cut -d, -f2,3 "$file" | grep -v IP > "data_$file"
done

或者,您可以使用awk而不是cut

awk -F, -v OFS=, '$2$3 !~ /IP/ { print $2, $3 > "data_" FILENAME }' *data_batch_*

如果既不包含$2也不$3包含“IP”,则使用重定向到与当前文件名(awk 的变量)同名的文件的 stdout 打印它们FILENAME,并以字符串“data_”为前缀。

这将明显更快,因为它不必分叉cutgrep多次 - 对于它处理的每个文件一次。


最后,CSV 文件可以(并且经常)包含双引号字符串字段 - 并且这些引号字段可以包含逗号。可以使用 . 可靠地处理不带引号且不带逗号嵌入字段的简单逗号分隔文件cut。实际的 CSV 及其所有可选附加功能需要 CSV 解析器。您最好的选择是使用:

  1. 一种已经具有全功能 CSV 解析器的语言 - 例如perl具有文本::CSV模块并python包括一个数据集图书馆。

  2. 像这样的工具磨坊主或者csvkit

答案2

如果您有 awk 可用:

$ cat /tmp/abc
name1,0.0.0.0,NetworkName1
name2,0.4.2.3,NetworkName2
name3,0.1.43.5,NetworkName3

$ awk 'BEGIN { FS = "," } ;{printf $2","$3"\n"}' /tmp/abc
0.0.0.0,NetworkName1
0.4.2.3,NetworkName2
0.1.43.5,NetworkName3

所以在这种情况下,

for i in $(ls | grep -E ^test.*[.]csv$)
do
    cat $i | cut -d , -f2,3 >> testing.txt
done

可以变成

$ awk 'BEGIN { FS = "," } ;{printf $2","$3"\n"}' test*.csv > testing.txt

如果您经常进行结构化文本处理,那么投入一些时间学习 awk 将会是有益的。

答案3

我在以下方面有一些运气:

目录内容:

$ ls
test.csv  test1.csv  test3csv test5.txt

其中每个文件都包含如下所示的一些行:

name1,0.0.0.0,NetworkName1
name2,0.4.2.3,NetworkName2
name3,0.1.43.5,NetworkName3

剧本:


for i in $(ls | grep -E ^test.*[.]csv$)
do
    cat $i | cut -d , -f2,3 >> testing.txt
done

这将获取所有以 test 开头、以 结尾的文件.csv,删除字段二和字段三,并将它们附加到文件 中testing.txt

之后的输出文件看起来像这样

0.0.0.0,NetworkName1
0.4.2.3,NetworkName2
0.1.43.5,NetworkName3

在单独的行上列出每个 IP 地址和每个网络名称。

在脚本中,您看到输出文件中的内容被覆盖的原因是因为您当前正在使用>运算符,它会覆盖文件中的所有内容,而您可能想要的是运算>>符,它将文本附加到文件的末尾文件。

相关内容