How to replace pairs of strings in two files to identical IDs? - shell
[Update2] As it often happens, the scope of the task expanded quite a bit as a understood it better. The obsolete parts are crossed out, and you find the updated explanation below. [/Update2]
I have a pair of rather large log files with very similar content, except that some strings are different between the two. A couple of examples:
UnifiedClassLoader3#19518cc | UnifiedClassLoader3#d0357a
JBossRMIClassLoader#13c2d7f | JBossRMIClassLoader#191777e
That is, wherever the first file contains UnifiedClassLoader3#19518cc, the second contains UnifiedClassLoader3#d0357a, and so on. [Update] There are about 40 distinct pairs of such identifiers.[/Update]
UnifiedClassLoader3#19518cc | UnifiedClassLoader3#d0357a
JBossRMIClassLoader#13c2d7f | JBossRMIClassLoader#191777e
Logi18n#177060f | Logi18n#12ef4c6
LogFactory$1#15e3dc4 | LogFactory$1#2942da
That is, wherever the first file contains UnifiedClassLoader3#19518cc, the second contains UnifiedClassLoader3#d0357a, and so on. Note that all these strings are inside long lines of text, and they appear in many rows, intermixed with each other. There are about 4000 distinct pairs of such identifiers, and the size of each file is about 34 MB. So performance became an issue as well.
I want to replace these with identical IDs so that I can spot the really important differences between the two files. I.e. I want to replace all occurrences of both UnifiedClassLoader3#19518cc in file1 and UnifiedClassLoader3#d0357a in file2 with UnifiedClassLoader3#1; all occurrences of both Logi18n#177060f in file1 and Logi18n#12ef4c6 in file2 with Logi18n#2 etc. The counters 1 and 2 are arbitrary choices - the only requirement is that there is a one to one mapping between the old and new strings (i.e. the same string is always replaced by the same value and no different strings are replaced by the same value).
Using the Cygwin shell, so far I managed to list all different identifiers occurring in one of the files with
grep -o -e 'ClassLoader[0-9]*#[0-9a-f][0-9a-f]*' file1.log | sort | uniq
grep -o -e '[A-Z][A-Za-z0-9]*\(\$[0-9][0-9]*\)*#[0-9a-f][0-9a-f]*' file1.log
| sort | uniq
However, now the original order is lost, so I don't know which is the pair of which ID in the other file. With grep -n I can get the line number, so the sort would preserve the order of appearance, but then I can't weed out the duplicate occurrences. Unfortunately grep can not print only the first match of a pattern.
I figured I could save the list of identifiers produced by the above command into a file, then iterate over the patterns in the file with grep -n | head -n 1, concatenate the results and sort them again. The result would be something like
2 ClassLoader3#19518cc
137 ClassLoader#13c2d7f
563 ClassLoader3#1267649
...
Then I could (using sed itself) massage this into a sed command like
sed -e 's/ClassLoader3#19518cc/ClassLoader3#2/g'
-e 's/ClassLoader#13c2d7f/ClassLoader#137/g'
-e 's/ClassLoader3#1267649/ClassLoader3#563/g'
file1.log > file1_processed.log
and similarly for file2.
However, before I start, I would like to verify that my plan is the simplest possible working solution to this.
Is there any flaw in this approach? Is there a simpler way?
I think this does the trick, or at least comes close
#!/bin/sh
for PREFIX in file1 file2
do
cp ${PREFIX}.log /tmp/filter.$$.txt
FILE_MAP=`egrep -o -e 'ClassLoader[0-9a-f]*#[0-9a-f]+' ${PREFIX}.log | uniq | egrep -n .`
for MAP in `echo $FILE_MAP`
do
NUMBER=`echo $MAP | cut -d : -f 1`
WORD=`echo $MAP | cut -d : -f 2`
sed -e s/$WORD/ClassLoader#$NUMBER/g /tmp/filter.$$.txt > ${PREFIX}_processed.log
cp ${PREFIX}_processed.log /tmp/filter.$$.txt
done
rm /tmp/filter.$$.txt
done
Let me know if you have questions on how it works and why.
Here's my test data and the output
file1.log:
A1
UnifiedClassLoader3#a45bc1
A2
UnifiedClassLoader3#a45bc1
A3
UnifiedClassLoader3#a45bc1
A4
JBossRMIClassLoader#bc450a
A5
JBossRMIClassLoader#bc450a
A6
JBossRMIClassLoader#bc450a
B1
UnifiedClassLoader3#a45bc2
B2
UnifiedClassLoader3#a45bc2
B3
UnifiedClassLoader3#a45bc2
B4
JBossRMIClassLoader#bc450b
B5
JBossRMIClassLoader#bc450b
B6
JBossRMIClassLoader#bc450b
C1
UnifiedClassLoader3#a45bc3
C2
UnifiedClassLoader3#a45bc3
C3
UnifiedClassLoader3#a45bc3
C4
JBossRMIClassLoader#bc450c
C5
JBossRMIClassLoader#bc450c
C6
JBossRMIClassLoader#bc450c
file2.log (Similar patterns except the "C" set repeats the "A" set)
A1
UnifiedClassLoader3#d0357a
A2
UnifiedClassLoader3#d0357a
A3
UnifiedClassLoader3#d0357a
A4
JBossRMIClassLoader#191777e
A5
JBossRMIClassLoader#191777e
A6
JBossRMIClassLoader#191777e
B1
UnifiedClassLoader3#d0357b
B2
UnifiedClassLoader3#d0357b
B3
UnifiedClassLoader3#d0357b
B4
JBossRMIClassLoader#191777f
B5
JBossRMIClassLoader#191777f
B6
JBossRMIClassLoader#191777f
C1
UnifiedClassLoader3#d0357a
C2
UnifiedClassLoader3#d0357a
C3
UnifiedClassLoader3#d0357a
C4
JBossRMIClassLoader#191777e
C5
JBossRMIClassLoader#191777e
C6
JBossRMIClassLoader#191777e
And after processing you get file1_processed.log
A1
UnifiedClassLoader#1
A2
UnifiedClassLoader#1
A3
UnifiedClassLoader#1
A4
JBossRMIClassLoader#2
A5
JBossRMIClassLoader#2
A6
JBossRMIClassLoader#2
B1
UnifiedClassLoader#3
B2
UnifiedClassLoader#3
B3
UnifiedClassLoader#3
B4
JBossRMIClassLoader#4
B5
JBossRMIClassLoader#4
B6
JBossRMIClassLoader#4
C1
UnifiedClassLoader#5
C2
UnifiedClassLoader#5
C3
UnifiedClassLoader#5
C4
JBossRMIClassLoader#6
C5
JBossRMIClassLoader#6
C6
and file2_processed.log
A1
UnifiedClassLoader#1
A2
UnifiedClassLoader#1
A3
UnifiedClassLoader#1
A4
JBossRMIClassLoader#2
A5
JBossRMIClassLoader#2
A6
JBossRMIClassLoader#2
B1
UnifiedClassLoader#3
B2
UnifiedClassLoader#3
B3
UnifiedClassLoader#3
B4
JBossRMIClassLoader#4
B5
JBossRMIClassLoader#4
B6
JBossRMIClassLoader#4
C1
UnifiedClassLoader#1
C2
UnifiedClassLoader#1
C3
UnifiedClassLoader#1
C4
JBossRMIClassLoader#2
C5
JBossRMIClassLoader#2
C6
JBossRMIClassLoader#2
Related
awk or sed command for columns and rows selection from multiple files
Looking for a command for the following task: I have three files, each with two columns, as seen below. I would like to create file4 with four columns. The output should resemble a merge-sorted version of file1, file2 and file3 such that the first column is sorted, the second column is the second column of file1 the third column is the second column of file2 and the fourth column is the second column of file3. The entries in column 2 to 3 should not be sorted but should match the key-value in the first column of the original files. I tried intersection in Linux, but not giving the desired outputs. Any help will be appreciated. Thanks in advance!! $ cat -- file1 A1 B5 A10 B2 A3 B15 A15 B6 A2 B10 A6 B19 $ cat -- file2 A10 C4 A4 C8 A6 C5 A3 C10 A12 C14 A15 C18 $ cat -- file 3 A3 D1 A22 D9 A20 D3 A10 D5 A6 D10 A21 D11 $ cat -- file 4 col1 col2 col3 col4 A1 B5 A2 B10 A3 B15 C10 D1 A4 C8 A6 B19 C5 D10 A10 B2 C4 D5 A12 C14 A15 B6 C18 A20 D3 A21 D11 A22 D9
Awk + Bash version: ( echo "col1, col2, col3, col4" && awk 'ARGIND==1 { a[$1]=$2; allkeys[$1]=1 } ARGIND==2 { b[$1]=$2; allkeys[$1]=1 } ARGIND==3 { c[$1]=$2; allkeys[$1]=1 } END{ for (k in allkeys) { print k", "a[k]", "b[k]", "c[k] } }' file1 file2 file3 | sort -V -k1,1 ) | column -t -s ',' Pure Bash version: declare -A a while read key value; do a[$key]="${a[$key]:-}${a[$key]:+, }$value"; done < file1 while read key value; do a[$key]="${a[$key]:-, }${a[$key]:+, }$value"; done < file2 while read key value; do a[$key]="${a[$key]:-, , }${a[$key]:+, }$value"; done < file3 (echo "col1, col2, col3, col4" && for i in ${!a[#]}; do echo $i, ${a[$i]} done | sort -V -k1,1) | column -t -s ',' Explanation for "${a[$key]:-, , }${a[$key]:+, }$value" please check Shell-Parameter-Expansion
Using GNU Awk: gawk '{ a[$1] = substr($1, 1); b[$1, ARGIND] = $2 } END { PROCINFO["sorted_in"] = "#val_num_asc" for (i in a) { t = i for (j = 1; j <= ARGIND; ++j) t = t OFS b[i, j] print t } }' file{1..3} | column -t
There is a simple tool called join that allows you to perform this operation: #!/usr/bin/env bash cut -d ' ' -f1 file{1,2,3} | sort -k1,1 -u > ftmp for f in file1 file2 file3; do mv -- ftmp file4 join -a1 -e "---" -o auto file4 <(sort -k1,1 "$f") > ftmp done sort -k1,1V ftmp > file4 cat file4 This outputs A1 B5 --- --- A2 B10 --- --- A3 B15 C10 D1 A4 --- C8 --- A6 B19 C5 D10 A10 B2 C4 D5 A12 --- C14 --- A15 B6 C18 --- A20 --- --- D3 A21 --- --- D11 A22 --- --- D9 I used --- to indicate an empty field. If you want to pretty print this, you have to re-parse it with awk or anything else.
This might work for you (GNU sed and sort): s=''; for f in file{1,2,3}; do s="$s\t"; sed -E "s/\s+/$s/" $f; done | sort -V | sed -Ee '1i\col1\tcol2\tcol3\tcol4' -e ':a;N;s/^((\S+\t).*\S).*\n\2\t+/\1\t/;ta;P;D' Replace spaces by tabs and insert the number of tabs between the key and value depending on which file is being processed. Sort the output by key column order. Coalesce each line with its key and print the result.
uniq -c in one column
Imagine we have a txt file like the next one: Input: a1 D1 b1 D1 c1 D1 a1 D2 a1 D3 c1 D3 I want to count the time each element in the first column appears but also keep the information provided by the second column (someway). Potential possible output formats are represented, but any coherent alternative is also accepted: Possible output 1: 3 a1 D1,D2,D3 1 b1 D1 2 c1 D1,D3 Possible output 2: 3 a1 D1 1 b1 D1 2 c1 D1 3 a1 D2 3 a1 D3 1 c1 D3 How can I do this? I guess a combination sort -k 1 input | uniq -c <keep col2> or perhaps using awk but I was not able to write anything that works. However, all answers are considered.
I would harness GNU AWK for this task following way, let file.txt content be a1 D1 b1 D1 c1 D1 a1 D2 a1 D3 c1 D3 then awk 'FNR==NR{arr[$1]+=1;next}{print arr[$1],$0}' file.txt file.txt gives output 3 a1 D1 1 b1 D1 2 c1 D1 3 a1 D2 3 a1 D3 2 c1 D3 Explanation: 2-pass solution (observe that file.txt is repeated), first pass does count number of occurences of first column value storing that data into array arr, second pass is for printing computed number from array, followed by whole line. (tested in GNU Awk 5.0.1)
Using any awk: $ awk ' { vals[$1] = ($1 in vals ? vals[$1] "," : "") $2 cnts[$1]++ } END { for (key in vals) { print cnts[key], key, vals[key] } } ' file 3 a1 D1,D2,D3 1 b1 D1 2 c1 D1,D3
Using bash to get Two variables in for-loop form two different lists
I'm working with bash and I have two lists: AZU SJI IOP A1 B1 C1 Using the bash code below: for f1 in AZU SJI IOP do for f2 in A1 B1 C1 do echo $f1 + $f2 done done I get this result: $ bash dir.sh AZU + A1 AZU + B1 AZU + C1 SJI + A1 SJI + B1 SJI + C1 IOP + A1 IOP + B1 IOP + C1 I would like to get the result in this way AZU A1 SJI B1 IOP C1
Define two arrays such that they have the same indices, then iterate over the indices of one array: list1=(AZU SJI IOP) list2=(A1 B1 C1) for i in "${!list1[#]}"; do echo "${list1[i]} ${list2[i]}" done
You could of course use paste, but since your lists are not in files, you might be interested in a solution without external commands: set -- A1 B1 C1 for f1 in AZU SJI IOP do echo $f1 $1 shift done
How to order the rsync copy of files?
Is it possible to order file copy order for the rsync of a batch of N files? For example, if I have 5 files within a batch, I want to rsync them ordered by filename. Given the following files: A1 C5 A2 B1 B4 Then the rsync order would be: A1 (arrives first on remote_server) A2 B1 B4 C5 (arrives last on remote server) Is this possible?
rsync A1 A2 B1 B4 C5 remote:/path/
Only display the largest number in head
I use sort -r | head and get the out put like this: 8 a1 8 a2 5 a3 5 a4 4 a5 4 a6 4 a7 4 a8 4 a9 4 a0 What can I do to make the output like this: 8 a1 8 a2 only the largest k1 number show up????
There are several ways to do it, but here is one using awk. Since it is already sorted, you want to check to just print lines that match the first value by piping the headed list into something like awk 'BEGIN{maxval=0}; (maxval==0) {maxval=$1}; ($1==maxval) {print $0}'