开发者

Move line(s) to follow another line in a file

I got a file that has a line in the file like this:

check=('78905905f5a4ed82160c327f3fd34cba')

I'd like to be able to move this line to follow a line that looks like this:

files=('somefile.txt')

The array though at times that can span multiple lines, for example:

files=('somefile.txt'
       'file2.png'
       'another.txt'
       'andanother...')

text
in between

check=('78905905f5a4ed82160c327f3fd34cba'
       '5277a9164001a4276837b59dade26af2'
       '3f8b60b6fbb993c18442b62ea661aa6b')

The array/line always ends in a ) and no text in between will contain a closed parenthesis.

I got some advice that awk can do this:

awk '/files/{
    f=0
    print $0
    f开发者_运维技巧or(i=1;i<=d;i++){ print a[i]  }
    g=0
    delete a # remove array after found
    next
}
/check/{ f=1; g=1 }
f{ a[++d]=$0 }
!g' file

This will only span one line though. I was told to expand the search:

awk '/source/ && /\)$/{
    f=0
    print $0
    for(i=1;i<=d;i++){ print a[i]  }
    g=0
    delete a # remove array after found
    next
}
/md5sum/ && /\)$/{ f=1; g=1 }
f{ a[++d]=$0 }
!g'

Just learning awk so I'd appreciate help with this. Or if there is another tool that can do this, I'd like to hear about it. Someone told me that 'ed' these types of capabilities.


To answer your last question first, yes, awk is the typical Unix tool for this, other candidates are the incredibly powerful Perl, Python, or .. my favorite .. Ruby. One advantage of awk is that it's always there; it's part of the base system. Another way to solve this kind of problem is with an editor script that controls ed(1) or ex(1).

Ok, new program for the revised question. This program will move the "check" lines either up or down as necessary so that they follow the "files" lines.

BEGIN {
  checkAt = 0
  filesAt = 0
  scanning = 0
}

/check=\(/ {
  checkAt = NR
  scanning = 1
}

/files=\(/ {
  filesAt = NR
  scanning = 1
}

/)$/ {
  if (scanning) {
    if (checkAt > filesAt) {
      checkEnd = NR
    } else {
      filesEnd = NR
    }
    scanning = 0
  }
}

{
  lines[NR] = $0
}

END {
  for (i = 1; i <= NR; ++i) {
    if (checkAt <= i && i <= checkEnd) {
      continue
    }
    print lines[i]
    if (i == filesEnd) {
      for (j = checkAt; j <= checkEnd; ++j) {
        print lines[j]
      }
    }
  }
}


I looked in to doing this with Awk, but it looked like you wouldn't really get anything clever out of it, it would just be the same logic, but with some Awk pain to go with it, so I did it in Perl :)

#!/usr/bin/perl

open(IN, $ARGV[0]) || die("Could not open file: " . $ARGV[0]);

my $buffer="";

foreach $line (<IN>) {
        if ($line =~ /^check=/) {
                $flag = 1;
                $buffer .= $line;
        } elsif ($flag == 1 && $line =~/\)/) {
                $flag = 0;
                $buffer .= $line;
        } elsif ($flag == 1) {
                $buffer .= $line;
        } elsif ($flag == 0 && $line =~ /^files=/) {
                $flag = 2;
                print $line;
        } elsif ($flag == 2 && $line =~ /\)/) {
                $flag = 0;
                print $line;
                if (length($buffer) > 0) {
                        print $buffer;
                        $buffer = "";
                }
        } else {
                print $line;
        }

}

And the output :)

Chill:~ rus$ cat test check=('78905905f5a4ed82160c327f3fd34cba'
       '5277a9164001a4276837b59dade26af2'
       '3f8b60b6fbb993c18442b62ea661aa6b')

text in between

files=('somefile.txt'
       'file2.png'
       'another.txt'
       'andanother...')

asdasdasd

check=('78905905f5a4ed82160c327f3fd34cba'
       '5277a9164001a4276837b59dade26af2'
       '3f8b60b6fbb993c18442b62ea661aa6b')

text in between

files=('somefile.txt'
       'file2.png'
       'another.txt'
       'andanother...')

asdsd

check=('78905905f5a4ed82160c327f3fd34cba'
       '5277a9164001a4276837b59dade26af2'
       '3f8b60b6fbb993c18442b62ea661aa6b')

text in between

files=('somefile.txt'
       'file2.png'
       'another.txt'
       'andanother...')

Chill:~ rus$ ./t.pl test

text in between

