SED- combine matching regex lines to make a csv file - regex

I was wondering if it is possible to use sed to create a csv file by combining multiple lines together onto a singe line separate by commas.
For example I have written a sed statement that retrieves the lines I want.
sed -n -e '/ENTITIES/,/ENDSEC/p' | sed -n -e '/ 8/{n;p;}' -e '/ 10/{n;p;}' -e '/ 20/{n;p;}' -e '/ 11/{n;p;}' -e '/ 21/{n;p;}' < Test.txt > out.csv
Which produces the output;
0
4.93
9.04
27.9
23.4
0
34.56
0.77
66.65
19.50
0
55.26
47.29
53.42
19.75
0
-18.22
44.35
19.74
53.28
But I would Like it to output;
0,4.93,9.04,27.9,23.4
0,34.56,0.77,66.65,19.50
0,55.26,47.29,53.42,19.75
0,-18.22,44.35,19.74,53.28
Is there anyway to do this without a pipe? Id Rather not invoke another command as the files I process are upwards of 100 mil lines or so.
Thanks in advance for your help!
To add, here is a portion of my input file;
More Stuff Above
AcDbBlockEnd
0
ENDSEC
0
SECTION
2
ENTITIES
0
LINE
5
1B1
330
1F
100
AcDbEntity
8
0
100
AcDbLine
10
4.933855223957067
20
9.042372500389475
30
0.0
11
27.92566226775641
21
23.49207557886149
31
0.0
0
LINE
5
1B2
330
1F
100
AcDbEntity
8
0
100
AcDbLine
10
34.56437535704545
20
0.778745874786317
30
0.0
11
66.65564369957746
21
19.50612180407816
31
0.0
0
LINE
5
1B3
330
1F
100
AcDbEntity
8
0
100
AcDbLine
10
55.26446832764479
20
47.29118282642324
30
0.0
11
53.42718194719286
21
19.75092411476788
31
0.0
0
LINE
5
1B4
330
1F
100
AcDbEntity
ENDSEC
0
More stuff below.

Something like this might be what you're looking for, but as jaypal said, without seeing the input it's somewhat of a guess.
sed -n '
/ENTITIES/,/ENDSEC/p
/ 8/{n;h}
/ 10/{n;H}
/ 20/{n;H}
/ 11/{n;H}
/ 21/{n;H;g;s/\n/,/g;p}
' Test.txt > out.csv
With comments:
sed -n '
/ENTITIES/,/ENDSEC/p
/ 8/{n;h} # store next line in hold space
/ 10/{n;H} # append next line to hold space (after newline)
/ 20/{n;H} # ditto
/ 11/{n;H} # ditto
/ 21/{n;H; # ditto
g; # put hold space into pattern space
s/\n/,/g; # substitute commas for newlines
p # print it
}
' Test.txt > out.csv

Just pipe your sed to
sed 'your long sed commnand' | paste -d, - - - - -
the result will be
0,4.93,9.04,27.9,23.4
0,34.56,0.77,66.65,19.50
0,55.26,47.29,53.42,19.75
0,-18.22,44.35,19.74,53.28

Got it Thanks to ooga! Before I lacked the understanding of hold space vs. pattern space, now it has all become clear!
sed -n '
/ENTITIES/,/ENDSEC/{
/ 8/{n;h;};
/ 10/{n;H;};
/ 20/{n;H;};
/ 11/{n;H;};
/ 21/{n;H;g;s/\n/,/g;p};
}
' < Test.dxf > out.csv

Related

Split file by vector of line numbers

