我有一些文件:
file1.csv
file2.csv
file3.csv
给定的脚本处理它们,并记录到此文件:
my.log
采用这种格式:( filename
col2
col3
):
file1.csv 1 a
file2.csv 1 a
file3.csv 1 a
file2.csv 2 b
file1.csv 2 b
file3.csv 2 b
file1.csv 3 c
file2.csv 3 c
file3.csv 3 c
file2.csv 4 d
file3.csv 4 d
我想从每个文件的文件中获取一个col3
值(仅最后一个)。my.log
*.csv
我运行这个命令:
ls *.csv | xargs -I@ bash -c "cat my.log | grep @ | tail -n 1 | awk '{ print $3 }'"
它工作得很好,除了 awk 给了我所有的列。
file1.csv 3 c
file2.csv 4 d
file3.csv 4 d
我怎样才能只得到col3
一列?例如,这个:
c
d
d
答案1
在你的表情里
"cat my.log | grep @ | tail -n 1 | awk '{ print $3 }'"
...该字符串周围的双引号意味着单引号被视为文字。它们不保护$3
外壳,因此它被扩展为环境变量。由于$3
实际上并未由 shell 定义(除非这是在您使用 3 个参数调用的脚本中),因此它变成空字符串,并且表达式awk
很简单{ print }
,打印整行。
您可以通过转义来解决此问题$
:
ls *.csv | xargs -I@ bash -c "cat my.log | grep @|tail -n 1|awk '{print \$3}'"
...或者通过将表达式awk
移出xargs
:
ls *.csv | xargs -I@ bash -c "cat my.log | grep @|tail -n 1"|awk '{print $3}'
答案2
ls
将into的输出通过管道传输xargs
是一个坏主意(事实上,对 into 的输出执行任何操作ls
传输到是一个坏主意(事实上,除了简单地在终端中查看它之外,馊主意)。如果你绝对必须做这样的事情,至少使用类似的东西find . -maxdepth 1 -type f -iname '*.csv' -print0
并将其通过管道传输到xargs -0r
.
但是,在这种情况下,您根本不需要这样做,因为 .csv 文件的文件名是已经在my.log
。
在 awk 中:
#!/usr/bin/awk -f
{ seen[$1] = $3 }
END {
for (f in seen) { print seen[f] };
}
或作为单行:
$ awk '{seen[$1] = $3}; END {for (f in seen) { print seen[f] };}' my.log
c
d
d
这些将打印第 1 列中列出的每个文件在第 3 列中看到的最后一个值。
如果您希望它仅打印第 3 列中看到的第一个值,请将其更改为:
!seen[$1] { seen[$1] = $3 }
如果您不想使用find | xargs
并且确实需要使用.csv
当前目录中当前所有文件的文件名,一种替代方法是执行以下操作:
#!/usr/bin/perl
use strict;
my $logfile=shift; # get the first arg (the logfile name)
my $re=join("|",@ARGV); # turn the remaining args into a regular expression
@ARGV=$logfile; # set the logfile name as the sole cmd-line argument.
my %seen=();
while(<>) {
next unless (m/^($re)/o); # ignore any filenames that weren't on the cmd line.
my(@F) = split;
$seen{$F[0]} = $F[2]; # perl arrays start from 0, not 1.
};
foreach my $file (sort keys %seen) {
print $seen{$file}, "\n";
};
将其另存为,例如nandro.pl
,使其可执行chmod +x
并运行为:
$ ./nandro.pl my.log *.csv
c
d
d