How to display content of a file with taking accou

2019-08-21 00:06发布

问题:

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?