#!/usr/bin/ruby # vim: et sw=2 VER="(a74dbb7)" ME="blgrep: " $col="1;36" $rs=78 $csv=false $dups=false $wib=false $cat=false $im=false $cnt=false $dwb=false $doh=false $sqs=0 $sort=nil $sortmode=0 $incl=[] $excl=[] $inclj=[] $exclj=[] $by=nil $fld=nil $fldm=nil $chk=nil $join=nil $expr=[] $aggr=nil $db={} $ma=[] def in_list(s,list) for i in list if i.match(s) return true end end return false end def regexfix(str) tmp=[] for t in str.scan(/(_)|(\|)|([^_\|]*)/).flatten.compact tmp.push(t) if not t.empty? end res="" c=0 while t=tmp.shift c+=1 if t=="|" res+="|" c=0 next end if t=="_" if tmp.empty? or tmp[0]=="|" res+="([ \t.:()<>]+|$)" elsif c==1 res+="([ \t.:()<>]+|^)" else res+="[ \t.:()<>]+" end next end res+=t end return res end def regexfix2(str) tmp=[] for t in str.scan(/(_)|(\|)|([^_\|]*)/).flatten.compact tmp.push(t) if not t.empty? end res="" while t=tmp.shift case t when("_") then res+=(tmp.empty? or tmp[0]=="|") ? '([ ."\[\]\(\)\{\}]+|$)' : '[ ."\[\]\(\)\{\}]+' when("|") then res+="|" else res+=t end end return res end def scomp(x,y) return 0 if x.empty? and y.empty? return -1 if x.empty? return 1 if y.empty? xa=x.scan(/([0-9]+)|([^0-9]+)/) ya=y.scan(/([0-9]+)|([^0-9]+)/) r=nil xi=xa.shift yi=ya.shift while xi and yi if xi[0] and yi[0] xv=xi[0].to_i yv=yi[0].to_i if(xv==yv) r=0 xi=xa.shift yi=ya.shift next end r=(xv>yv) ? 1 : -1 break end if xi[1] and yi[1] xv=xi[1] yv=yi[1] if(xv==yv) r=0 xi=xa.shift yi=ya.shift next end r=(xv<=>yv) break end break end return (x<=>y) if !r if r==0 r=-1 if !xi and yi r=1 if xi and !yi end return r end $output_cnt=0 class Expr FN0=["count","show"] FN1=["count","sum","min","max","avg","add","sub","icnt","show","pfxl","pfxc"] FN2=["sumif","xval"] FNS=FN1+FN2 AGG=["count","sum","sumif","min","max","avg"] SAG=["add","sub"] OPS=["+","-","*","/"] CMP=["=","==","!=","<","<=",">",">="] LOG=["|","&","^","?","!"] ALL=OPS+CMP+LOG OPL={ "|"=>1,"&"=>1,"^"=>1, "="=>2,"=="=>2,"!="=>2,"<"=>2,"<="=>2,">"=>2,">="=>2, "+"=>3,"-"=>3,"*"=>4,"/"=>4, "?"=>0,"!"=>9 } Ops=Struct.new :ops, :op, :ac attr_reader :fld, :aggr def initialize(arr) @aggr=0 # 1:AGG, 2:SAG, 4:vaggr @show=false @temp=false @fld=nil @str=arr.join @ops=[] tmp=[] tmp[0]=Ops.new([],nil,0) i=0 if arr.length>1 and arr[1]==":" t=arr.shift if m=/^%(.*)/.match(t) @fld=m[1] @temp=true else @fld=t end arr.shift end for s in arr if s=="," or s==")" while i>0 and tmp[i].ac==0 tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i-1].ops+=tmp[i].ops i-=1 end if tmp[i].op=="?" if tmp[i].ac!=-3 $stderr.puts ME+"syntax error" exit end tmp[i].ops.push(tmp[i].op) tmp[i-1].ops+=tmp[i].ops i-=1 end end if s==":" while i>0 and tmp[i].ac==0 tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i-1].ops+=tmp[i].ops i-=1 end if tmp[i].op!="?" or tmp[i].ac!=-2 $stderr.puts ME+"syntax error" exit end tmp[i].ac=-3 next end if s=="?" if tmp[i].op=="?" $stderr.puts ME+"syntax error" exit end if tmp[i].op tmp[i].ops.push(tmp[i].op) tmp[i].op=nil end tmp[i+1]=Ops.new([],s,-2) i+=1 next end if s=="()" if !tmp[i].op or not FNS.include?(tmp[i].op) $stderr.puts ME+"syntax error" exit end if not FN0.include?(tmp[i].op) $stderr.puts ME+"function needs argument" exit end tmp[i].ops.push("1",tmp[i].op) tmp[i].op=nil next end if s=="," if i<1 or tmp[i-1].ac<2 $stderr.puts ME+"syntax error" exit end tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i].op=nil tmp[i-1].ac-=1 next end if s=="(" tmp[i+1]=Ops.new([],nil,-1) i+=1 next end if s==")" if tmp[i].ac!=-1 $stderr.puts ME+"parantheses mismatch" exit end tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i-1].ops+=tmp[i].ops i-=1 next if tmp[i].ac<1 if tmp[i].ac>1 $stderr.puts ME+"missing argument" exit end if i<1 tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i].op=nil tmp[i].ac=0 next end tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i-1].ops+=tmp[i].ops i-=1 next end if s=="!" if !tmp[i].op and tmp[i].ac==0 tmp[i].op=s next end tmp[i+1]=Ops.new([],s,0) i+=1 next end if FNS.include?(s) @aggr|=1 if AGG.include?(s) @aggr|=2 if SAG.include?(s) if ("%b" % @aggr).count("1")>1 $stderr.puts ME+"cannot mix aggregation functions" exit end @show=true if s=="show" c=0 c=1 if FN1.include?(s) c=2 if FN2.include?(s) if !tmp[i].op and tmp[i].ac==0 tmp[i].op=s tmp[i].ac=c next end tmp[i+1]=Ops.new([],s,c) i+=1 next end if ALL.include?(s) if !tmp[i].op tmp[i].op=s next end if OPL[s]>OPL[tmp[i].op] tmp[i+1]=Ops.new([tmp[i].ops.pop],s,0) i+=1 next end if OPL[s]==OPL[tmp[i].op] tmp[i].ops.push(tmp[i].op) tmp[i].op=s next end while i>0 and tmp[i].ac==0 tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i-1].ops+=tmp[i].ops i-=1 end tmp[i].ops.push(tmp[i].op) tmp[i].op=s next end tmp[i].ops.push(s) if tmp[i].op=="!" tmp[i].ops.push(tmp[i].op) tmp[i-1].ops+=tmp[i].ops i-=1 end end if tmp[i].ac==-1 $stderr.puts ME+"parantheses mismatch" exit end while i>1 and tmp[i].ac==0 tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i-1].ops+=tmp[i].ops i-=1 end if tmp[i].op=="?" tmp[i].ops.push(tmp[i].op) tmp[i-1].ops+=tmp[i].ops i-=1 end if i>1 $stderr.puts ME+"stack error" exit end if i==1 tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i-1].ops+=tmp[i].ops end @ops=tmp[0].ops @ops.push(tmp[0].op) if tmp[0].op end def self.parse(str) expr=[] arr=[] pc=0 for s in str.scan(/!=|==|<=|>=|\(\)|[\:\,\=\?\<\>\+\-\*\/\!\|\&\^\(\)]|"[^"]*"|[@%]?[a-zA-Z0-9\.]*/) next if s.empty? if s=="," and pc==0 e=Expr.new(arr) e.chk_vaggr(expr) expr<v) ? r : v if r s.push(v) ar.push(v) next end if o=="avg" if s.length<1 $stderr.puts ME+"expr: avg(): need argument" exit end a=s.pop if !a $stderr.puts ME+"expr: avg(): invalid argument" exit end v=a.to_f rs=av.shift rc=av.shift if rs and rc rs+=v rc+=1 v=rs/rc else rs=v rc=1 end s.push(v) ar.push(rs,rc) next end if o=="add" if s.length<1 $stderr.puts ME+"expr: add(): need argument" exit end a=s.pop if !a $stderr.puts ME+"expr: add(): invalid argument" exit end v=a.to_f r=av.shift r=0.0 if !r v=(bl) ? r+v : 0.0 s.push(v) ar.push(v) next end if o=="sub" if s.length<1 $stderr.puts ME+"expr: sub(): need argument" exit end a=s.pop if !a $stderr.puts ME+"expr: sub(): invalid argument" exit end v=a.to_f r=av.shift r=0.0 if !r v=(bl) ? r-v : v s.push(v) ar.push(v) next end if o=="icnt" if s.length<1 $stderr.puts ME+"expr: icnt(): need argument" exit end a=s.pop if !a $stderr.puts ME+"expr: icnt(): invalid argument" exit end v=a.split(/[, ]+/).compact.length s.push(v) next end if o=="show" if s.length<1 $stderr.puts ME+"expr: show(): need argument" exit end a=s.pop if !a s.push(0) next end if a.class==String a=(a.empty? or a=="-") ? 0 : 1 else a=a.to_i end v=(a==0) ? 0 : 1 s.push(v) next end if o=="pfxl" if s.length<1 $stderr.puts ME+"expr: pfxl(): need argument" exit end a=s.pop if !a or a.class!=String $stderr.puts ME+"expr: pfxl(): invalid argument" next end d=(a.include?(":")) ? 128 : 32 t=a.split("/") if t.length!=2 s.push(d) next end v=t[1].to_i s.push(v) next end if o=="pfxc" if s.length<1 $stderr.puts ME+"expr: pfxc(): need argument" exit end a=s.pop if !a or a.class!=String $stderr.puts ME+"expr: pfxc(): invalid argument" next end if a.include?(":") s.push(0) next end t=a.split("/") if t.length==2 v=32-t[1].to_i s.push(1<" b=s.pop.to_f a=s.pop.to_f v=(a>b) ? 1 : 0 s.push(v) next end if o==">=" b=s.pop.to_f a=s.pop.to_f v=(a>=b) ? 1 : 0 s.push(v) next end if /^"/.match(o) o=o.delete('"') s.push(o) next end if /^[0-9.]+$/.match(o) if o.include?(".") o=o.to_f else o=o.to_i end s.push(o) next end t=o.downcase if m=/^@(.*)/.match(t) if !n=vars["&"+m[1]] if !n=find_key(flds,m[1]) $stderr.puts ME+"expr: unknown var: "+t exit end vars["&"+m[1]]=n end if bl o="" if !o=vars["@"+n] else o="" if !o=flds[n] vars["@"+n]=o end elsif vars.has_key?(t) o="" if !o=vars[t] elsif flds.has_key?(t) o="" if !o=flds[t] elsif n=vars["&"+t] o="" if !o=flds[n] elsif n=find_key(flds,t) vars["&"+t]=n o="" if !o=flds[n] else $stderr.puts ME+"expr: unknown var: "+t exit end s.push(o) end if not av.empty? $stderr.puts ME+"syntax error" exit end av.replace(ar) v=s.pop if v.class==String and v.include?("\n") return v.split("\n")[0] end vars[@fld]=v if @fld return v end def name return @fld if @fld return @str end def temp? return @temp end def aggr?(*ba) for b in ba return true if (@aggr&b)==b end return false end def show? return @show end def chk_vaggr(expr) vars=[] for o in @ops next if FNS.include?(o) or ALL.include?(o) vars<7 and mp>7 m=m1=m2=nil s=s1=s2=nil tmp=ma.shift.split("-") if tmp[1] m1=tmp[0].to_i m2=tmp[1].to_i m=nil else m=tmp[0].to_i end f=false for t in sa.shift.split(",") if t=="*" f=true next end tmp=t.split("-") if tmp[1] s1=tmp[0].to_i s2=tmp[1].to_i s=nil else s=tmp[0].to_i end f=true if net_chk(s,m,s1,s2,m1,m2) end return false if not f sp-=8 mp-=8 end return false if sp15 and mp>15 m=m1=m2=nil s=s1=s2=nil if mz>0 m=0 mz-=1 else mt=ma.shift if !mt m=0 mz=8-mc-1 elsif mt.empty? m=0 mz=8-mc else tmp=mt.split("-") if tmp[1] m1=tmp[0].to_i(16) m2=tmp[1].to_i(16) m=nil else m=tmp[0].to_i(16) end end end f=false if sz>0 s=0 sz-=1 f=true if net_chk(s,m,s1,s2,m1,m2) else st=sa.shift if !st s=0 sz=8-sc-1 f=true if net_chk(s,m,s1,s2,m1,m2) elsif st.empty? s=0 sz=8-sc f=true if net_chk(s,m,s1,s2,m1,m2) else for t in st.split(",") if t=="*" f=true next end tmp=t.split("-") if tmp[1] s1=tmp[0].to_i(16) s2=tmp[1].to_i(16) s=nil else s=tmp[0].to_i(16) end f=true if net_chk(s,m,s1,s2,m1,m2) end end end return false if not f sp-=16 mp-=16 end return false if sp0 m=0 else mt=ma.shift if !mt or mt.empty? m=0 else tmp=mt.split("-") if tmp[1] m1=tmp[0].to_i(16)&bf m2=tmp[1].to_i(16)&bf m=nil else m=tmp[0].to_i(16)&bf end end end if sz>0 s=0 return true if net_chk(s,m,s1,s2,m1,m2) else st=sa.shift if !st or st.empty? s=0 return true if net_chk(s,m,s1,s2,m1,m2) else for t in st.split(",") if t=="*" return true end tmp=t.split("-") if tmp[1] s1=tmp[0].to_i(16)&bf s2=tmp[1].to_i(16)&bf s=nil else s=tmp[0].to_i(16)&bf end return true if net_chk(s,m,s1,s2,m1,m2) end end end return false end def net_chk(s,m,s1,s2,m1,m2) if m and s return (s==m) end if m and !s return (m>=s1 and m<=s2) end if !m and s return (s>=m1 and s<=m2) end return true if m1>=s1 and m1<=s2 or m2>=s1 and m2<=s2 return true if s1>=m1 and s1<=m2 or s2>=m1 and s2<=m2 return false end end class ByMatch attr_reader :fld def initialize(fld=nil) @fld=fld @mhs={} @mah=[] return end def add(s) k=s.downcase.split(":")[0] if @fld=="#" @mah<<"# "+k else @mhs[k]=true if k!="-" and k!="?" end return end def chk_fld(fields) return true if !@fld return true if fields.include?(@fld) return false end def match(h,l) if !@fld l.times do |i| next if !h[i] or h[i]=="-" or h[i]=="?" s=h[i].downcase.split(":")[0] return true^$im if @mhs[s] end return false^$im end return false if !s=h[@fld] s=s.downcase.split(":")[0] return true^$im if @mhs[s] return false^$im end def hmatch(hdr) str=/# [^ ]*/.match(hdr.downcase)[0] for s in @mah return true^$im if s==str end return false^$im end end class Line attr_reader :b, :la, :ca, :flds def initialize(l,b=nil) @sqs=nil @ca=[] if l.class==Line @la=[] @lflds=[] @b=l.b return end @la=[l] @lflds=[] @b=b end def new0 r=Line.allocate r.instance_exec(@b) do |b| @sqs=self @ca=[] @la=[] @lflds=[] @b=b end return r end def new1 return @sqs if @sqs r=Line.allocate r.instance_exec(@la.first,@lflds.first,@b) do |l,f,b| @sqs=self @ca=[] @la=[l] @lflds=[f] @b=b end r.mk_flds @sqs=r return r end def add(l,t=nil) @la<1 f=fields[i] @flds[f]=[] if !@flds[f] @flds[i]=[] if !@flds[i] if tmp[j].start_with?("!") str=tmp[j].sub(/^! ?/,"") @flds["comment"]<=1 and !m and l.b if t=l.match(m) if $sqs==0 bm=true res+=ba next end b=(t.b) ? t.b : t b=b.new1 if $sqs>=2 res<0 out.puts @hdr end $output_cnt+=1 return if $doh if $cnt c=res.length $aggr.add_cnt(c) if $aggr out.print "Count: ",c,"\n" return end out.puts @fld,@sep for l in res l.la.each { |s| out.puts s } end out.puts @sep end def output_e(res,agr,out) if res.empty? if $istty out.print "\e[#{$col}m",@hdr,"\e[m\n" else out.puts "\n" if $output_cnt>0 out.puts @hdr end $output_cnt+=1 agr.each { |l| out.puts l } return end buf=[] fld=[] col=[] for e in $expr next if e.temp? next if e.aggr?(1) fld<col[i] end z=r.length-1 lcs=r[z].length if lcs$rs $stderr.puts ME+"row length exceeded in expr output" exit end tmp="" tab=nil fld.length.times do |i| tmp+=tab if tab tmp+=fld[i] tab="\t"*(col[i]-(fld[i].length/8)) end fld=tmp for r in res tmp="" tab=nil r.length.times do |i| tmp+=tab if tab tmp+=r[i] tab="\t"*(col[i]-(r[i].length/8)) end buf<0 out.puts @hdr end $output_cnt+=1 out.puts fld,sep buf.each { |l| out.puts l } out.puts sep return if agr.empty? if $istty out.print "\e[#{$col}m",@hdr,"\e[m\n" else out.puts "\n" if $output_cnt>0 out.puts @hdr end $output_cnt+=1 agr.each { |l| out.puts l } end def output_j(out) return if !@res buf=[] fld=[] col=[] for f in @flds fld<col[i] end end z=@fields.length-1 for v in r.flds[z] lcs=v.length if lcs$rs $stderr.puts ME+"row length exceeded in join output" exit end tmp="" tab=nil fld.length.times do |i| tmp+=tab if tab tmp+=fld[i] tab="\t"*(col[i]-(fld[i].length/8)) end fld=tmp @res.each { |r| r.mk_la(col) } @res=_sort(@res) if $sort sep="-"*mcs if $istty out.print "\e[#{$col}m",@hdr,"\e[m\n" else out.puts "\n" if $output_cnt>0 out.puts @hdr end $output_cnt+=1 out.puts fld,sep for l in @res l.la.each { |s| out.puts s } end out.puts sep end def _dups(ra) res=[] for l in ra s=l.flds[0][0].downcase next if s.empty? or s=="-" or s=="?" next if !$db[s] res<y) end return 0 if x.empty? and y.empty? return -1 if x.empty? return 1 if y.empty? xa=x.scan(/([0-9]+)|([^0-9]+)/) ya=y.scan(/([0-9]+)|([^0-9]+)/) r=nil xi=xa.shift yi=ya.shift while xi and yi if xi[0] and yi[0] xv=xi[0].to_i yv=yi[0].to_i if(xv==yv) r=0 xi=xa.shift yi=ya.shift next end r=(xv>yv) ? 1 : -1 break end if xi[1] and yi[1] xv=xi[1] yv=yi[1] if(xv==yv) r=0 xi=xa.shift yi=ya.shift next end r=(xv<=>yv) break end break end return (x<=>y) if !r if r==0 r=-1 if !xi and yi r=1 if xi and !yi end return r end end class Block def output_csv(out) return if !@res str="" d=nil for s in @fields str+=d if d str+=s d="," end out.puts str c=@fields.length for l in @res l.parse(@fields,@ft) str="" d=nil c.times do |i| str+=d if d s=(l.flds[i]) ? l.fld(i) : "" s.gsub!(/"/,'""') s='"'+s+'"' if /[ ,"\n]/.match(s) str+=s d="," end out.puts str end end end class Aggr def output(out) return if $cnt and @cnt==0 return if @agr and @agr.empty? if $istty out.print "\e[#{$col}m+ Sum (Aggregated)\e[m\n" else out.puts "\n" if $output_cnt>0 out.puts "+ Sum (Aggregated)" end $output_cnt+=1 if $cnt out.print "Count: ",@cnt,"\n" return end for e in $expr next if not e.aggr?(1) out.print e.name,": ",@agr[e]._str(2),"\n" end end end def p_output(blocks) return if blocks.empty? if $csv if blocks.length>1 $stderr.puts ME+"output from multiple blocks is not supported" exit end end if $istty out=IO.popen("less -fqRFX","w") else out=$stdout end begin if $csv blocks.first.output_csv(out) elsif $doh and $cnt out.print "Count: ",blocks.length,"\n" else blocks.each { |b| b.output(out) } $aggr.output(out) if $aggr end rescue SystemExit out.close if out!=$stdout exit rescue Errno::EPIPE end out.close if out!=$stdout end def i_fld() buf=[] for l in $stdin l.chop! next if l.empty? next if l.start_with?("#!/") next if l.start_with?("! ") buf.push(l) end tmp=$fld.split(":") f1=tmp[0] f2=(tmp[1]) ? tmp[1] : tmp[0] m=ByMatch.new(f2) inbl=igbl=false hdr=fld=nil fields=ft=nil ca=[] for s in buf if inbl if s.start_with?("-----") for l in ca l.parse(fields,ft) next if !l.flds[f1] l.flds[f1].each { |v| m.add(v) } end inbl=igbl=false hdr=fld=nil fields=ft=nil ca=[] next end next if igbl if s.start_with?("\t") ca.last.add(s) else ca.push(Line.new(s)) end next end if s.start_with?("#") hdr=s next end if s.start_with?("-----") inbl=true fields,ft=get_fields(fld) igbl=true if not fields.include?(f1) next end fld=s if hdr end return m end def i_fldm() buf=[] for l in $stdin l.chop! next if l.empty? next if l.start_with?("#!/") next if l.start_with?("! ") buf.push(l) end m=ByMatch.new inbl=igbl=false hdr=fld=nil fields=ft=nil ca=[] for s in buf if inbl if s.start_with?("-----") for l in ca l.parse(fields,ft) next if !l.flds[$fldm] for t in l.flds[$fldm] t.split(" ").each { |v| m.add(v) } end end inbl=igbl=false hdr=fld=nil fields=ft=nil ca=[] next end next if igbl if s.start_with?("\t") ca.last.add(s) else ca.push(Line.new(s)) end next end if s.start_with?("#") hdr=s next end if s.start_with?("-----") inbl=true fields,ft=get_fields(fld) igbl=true if not fields.include?($fldm) next end fld=s if hdr end return m end def i_parse(buf) blocks=[] inbl=false hdr=fld=nil bl=nil tmp={} for s in buf if inbl if s.start_with?("-----") inbl=false hdr=fld=nil blocks.push(bl) if bl bl=nil next end next if !bl bl.addline(s) if $dups s=s.sub(/^ /,"").downcase.split(/\t+/)[0] next if s.empty? or s=="-" or s=="?" if tmp[s] $db[s]=true else tmp[s]=true end end next end if s.start_with?("#") hdr=s next end if s.start_with?("-----") inbl=true bl=Block.new(hdr,fld,s) if hdr and fld next end fld=s if hdr end return blocks end def load_file(buf,fn) begin f=File.open(fn,"r") rescue $stderr.puts ME+"unable to open file" exit end for l in f l.chop! next if l.empty? next if l.start_with?("#!/") if m=/^! blrs:([0-9]+)/.match(l) $rs=m[1].to_i next end next if l.start_with?("! ") buf.push(l) end f.close end def usage me=ME.delete(":") puts "Usage: "+me+"[-B ] [-S ] [] ..." puts " -i - include ignored blocks" puts " -n - check for duplicate items" puts " -c - count matches" puts " -a - show aggregated count and expr" puts " -v - invert match" puts " -w - display whole block on match" puts " -s - display only matching sub-lines (squash)" puts " -q - display only matching lines" puts " -h - display only block headers" puts " -C - display blocks matching RegEx " puts " -B - include blocks matching RegEx " puts " -S - skip blocks matching RegEx " puts " -Bj - include blocks matching RegEx (in join)" puts " -Sj - skip blocks matching RegEx (in join)" puts " -m - match both in blocks and lines" puts " -e - do simple math expr on every record" puts " -k - sort result by field" puts " -K - sort result by field (natural order)" puts " -by - select from file/stdin" puts " -fld - simple crossjoin by " puts " -chk - check if entry exist (crossjoin by )" puts " -j ,... - join multiple files by " exit end $istty=$stdout.isatty i=0 while i=ARGV.length and not $cat and $ma.empty? usage end if $cnt and not $expr.empty? $stderr.puts ME+"'-c' and '-e' options cannot be mixed" exit end if $sqs>=2 and not $expr.empty? $stderr.puts ME+"'-q' and '-e' options cannot be mixed" exit end ec=sc=ac=0 for e in $expr ec+=1 sc+=1 if e.show? ac+=1 if e.aggr>0 end if sc>0 and (sc!=ec or ac>0) $stderr.puts ME+"expr: show function cannot be mixed" exit end $aggr=nil if not $cnt and ac<1 $aggr.init if $aggr and ac>0 if $by buf=[] tmp=$by.split(":") fn=tmp[0] fld=(tmp[1]) ? tmp[1] : nil if fn!="-" begin f=File.open(fn,"r") rescue $stderr.puts ME+"unable to open file" exit end else f=$stdin end for l in f l.chop! next if l.empty? buf.push(l) end f.close if f!=$stdin m=ByMatch.new(fld) buf.each { |s| m.add(s) } buf=[] while i