I have a large file, about 10GB. I have a vector of line numbers which I would like to use to split the file. Ideally I would like to accomplish this using command-line utilities. As a regex:
File:
1 2 3
4 5 6
7 8 9
10 11 12
13 14 15
16 17 18
Vector of line numbers:
2 5
Desired output:
File 1:
1 2 3
File 2:
4 5 6
7 8 9
10 11 12
File 3:
13 14 15
16 17 18
Using awk:
$ awk -v v="2 5" ' # space-separated vector if indexes
BEGIN {
n=split(v,t) # reshape vector to a hash
for(i=1;i<=n;i++)
a[t[i]]
i=1 # filename index
}
{
if(NR in a) { # file record counter in the vector
close("file" i) # close previous file
i++ # increase filename index
}
print > ("file" i) # output to file
}' file
Sample output:
$ cat file2
4 5 6
7 8 9
10 11 12
Very slightly different from James's and kvantour's solutions: passing the vector to awk as a "file"
vec="2 5"
awk '
NR == FNR {nr[$1]; next}
FNR == 1 {filenum = 1; f = FILENAME "." filenum}
FNR in nr {
close(f)
f = FILENAME "." ++filenum
}
{print > f}
' <(printf "%s\n" $vec) file
$ ls -l file file.*
-rw-r--r-- 1 glenn glenn 48 Jul 17 10:02 file
-rw-r--r-- 1 glenn glenn 7 Jul 17 10:09 file.1
-rw-r--r-- 1 glenn glenn 23 Jul 17 10:09 file.2
-rw-r--r-- 1 glenn glenn 18 Jul 17 10:09 file.3
This might work for you:
csplit -z file 2 5
or if you want regexp:
csplit -z file /2/ /5/
With the default values, the output files will be named xxnn where nn starts at 00 and is incremented by 1.
N.B. The -z option prevents empty elided files.
Here is a little awk that does the trick for you:
awk -v v="2 5" 'BEGIN{v=" 1 "v" "}
index(v," "FNR" ") { close(f); f=FILENAME "." (++i) }
{ print > f }' file
This will create files of the form: file.1, file.2, file.3, ...
Ok, I've gone totally mental this morning, and I came up with a Sed program (with functions, loops, and all) to generate a Sed script to make what you want.
Usage:
put the script in a file (e.g. make.sed) and chmod +x it;
then use it as the script for this Sed command sed "$(./make.sed <<< '1 4')" inputfile¹
Note that ./make.sed <<< '1 4' generates the following sed script:
1,1{w file.1
be};1,4{w file.2
be};1,${w file.3
be};:e
¹ Unfortunately I misread the question, so my script works taking the line number of the last line of each block that you want to write to file, so your 2 5 has to be changed to 1 4 to be fed to my script.
#!/usr/bin/env -S sed -Ef
###########################################################
# Main
# make a template sed script, in which we only have to increase
# the number of each numbered output file, each of which is marked
# with a trailing \x0
b makeSkeletonAndMarkNumbers
:skeletonMade
# try putting a stencil on the rightmost digit of the first marked number on
# the line and loop, otherwise exit
b stencilLeastDigitOfNextMarkedNumber
:didStencilLeastDigitOfNextMarkedNumber?
t nextNumberStenciled
b exit
# continue processing next number by adding 1
:nextNumberStenciled
b numberAdd1
:numberAdded1
# try putting a stencil on the rightmost digit of the next marked number on
# the line and loop, otherwise we're done with the first marked number, we can
# clean its marker, and we can loop
b stencilNextNumber
:didStencilNextNumber?
t nextNumberStenciled
b removeStencilAndFirstMarker
:removeStencilAndFirstMarkerDone
b stencilLeastDigitOfNextMarkedNumber
###########################################################
# puts a \n on each side of the first digit marked on the right by \x0
:stencilLeastDigitOfNextMarkedNumber
tr
:r
s/([0-9])\x0;/\n\1\n\x0;/1
b didStencilLeastDigitOfNextMarkedNumber?
###########################################################
# makes desired sed script skeleton from space-separated numbers
:makeSkeletonAndMarkNumbers
s/$/ $/
s/([1-9]+|\$) +?/1,\1{w file.0\x0;be};/g
s/$/:e/
b skeletonMade
###########################################################
# moves the stencil to the next number followed by \x0
:stencilNextNumber
trr
:rr
s/\n(.)\n([^\x0]*\x0[^\x0]+)([0-9])\x0/\1\2\n\3\n\x0/
b didStencilNextNumber?
###########################################################
# +1 with carry to last digit on the line enclosed in between two \n characters
:numberAdd1
#i\
#\nprima della somma:
#l
:digitPlus1
h
s/.*\n([0-9])\n.*/\1/
y/0123456789/1234567890/
G
s/(.)\n(.*)\n.\n/\2\n\1\n/
trrr
:rrr
/[0-9]\n0\n/s/(.)\n0\n/\n\1\n0/
t digitPlus1
# the following line can be problematic for lines starting with number
/[^0-9]\n0\n/s/(.)\n0\n/\n\1\n10/
b numberAdded1
###########################################################
# remove stencil and first marker on line
:removeStencilAndFirstMarker
s/\n(.)\n/\1/
s/\x0//
b removeStencilAndFirstMarkerDone
###########################################################
:exit
# a bit of post processing the `w` command has to be followed
# by the filename, then by a newline, so we change the appropriate `;`s to `\n`.
s/(\{[^;]+);/\1\n/g

Bash script to split a file by grep everything till the second time match in a column into one file and the rest into another

I am trying to split a file with data like
2 0.2345
58 0.3608
59 0.3504
60 0.4175
65 0.3995
66 0.3972
67 0.4411
411 0.3455
2 1.3867
3 1.4532
4 1.2925
5 1.2473
6 1.2605
7 1.2463
8 1.1667
9 1.1312
10 1.1502
11 1.1190
12 1.0346
13 1.0291
409 0.8025
410 0.8695
411 0.9154
For this kind of data, I am trying to split this into two files:
File 1 : 2 -411 (first Column match)
File 2 : 2-411 (second occurrence in the first column)
For this, I wrote these two one liners:
awk '1;/411/{exit}' $1 > File1_$1 ;
awk '/411/,0' $1 | awk '{if (NR!=1) {print}}' > File2_$1
The problem is that if there is a match of "411" (as in "67 0.4411") on the second column, my script prematurely cuts from that line.
I am unable to make the match on the first column only as occurrence of 411 on the second column can be number of times and not of interest.
Any help would be greatly appreciated.
an idea could be to use this command combination
awk '{ if ($1 >= 2 && $1 <= 411) print $0 }{if ($1=="411") exit}' input > f1
then
grep -v -f f1 input > f2
if your input file is more bigger you should repeat step2.
I don't know nothing about Bash, but for regex i think you should indicate that the line begins with 411 like that \b411.

Extracting text file information via command line/script

I'd like to extract only certain information from a block of text. I have had great luck with asking the StackOverflow community for their expertise assistance, especially with tricky topics (RegEx, perl, sed, awk).
The text is output from a tshark command that I would like to manipulate and print out to avoid unnecessary information.
Any help would be appreciated. I am currently learning the ways of the aforementioned topics, but it's slow going!
Any script or command help to achieve the following output will be seriously helpful.
Original:
Host 1 Host 2 Total Relative Duration
Host 1 Host 2 Frames Bytes Frames Bytes Frames Bytes Start
192.168.0.14 <-> 192.168.0.13 3898 4872033 1971 120545 5869 4992578 0.001886000 283.6363
192.168.0.162 <-> 192.168.0.71 2 1992 2 1992 4 3984 176.765198000 77.0542
192.168.0.191 <-> 192.168.0.150 3 2988 0 0 3 2988 199.319020000 59.7055
192.168.0.227 <-> 192.168.0.157 3 2988 0 0 3 2988 197.013283000 76.7197
192.168.0.221 <-> 192.168.0.94 3 2988 0 0 3 2988 196.312847000 59.7065
192.168.0.75 <-> 192.168.0.58 2 1992 1 996 3 2988 191.995706000 59.7121
224.0.0.252 <-> 192.168.0.13 3 207 0 0 3 207 180.521299000 0.0536
192.168.0.191 <-> 192.168.0.50 1 996 2 1992 3 2988 173.452130000 59.6849
192.168.0.41 <-> 192.168.0.13 3 2988 0 0 3 2988 167.180087000 76.6960
192.168.0.206 <-> 192.168.0.153 1 996 1 996 2 1992 270.528070000 4.4070
Desired:
Host 1 Host 2 Total Bytes
x.x.x.x x.x.x.x N
x.x.x.x x.x.x.x N
x.x.x.x x.x.x.x N
Try:
awk '
BEGIN { printf "%-15s %-15s %s\n", "Host 1", "Host 2", "Total Bytes" }
NR>2 { printf "%-15s %-15s %11s\n", $1, $3, $9 }
' file
Adjust the output-field widths as needed.
The BEGIN block is used to print the output header line.
NR > 2 ensures that the input header lines are skipped.
printf is used with field-width specifiers create column-aligned output.
a - before the width specifier indicates left-aligned output (e.g.,%-15s; without it, the value is right-aligned (e.g., %11s)
in perl:
tshark | perl -lane 'print join "\t", ($F[0], $F[2], $F[8])'
the -a option splits each line of stdin into an array called #F. the column numbers don't correspond well to the array index numbers because -a splits by space by default. you can set the delimiter with -F if you like.
-F would help get the headers aligned correctly too, but to just skip the misaligned headers, add next if $. < 3; before print to skip the first two lines
Given your output is in filename:
sed 's/ \+/ /g' filename | tail -n +3 | cut -f1,3,9 -d ' ' | sed 's/ /\t/g' | sort -r -n -k3
replace multiple spaces with a single one, for tokenizing
discard the first two header lines
project columns 1, 3, and 9
replace spaces with tabs to have columns back
sort desc by total bytes
output:
192.168.0.14 192.168.0.13 4992578
192.168.0.162 192.168.0.71 3984
192.168.0.75 192.168.0.58 2988
192.168.0.41 192.168.0.13 2988
192.168.0.227 192.168.0.157 2988
192.168.0.221 192.168.0.94 2988
192.168.0.191 192.168.0.50 2988
192.168.0.191 192.168.0.150 2988
192.168.0.206 192.168.0.153 1992
224.0.0.252 192.168.0.13 207

grep -c value NH:i:1 only for every line in file, not also NH:i:12

cat samtry.txt | grep -c NH:i:1
See an example of three lines below. the bold information is whats important
HWI-ST697:178:D1U9CACXX:1:2111:12787:5687 153 scaffold_1 33005 50 101M * 0 0 GACTAAGGAAGTCATCTGCAGTGCCCCTTGCACTTCCTAATGGGACTTTCCCTGGTTGACTATTCTTACTATGAGAACAATGAGCACCAGCTTCATTCACA DCDDDDDDDDDDDEEEEEEEEFGHGJIHGHFHJIJIJJIJJJJIHJJIJIIIFJJIGGGIJJJIIJJHIGJIJJJGHJJIJIJIGFJJGHHHHFFFFFCCC AS:i:-11 XN:i:0 XM:i:2 XO:i:0 XG:i:0 NM:i:2 MD:Z:18T26G55YT:Z:UU **NH:i:1**
HWI-ST697:178:D1U9CACXX:3:1310:18383:72540 89 scaffold_1 33005 50 101M * 0 0 GACTAAGGAAGTCATCTGCAGTGCCCCTTGCACTTCCTAATGGGACTTTCCCTGGTTGACTATTCTTACTATGAGAACAATGAGCACCAGCTTCATTCACA DDDDDDDDDDDDDEEEEEEFFFHHHIIJJIIIJIJJJJJJJJJJHJJJJJJJJJJJJJIJJJJJJJJIJJJIJJIJJJJJJJJIHFJJHHHHHFFFFFCCC AS:i:-11 XN:i:0 XM:i:2 XO:i:0 XG:i:0 NM:i:2 MD:Z:18T26G55YT:Z:UU **NH:i:11**
HWI-ST697:178:D1U9CACXX:7:1212:17559:76798 89 scaffold_1 33007 50 101M * 0 0 CTAAGGAAGTCATCTGCAGTGCCCCTTGCACTTCCTAATGGGACTTTCCCTGGTTGACTATTCTTACTATGAGAACAATGAGCACCAGCTTCATTCACAAG DDDDDDDDDDDDDEEEECDFFHGHIGJIIHJJJIIJJJJJJHHJJJJJJJJJJJIIIJJJJGIIGBJJIJJJJIJJJJJIHHHFJJIJHHHHGFFFFFCCC AS:i:-11 XN:i:0 XM:i:2 XO:i:0 XG:i:0 NM:i:2 MD:Z:16T26G57YT:Z:UU **NH:i:1**
I am trying to use a shell script to count all the lines in a tab-delimited-file (testfile: samtry.txt, contains 10 lines to test on) that contains the following Regular expression NH:i:1
The problem is of course that I get the information I wanted; but it also counts the lines with the following outcome: NH:i:1x (where x is any possible digit: 0-9)
The position of the NH:i:x (x = any digit until around 50) is in every line of the file on 20, its not the last position of the line. Every line has 23 'positions'.
Does anyone know how to do this with grep or another tool?
I've got around 100 files which each have a size of around 3GB each, and I don't know how to solve this problem
I hope I give enough information, I am happy for every answer
Try grep with word boundaries:
grep -c '\<NH:i:1\>' samtry.txt
OR grep -w:
grep -wc 'NH:i:1' samtry.txt

Replace first two whitespace occurrences with a comma using sed

I have a whitespace delimited file with a variable number of entries on each line. I want to replace the first two whitespaces with commas to create a comma delimited file with three columns.
Here's my input:
a b 1 2 3 3 2 1
c d 44 55 66 2355
line http://google.com 100 200 300
ef jh 77 88 99
z y 2 3 33
And here's my desired output:
a,b,1 2 3 3 2 1
c,d,44 55 66 2355
line,http://google.com,100 200 300
ef,jh,77 88 99
z,y,2 3 33
I'm trying to use perl regular expressions in a sed command but I can't quite get it to work. First I try capturing a word, followed by a space, then another word, but that only works for lines 1, 2, and 5:
$ cat test | sed -r 's/(\w)\s+(\w)\s+/\1,\2,/'
a,b,1 2 3 3 2 1
c,d,44 55 66 2355
line http://google.com 100 200 300
ef jh 77 88 99
z,y,2 3 33
I also try capturing whitespace, a word, and then more whitespace, but that gives me the same result:
$ cat test | sed -r 's/\s+(\w)\s+/,\1,/'
a,b,1 2 3 3 2 1
c,d,44 55 66 2355
line http://google.com 100 200 300
ef jh 77 88 99
z,y,2 3 33
I also try doing this with the .? wildcard, but that does something funny to line 4.
$ cat test | sed -r 's/\s+(.?)\s+/,\1,/'
a,b,1 2 3 3 2 1
c,d,44 55 66 2355
line http://google.com 100 200 300
ef jh,,77 88 99
z,y,2 3 33
Any help is much appreciated!
How about this:
sed -e 's/\s\+/,/' | sed -e 's/\s\+/,/'
It's probably possible with a single sed command, but this is sure an easy way :)
My output:
a,b,1 2 3 3 2 1
c,d,44 55 66 2355
line,http://google.com,100 200 300
ef,jh,77 88 99
z,y,2 3 33
Try this:
sed -r 's/\s+(\S+)\s+/,\1,/'
Just replaced \w (one "word" char) with \S+ (one or more non-space chars) in one of your attempts.
You can provide multiple commands to a single instance of sed by just providing multiple -e arguments.
To do the first two, just use:
sed -e 's/\s\+/,/' -e 's/\s\+/,/'
This basically runs both commands on the line in sequence, the first doing the first block of whitespace, the second doing the next.
The following transcript shows this in action:
pax$ echo 'a b 1 2 3 3 2 1
c d 44 55 66 2355
line http://google.com 100 200 300
ef jh 77 88 99
z y 2 3 33
' | sed -e 's/\s\+/,/' -e 's/\s\+/,/'
a,b,1 2 3 3 2 1
c,d,44 55 66 2355
line,http://google.com,100 200 300
ef,jh,77 88 99
z,y,2 3 33
Sed s/// supports a way to say which occurrence of a pattern to replace: just add the n to the end of the command to replace only the nth occurrence. So, to replace the first and second occurrences of whitespace, just use it this way:
$ sed 's/ */,/1;s/ */,/2' input
a,b ,1 2 3 3 2 1
c,d ,44 55 66 2355
line,http://google.com 100,200 300
ef,jh ,77 88 99
z,y 2,3 33
EDIT: reading another proposed solutions, I noted that the 1 and 2 after s/ */,/ is not only unnecessary but plainly wrong. By default, s/// just replaces the first occurrence of the pattern. So, if we have two identical s/// in sequence, they will replace the first and the second occurrence. What you need is just
$ sed 's/ */,/;s/ */,/' input
(Note that you can put two sed commands in one expression if you separate them by a semicolon. Some sed implementations do not accept the semicolon after the s/// command; use a newline to separate the commands, in this case.)
A Perl solution is:
perl -pe '$_=join ",", split /\s+/, $_, 3' some.file
Not sure about sed/perl, but here's an (ugly) awk solution. It just prints fields 1-2, separated by commas, then the remaining fields separated by space:
awk '{
printf("%s,", $1)
printf("%s,", $2)
for (i=3; i<=NF; i++)
printf("%s ", $i)
printf("\n")
}' myfile.txt