我过去没有经常使用 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
不要使用反引号。代替使用
$()
。它做同样的事情,但不会破坏引用并且可以嵌套。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")
用双引号引用您的变量扩展。使用花括号是不是引用的替代品。看为什么我的 shell 脚本会因为空格或其他特殊字符而卡住?和$VAR 与 ${VAR} 以及引用或不引用出于原因。
在第二个脚本中,您将管道输出
cut -d, -f2
intocut -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
。我不知道这是否是一个问题,但这是需要注意的事情。您的脚本将 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
如果您确实想使用文件名数组,则可以使用mapfile
andfind
与-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_”为前缀。
这将明显更快,因为它不必分叉cut
和grep
多次 - 对于它处理的每个文件一次。
最后,CSV 文件可以(并且经常)包含双引号字符串字段 - 并且这些引号字段可以包含逗号。可以使用 . 可靠地处理不带引号且不带逗号嵌入字段的简单逗号分隔文件cut
。实际的 CSV 及其所有可选附加功能需要 CSV 解析器。您最好的选择是使用:
答案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 地址和每个网络名称。
在脚本中,您看到输出文件中的内容被覆盖的原因是因为您当前正在使用>
运算符,它会覆盖文件中的所有内容,而您可能想要的是运算>>
符,它将文本附加到文件的末尾文件。