I want to swap the appearance order of two lines in a file if needed.
For detail, please see the following minimal example:
...
here_comes_line_b
...
here_comes_line_a
...
Suppose the following should be the final result I want:
...
here_comes_line_a
...
here_comes_line_b
...
Furthermore, in my file, line_a and line_b only occur once.
What code should I use?
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
> Hi all,
>
> I want to swap the appearance order of two lines in a file if needed.
> For detail, please see the following minimal example:
>
> ...
> here_comes_line_b
> ...
> here_comes_line_a
> ...
>
>
> Suppose the following should be the final result I want:
>
> ...
> here_comes_line_a
> ...
> here_comes_line_b
> ...
>
> Furthermore, in my file, line_a and line_b only occur once.
>
> What code should I use?
perl -e '@_ = <STDIN>; ($_[$ARGV[0]-1], $_[$ARGV[1]-1])=($_[$ARGV[1]-1],
$_[$ARGV[0]-1]); print @_;' `grep -n here_comes_line_b your_file | cut -
d: -f1` `grep -n here_comes_line_a your_file | cut -d: -f1` < your_file
Either use an array to hold the whole file:
awk '{file[NR]=$0} /here_comes_line_a|here_comes_line_b/{swap[++cnt]=NR}
END{ tmp=file[swap[1]]; file[swap[1]]=file[swap[2]]; file[swap[2]]=tmp
for (i=1;i<=NR;i++) print file[i] }' file
or do two passes of the file:
awk 'NR==FNR{ if (/here_comes_line_a/) a[NR]=$0
else if (/here_comes_line_b/) b[NR]=$0
next
}
NR in a { $0 = b[NR] }
NR in b { $0 = a[NR] }
1' file file
Both untested.
Regards,
Ed.
Or, in one pass, store just line_b and the intermediate lines:
awk '
BEGIN { lineb = ""; counter = 0 }
/here_comes_line_b/ { lineb = $0; next }
/here_comes_line_a/ {
print
for (i = 1; i <= counter; i++) {print store[i]}
print lineb
lineb = ""
next
}
lineb != "" { store[++counter] = $0; next }
1 {print}
' file
--
Glenn Jackman
Write a wise saying and your name will live forever. -- Anonymous
$ txr -c \
'@(collect)
@ prolog
@ (until)
here_comes_line_b
@(end)
@ line_b
@(collect)
@ middle
@ (until)
here_comes_line_a
@(end)
@line_a
@(collect)
@ epilog
@(end)
@(output)
@ (repeat)
@ prolog
@ (end)
@ line_a
@ (repeat)
@ middle
@ (end)
@ line_b
@ (repeat)
@ epilog
@ (end)
@(end)' file
Simple logic: ``Collect the prologue material until the pattern for line
B is seen. Then collect that line. Collect the middle material unil
line A is seen. Collect that line. Then collect the trailing material.
Produce the material in the right order.''
I.e. we express the whole task as a pattern match for the entire file,
with embedded variable bindings, and regurgitate in a different order.
This txr query will nicely return a failed termination status and not produce
any output if the file does not match the pattern.
This could be done differently. Once line A is processed, the prologue,
line A, middle part and line B could be output immediately. Then,
an arbitrarily amount of epilogue could be copied using a constant
amount of memory. This would be useful if the two lines to be swapped
occur near the beginning of a huge file.
$ txr -c \
'@(collect)
@ prolog
@ (until)
here_comes_line_b
@(end)
@line_b
@(collect)
@ middle
@ (until)
here_comes_line_a
@(end)
@line_a
@(output)
@ (repeat)
@ prolog
@ (end)
@ line_a
@ (repeat)
@ middle
@ (end)
@ line_b
@(end)
@(collect)
@ epilog
@ (output)
@ epilog
@ (end)
@ (forget epilog)
@(end)' yourfile
Provided there's atleast one line between line_a & line_b we can do
this:
sed -e '
/\n/b
/re1/,/re2/!b
/re2/!H;/re1/h;/re2/!d
p;g;s/\n.*//;H;g;D
' yourfile
>Provided there's atleast one line between line_a & line_b we can do
>this:
In my case, the line_b sometimes just immediately appear before
line_a, i.e.,
line_b
line_a
How should your code be changed in order to deal with both cases?
>
>sed -e '
> /\n/b
> /re1/,/re2/!b
> /re2/!H;/re1/h;/re2/!d
> p;g;s/\n.*//;H;g;D
>' yourfile
So complicated for me to understand, any hints on your code?
Best regards.
>Suppose the following should be the final result I want:
>
>...
>here_comes_line_a
>...
>here_comes_line_b
>...
Oops, in fact, I want to obtain the following final result:
...
...
here_comes_line_a
here_comes_line_b
...
I.e., line_a should appear just immediately before line_b. Thanks
again.
So, is there actually any swapping involved or do you just want to save line_a
and print it before line_b?
Ed.
>So, is there actually any swapping involved or do you just want to save line_a
>and print it before line_b?
1- If line_b appears just immediately after line_a, then do nothing.
2- In other cases, move line_b to make sure it just immediately after
line_a
Best regards.
OK, so you don't actually want to swap anything, you want to find "line_b" and
move it to earlier in the file, specifically right after "line_a", right? try this:
tac file | awk '/line_b/{b=$0 RS; next} /line_a/{printf "%s",b} 1' | tac
Ed.
>OK, so you don't actually want to swap anything, you want to find "line_b" and
>move it to earlier in the file, specifically right after "line_a", right? try this:
>
>tac file | awk '/line_b/{b=$0 RS; next} /line_a/{printf "%s",b} 1' | tac
Let me give a minimal example:
$ cat test_swap2lines.txt
some_others1
line_b
some_others2
line_a
some_others3
I want to obtain the following result:
some_others1
some_others2
line_a
line_b
some_others3
But, your above code will give something like this:
$ tac test_swap2lines.txt | awk '/line_b/{b=$0 RS; next}
/line_a/{printf "%s",b
} 1' | tac
some_others1
some_others2
line_a
some_others3
In the result given by your code, the line_b is removed from the
result, which is not the result I want.
Using Ruby:
# Read the file.
gets(nil)
# Remove Line B.
sub!( /.*line_b.*\n/, "" )
# Remember Line B.
b = $&
# Insert Line B after Line A.
puts sub( /line_a.*\n/, '\&' + b )
>Using Ruby:
>
># Read the file.
>gets(nil)
># Remove Line B.
>sub!( /.*line_b.*\n/, "" )
># Remember Line B.
>b = $&
># Insert Line B after Line A.
>puts sub( /line_a.*\n/, '\&' + b )
Very good, thanks a lot, you let me know a succinct and powerful tool.
>Using Ruby:
>
># Read the file.
>gets(nil)
># Remove Line B.
>sub!( /.*line_b.*\n/, "" )
># Remember Line B.
>b = $&
># Insert Line B after Line A.
>puts sub( /line_a.*\n/, '\&' + b )
Is it possible to edit the input file in place by uisng ruby, i.e.,
just like the -i OPTION for sed?
Yes.
ruby -i.bak -pe'gsub( "c", "x" )' data
This makes a backup file named "data.bak" and replaces
each "c" with "x".
>Yes.
>ruby -i.bak -pe'gsub( "c", "x" )' data
>
>This makes a backup file named "data.bak" and replaces
>each "c" with "x".
Good, thanks again. I want to do the inplace edit without backup file
generated. What option should I use?
As you have stated that you want to align line_a then line_b no matter
where they come in the file.
& they occur just once in the file.
sed -ne '
/line_a/ba
/line_b/bb
p;d
:a
p;n;/line_b/bspin
:notb
/line_b/!{
H;n
bnotb
}
p;g;s/.//;bspin
:b
h;n
:nota
/line_a/!{
p;n
bnota
}
G;bspin
:spin
p;$d;n
bspin
' yourfile
-- Rakesh
> On Wed, 25 Nov 2009 21:02:04 -0600, Ed Morton <morto...@gmail.com>
> wrote:
>
>>So, is there actually any swapping involved or do you just want to save line_a
>>and print it before line_b?
>
> 1- If line_b appears just immediately after line_a, then do nothing.
>
> 2- In other cases, move line_b to make sure it just immediately after
> line_a
>
awk 'BEGIN{a=b=i=0}/line_a/{a=i;} /line_b/{b=i;} {l[i++]=$0;} END{t=l[b];for(i=0;i<NR;i++){if(i!=b)print l[i];if(i==a)print t;}}' your_file
--
Live like a child, think like the god.
> On Thu, 26 Nov 2009 13:37:15 -0800 (PST), w_a_x_man
> <w_a_...@yahoo.com> wrote:
>
>>Yes.
>>ruby -i.bak -pe'gsub( "c", "x" )' data
>>
>>This makes a backup file named "data.bak" and replaces
>>each "c" with "x".
>
> Good, thanks again. I want to do the inplace edit without backup file
> generated. What option should I use?
'-i' without anything, the same as 'sed'.
>'-i' without anything, the same as 'sed'.
Does the gets(nil) statement work with '-i' switch? In my case, I
always meet the error like this:
test.rb:9:in `gets': Can't do inplace edit without backup (fatal)
from test.rb:9
Why?
>awk 'BEGIN{a=b=i=0}/line_a/{a=i;} /line_b/{b=i;} {l[i++]=$0;} END{t=l[b];for(i=0;i<NR;i++){if(i!=b)print l[i];if(i==a)print t;}}' your_file
This works smooth, but I want to do the in place/inline edit on the
file. does awk support in place/inline edit just sed's '-i' switch?
It's rarely an inline edit what sed does; you just don't see the
temporary file.
No, awk has no -i option to hide the temporary file from you.
But you can put the awk call in a shell script if you don't want
to see the temporary.
Janis
>
> Best regards.
> On Fri, 27 Nov 2009 23:42:27 +0800, WANG Cong
> <xiyou.w...@gmail.com> wrote:
>
>>'-i' without anything, the same as 'sed'.
>
> Does the gets(nil) statement work with '-i' switch? In my case, I
> always meet the error like this:
>
> test.rb:9:in `gets': Can't do inplace edit without backup (fatal)
> from test.rb:9
>
> Why?
Are you using it on Window$? I have never seen such thing on Linux.
If so, you have to provide the backup file for '-i'.
>Are you using it on Window$? I have never seen such thing on Linux.
>If so, you have to provide the backup file for '-i'.
Cygwin in my case.
Best regards.
Perhaps not what's desired, but meets the "specification" (and a bit
more). It watches for a (here_comes_line_a) and b (here_comes_line_b)
lines. If a b line is the first a/b line preceeding an a line, it
will
swap them, unless the first a/b line preceeding that b line is an a
line. E.g. an aba sequence wouldn't change to aab, but a bba sequence
would change to bab.
sed -ne '
/^here_comes_line_[ab]$/!{
p
d
}
/^here_comes_line_a$/{
:a
${
p
q
}
N
/\nhere_comes_line_b$/{
p
d
}
/\nhere_comes_line_a$/{
:l
/\n/P
s/^[^\n]*\n//
tl
}
ba
}
/^here_comes_line_b$/{
:b
${
p
q
}
N
/\nhere_comes_line_a$/{
s/^here_comes_line_b\nhere_comes_line_a$/here_comes_line_a
\nhere_comes_line_b/
s/^here_comes_line_b\n\(.*\)\nhere_comes_line_a$/here_comes_line_a\n
\1\nhere_comes_line_b/
p
d
}
/\nhere_comes_line_b$/{
:m
/\n/P
s/^[^\n]*\n//
tm
}
bb
}
'
perl -F'\n' -00pale '
() = map { /line_[ab]/ && do{$h{$&}=[$i,$_];};++$i; } @F;
$f = ($h{line_a}->[0] > $h{line_b}->[0]) ? -1 : 0;
splice(@F, $f+$h{line_a}->[0], 1, $h{line_a}->[1], splice(@F, $h
{line_b}->[0], 1));
$_ = join "\n", @F;
' yourfile
Note: empty lines will be discarded. This may or may not be what you
want.
That doesn't appear to work correctly:
$ echo "one
two
line_b
four
five
six
line_a
eight
nine" | perl -F'\n' -00pale'
() = map { /line_[ab]/ && do{$h{$&}=[$i,$_];};++$i; } @F;
$f = ($h{line_a}->[0] > $h{line_b}->[0]) ? -1 : 0;
splice(@F, $f + $h{line_a}[0], 1, $h{line_a}->[1], splice(@F,
$h{line_b}->[0], 1));
$_ = join "\n", @F;
'
one
two
four
five
six
line_a
line_b
eight
nine
That doesn't properly exchange 'line_a' and 'line_b'. This appears to
work better:
$ echo "one
two
line_b
four
five
six
line_a
eight
nine" | perl -F'\n' -0777pae'
$F[ $_ ] =~ /line_([ab])/ and $h{ $1 } = $_ for 0 .. $#F;
@F[ @h{ qw/a b/ } ] = @F[ @h{ qw/b a/ } ];
$_ = join "\n", @F;
'
one
two
line_a
four
five
six
line_b
eight
nine
John
--
The programmer is fighting against the two most
destructive forces in the universe: entropy and
human stupidity. -- Damian Conway
Thanks for pointing it out!
I was working under the assumption given by the OT as:
>>> 1- If line_b appears just immediately after line_a, then do nothing.
>>> 2- In other cases, move line_b to make sure it just immediately after line_a.
He doesn't want to swap the positions of line_a <=> line_b, rather he
just wants to place line_b under line_a.
So that's the basic premise I used to write it.
But I had a few questions of my own.
1) What's the difference between perl -00pae & perl -0777pae?
I used perl -00 to intend "slurping". The perl manual lists that
we use -0777 as you have used also.
2) In the line that I wrote: () = map { /line_[ab]/ && do{$h{$&}=[$i,
$_];};++$i; } @F;
I remember being forced to put that do{} construct in otherwise
it gave an error. Whereas, if I take a look
at what you wrote: $F[ $_ ] =~ /line_([ab])/ and $h{ $1 } = $_
for 0 .. $#F;
Why didn't perl blurt out in this case?
Thanks for your patience,
TIA
--Rakesh
The -00 option uses the "\0" (the ASCII NUL) character as the Input
Record Separator which should not appear in a text file, but might, and
-0777 sets the Input Record Separator to an invalid value so it will
always properly slurp the entire file.
> 2) In the line that I wrote: () = map { /line_[ab]/ && do{$h{$&}=[$i,
> $_];};++$i; } @F;
> I remember being forced to put that do{} construct in otherwise
> it gave an error. Whereas, if I take a look
> at what you wrote: $F[ $_ ] =~ /line_([ab])/ and $h{ $1 } = $_
> for 0 .. $#F;
> Why didn't perl blurt out in this case?
The '&&' operator has higher precedence than the '=' operator which will
produce a error message:
$ perl -le' $ARGV[0] && $x = 123 ' 789
Can't modify logical and (&&) in scalar assignment at -e line 1, at EOF
Execution of -e aborted due to compilation errors.
Whereas the 'and' operator has lower precedence than the '=' operator so
it will work properly:
$ perl -le' $ARGV[0] and $x = 123 ' 789
If you want to use the '&&' operator you will have to enclose the
assignment in parentheses:
$ perl -le' $ARGV[0] && ( $x = 123 ) ' 789
The -00 option uses the "\0" (the ASCII NUL) character as the Input
Record Separator which should not appear in a text file, but might, and
-0777 sets the Input Record Separator to an invalid value so it will
always properly slurp the entire file.
> 2) In the line that I wrote: () = map { /line_[ab]/ && do{$h{$&}=[$i,
> $_];};++$i; } @F;
> I remember being forced to put that do{} construct in otherwise
> it gave an error. Whereas, if I take a look
> at what you wrote: $F[ $_ ] =~ /line_([ab])/ and $h{ $1 } = $_
> for 0 .. $#F;
> Why didn't perl blurt out in this case?
The '&&' operator has higher precedence than the '=' operator which will
produce a error message:
$ perl -le' $ARGV[0] && $x = 123 ' 789
Can't modify logical and (&&) in scalar assignment at -e line 1, at EOF
Execution of -e aborted due to compilation errors.
Whereas the 'and' operator has lower precedence than the '=' operator so
it will work properly:
$ perl -le' $ARGV[0] and $x = 123 ' 789
If you want to use the '&&' operator you will have to enclose the
assignment in parentheses:
$ perl -le' $ARGV[0] && ( $x = 123 ) ' 789
The -00 option uses the "\0" (the ASCII NUL) character as the Input
Record Separator which should not appear in a text file, but might, and
-0777 sets the Input Record Separator to an invalid value so it will
always properly slurp the entire file.
> 2) In the line that I wrote: () = map { /line_[ab]/ && do{$h{$&}=[$i,
> $_];};++$i; } @F;
> I remember being forced to put that do{} construct in otherwise
> it gave an error. Whereas, if I take a look
> at what you wrote: $F[ $_ ] =~ /line_([ab])/ and $h{ $1 } = $_
> for 0 .. $#F;
> Why didn't perl blurt out in this case?
The '&&' operator has higher precedence than the '=' operator which will
produce a error message:
$ perl -le' $ARGV[0] && $x = 123 ' 789
Can't modify logical and (&&) in scalar assignment at -e line 1, at EOF
Execution of -e aborted due to compilation errors.
Whereas the 'and' operator has lower precedence than the '=' operator so
it will work properly:
$ perl -le' $ARGV[0] and $x = 123 ' 789
If you want to use the '&&' operator you will have to enclose the
assignment in parentheses:
$ perl -le' $ARGV[0] && ( $x = 123 ) ' 789
John
The -00 option uses the "\0" (the ASCII NUL) character as the Input
Record Separator which should not appear in a text file, but might, and
-0777 sets the Input Record Separator to an invalid value so it will
always properly slurp the entire file.
> 2) In the line that I wrote: () = map { /line_[ab]/ && do{$h{$&}=[$i,
> $_];};++$i; } @F;
> I remember being forced to put that do{} construct in otherwise
> it gave an error. Whereas, if I take a look
> at what you wrote: $F[ $_ ] =~ /line_([ab])/ and $h{ $1 } = $_
> for 0 .. $#F;
> Why didn't perl blurt out in this case?
The '&&' operator has higher precedence than the '=' operator which will
produce a error message:
$ perl -le' $ARGV[0] && $x = 123 ' 789
Can't modify logical and (&&) in scalar assignment at -e line 1, at EOF
Execution of -e aborted due to compilation errors.
Whereas the 'and' operator has lower precedence than the '=' operator so
it will work properly:
$ perl -le' $ARGV[0] and $x = 123 ' 789
If you want to use the '&&' operator you will have to enclose the
assignment in parentheses:
$ perl -le' $ARGV[0] && ( $x = 123 ) ' 789
The -00 option uses the "\0" (the ASCII NUL) character as the Input
Record Separator which should not appear in a text file, but might, and
-0777 sets the Input Record Separator to an invalid value so it will
always properly slurp the entire file.
> 2) In the line that I wrote: () = map { /line_[ab]/ && do{$h{$&}=[$i,
> $_];};++$i; } @F;
> I remember being forced to put that do{} construct in otherwise
> it gave an error. Whereas, if I take a look
> at what you wrote: $F[ $_ ] =~ /line_([ab])/ and $h{ $1 } = $_
> for 0 .. $#F;
> Why didn't perl blurt out in this case?
The '&&' operator has higher precedence than the '=' operator which will
produce a error message:
$ perl -le' $ARGV[0] && $x = 123 ' 789
Can't modify logical and (&&) in scalar assignment at -e line 1, at EOF
Execution of -e aborted due to compilation errors.
Whereas the 'and' operator has lower precedence than the '=' operator so
it will work properly:
$ perl -le' $ARGV[0] and $x = 123 ' 789
If you want to use the '&&' operator you will have to enclose the
assignment in parentheses:
$ perl -le' $ARGV[0] && ( $x = 123 ) ' 789
John
Thanks once again John for your masterly explanations.!
Has it ever crossed your mind (or O'Reilly's) to gather your perl-one-
liner solutions from all over comp.unix.shell, comp.perl, etc.
& put them in a book form?
Thanks,
--Rakesh