files=('somefile.txt'
       'file2.png'
       'another.txt'
       'andanother...') check=('78905905f5a4ed82160c327f3fd34cba'
       '5277a9164001a4276837b59dade26af2'
       '3f8b60b6fbb993c18442b62ea661aa6b')

asdasdasd


text in between

files=('somefile.txt'
       'file2.png'
       'another.txt'
       'andanother...') check=('78905905f5a4ed82160c327f3fd34cba'
       '5277a9164001a4276837b59dade26af2'
       '3f8b60b6fbb993c18442b62ea661aa6b')

asdsd


text in between

files=('somefile.txt'
       'file2.png'
       'another.txt'
       'andanother...') check=('78905905f5a4ed82160c327f3fd34cba'
       '5277a9164001a4276837b59dade26af2'
       '3f8b60b6fbb993c18442b62ea661aa6b')

ta da ?! :D


Here's how to do it with sed:

sed -e /^check=(/,/)/{H;d} -e /)/{G;s/\n//} < filename

This assumes that there are no right parentheses after the "files=..." If there are then you'll need more precision:

sed -e /^check=(/,/)/{H;d} -e /^files=(/,/)/{/)/{G;s/\n//}} < filename

EDIT:
Working in bash? All right, try this:

sed -e /^check=(/,/)/H -e /^check=(/,/)/d -e '/)/G;s/\n//' < filename

This seems to work, but it's not clear to me why this variant and not a few other obvious ones. This dance-of-the-special-characters is always a problem with regexs.


@todd, I seem to have left you in the lurch after providing you the awk solution haven't i. ? :). here's another method, this time not using method of flags. there are some loose ends (hint: check the patterns p,q and output again) that i leave it to you to tidy up.

gawk 'BEGIN{
    RS="check=[(]"
    q="files=(.*\047)"  # pattern to replace files= part
    p=".*(files=(.*\047)).*" # to get the whole files= part to variable
}
NR>1{
    b=gensub(p, "\\1","g",$0) # get the files=part to var b
    printf "%s\n\n",b    
    printf "check=("
    gsub(q,"",$0)
    print $0
}' file

NB: gensub is specific to gawk so if you have gawk, then that's alright

output

$ more file
check=('5277a9164001a4276837b59dade26af2'
       '5277a9164001a4276837b59dade26af2'
       '3f8b60b6fbb993c18442b62ea661aa6b')

text in between one

files=('somefile1.txt'
       'file1.png'    
       'another1.txt' 
       'andanother1...')

asdasdasd blah blah

check=('78905905f5a4ed82160c327f3fd34cba'
       '5277a9164001a4276837b59dade26af2'
       '3f8b60b6fbb993c18442b62ea661aa6b')

text in between  two

files=('somefile2.txt'
       'file2.png'    
       'another2.txt' 
       'andanother2...')

asdsd blaasdf aslasdfaslj aslfjsldfsa 123e12

check=('78905905fblah blah5a4ed82160c327f3fd34cba'
       '5277a9164001a4276837b59dade26af2'         
       '3f8b60b6fbb993c18442b62ea661aa6b')        

text in between

files=('somefile3.txt'
       'file3.png'    
       'another3.txt' 
       'andanother3...')

$ ./shell.sh
files=('somefile1.txt'             
       'file1.png'                 
       'another1.txt'              
       'andanother1...'            

check=('5277a9164001a4276837b59dade26af2'
       '5277a9164001a4276837b59dade26af2'
       '3f8b60b6fbb993c18442b62ea661aa6b')

text in between one

)

asdasdasd blah blah


files=('somefile2.txt'
       'file2.png'
       'another2.txt'
       'andanother2...'

check=('78905905f5a4ed82160c327f3fd34cba'
       '5277a9164001a4276837b59dade26af2'
       '3f8b60b6fbb993c18442b62ea661aa6b')

text in between  two

)

asdsd blaasdf aslasdfaslj aslfjsldfsa 123e12


files=('somefile3.txt'
       'file3.png'
       'another3.txt'
       'andanother3...'

check=('78905905fblah blah5a4ed82160c327f3fd34cba'
       '5277a9164001a4276837b59dade26af2'
       '3f8b60b6fbb993c18442b62ea661aa6b')

text in between

)


This might work for you:

 sed ':a;$!N;/^files=.*\ncheck=/{/.*)$/!ba;s/\([^)]*)\)\(.*\)\(\ncheck=.*\)/\1\3\2/p;d};/^files=.*/ba;P;D' file
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