可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have the file1 with the following content
{"name":"clio5", "value":"13"}
{"name":"citroen_c4", "value":"23"}
{"name":"citroen_c3", "value":"12"}
{"name":"golf4", "value":"16"}
{"name":"golf3", "value":"8"}
And I have the file2 with the following content
{"name":"clio5", "value":"14"}
{"name":"citroen_c4", "value":"25"}
{"name":"golf4", "value":"18"}
I want to execute a shell command in order to display the content of the file1 and the file2. if a name
exist in both file1 and file2 so I want to display only the related line of the file2.
So the output should look like this:
$command taking account file1 file2
{"name":"clio5", "value":"14"}
{"name":"citroen_c4", "value":"25"}
{"name":"citroen_c3", "value":"12"}
{"name":"golf4", "value":"18"}
{"name":"golf3", "value":"8"}
The command should not edit file1 neither file2
Edit
The file1 and file2 have exactelly the same content format:
{"name":"any", "value":"xx"}
The command should be as simple as possible
The command could contains grep
, sed
, awk
回答1:
Here is one way with awk
:
awk -F"[:,]" '
NR==FNR { name[$2]=$0;next }
($2 in name) { delete name[$2]; print $0 }
END { for (left in name) print name[left] }' file1 file2
Test:
$ head file*
==> file1 <==
{"name":"clio5", "value":"13"}
{"name":"citroen_c4", "value":"23"}
{"name":"citroen_c3", "value":"12"}
{"name":"golf4", "value":"16"}
{"name":"golf3", "value":"8"}
==> file2 <==
{"name":"clio5", "value":"14"}
{"name":"citroen_c4", "value":"25"}
{"name":"golf4", "value":"18"}
$ awk -F"[:,]" '
NR==FNR { name[$2]=$0;next }
($2 in name) { delete name[$2]; print $0 }
END { for (left in name) print name[left] }' file1 file2
{"name":"clio5", "value":"14"}
{"name":"citroen_c4", "value":"25"}
{"name":"golf4", "value":"18"}
{"name":"golf3", "value":"8"}
{"name":"citroen_c3", "value":"12"}
回答2:
One way is to use join
to join both files on the name field and then use awk
to change the value. This is shown below:
$ join -t, -a1 <(sort file1) <(sort file2) | awk -F, -vOFS=, '{if($3){$2=$3;NF-=1}}1' | sed 's/new_value/value/g'
{"name":"citroen_c3", "value":"12"}
{"name":"citroen_c4", "value":"25"}
{"name":"clio5", "value":"14"}
{"name":"golf3", "value":"8"}
{"name":"golf4", "value":"18"}
join
requires both files to be sorted on the join key.
Alternatively, if ordering matters to you, you can use a loop to read each line and then grep the second file for the new value. This is shown below:
while IFS= read -r line
do
if [[ $line =~ name\":\"([^\"]*)\" ]]
then
name=${BASH_REMATCH[1]}
newVal=$(grep "\"name\":\"$name\"" file2 | sed 's/^.*"\([^"]\+\)"}$/\1/g')
if [[ -z $newVal ]]
then
echo "$line"
else
echo "{\"name\":\"$name\", \"value\":\"$newVal\"}"
fi
fi
done < file1
Output:
{"name":"clio5", "value":"14"}
{"name":"citroen_c4", "value":"25"}
{"name":"citroen_c3", "value":"12"}
{"name":"golf4", "value":"18"}
{"name":"golf3", "value":"8"}
回答3:
The input looks like JSON. Using a proper tool, the JSON library for Perl:
#!/usr/bin/perl
use warnings;
use strict;
use JSON qw(from_json to_json);
my %hash;
for my $file (qw/file1 file2/) {
open my $FH, '<', $file or die $!;
while (<$FH>) {
my $j = from_json($_);
$hash{$j->{name}} = $j->{value} // $j->{new_value};
}
}
while (my ($name, $value) = each %hash) {
print to_json({name => $name, value => $value}), "\n";
}
It reads the two files, overwriting the values when reading the second one. For me, the output is not exactly what you expected:
{"value":"14","name":"clio5"}
{"value":"18","name":"golf4"}
{"value":"25","name":"citroen_c4"}
{"value":"8","name":"golf3"}
{"value":"12","name":"citroen_c3"}
As JSON goes, it is equivalent to your expected output, so if you always use the appropriate libraries, you will not notice the difference. If not, you have to tweak the code some more.
Or, if you really want to use sed:
sed 's%\({"name":"[^"]*", \)"new_value":\("[^"]*"\)}%s/\1"value.*/\1"value":\2}/%' file2 \
| sed -f- file1
The first sed invocation translates file2 into a sed script that replaces the old values in file1.
回答4:
A simple change to my solution, and it seems to work:
Is the order of the output important? Otherwise you could do this, using sort
:
cat file2 file1 | sort -u -s --key=1
The change is adding --key=1
. This means it will sort on the first column (up to first whitespace) of each line. -s
makes it not use sorting on the rest of the line, when the other sort finds two equals. The order of the files determines which file's lines will be used when there is a match.
This will output the result sorted alphabetically. It looks like the input already is, in which case it should exactly what you describe (I believe). Otherwise, it would change the order of the lines (to be sorted). Not sure if that's a problem?