4

I'm using the ubuntu terminal, I need to move a specific line in a file ( 11th position ) to the first line and then transfer the final result into a new file. The original file contains hundreds of lines.
So far I tried to use the sed command tool, but I'm not achieving what I want. This is what I got so far :

[mission 09] $ sed -n -e '1p' -e '11p' bonjour > bonjour2

But it only displays the first and 11th line into the new file. But I want the new file to have the revised position wanted with the rest of the original lines.

Input :

English: Hello 
Turkish: Marhaba 
Italian: Ciao 
German: Hallo 
Spanish: Hola 
Latin: Salve 
Greek: chai-ray 
Welsh: Helo 
Finnish: Hei 
Breton: Demat 
French: Bonjour 

Desired output

French: Bonjour 
English: Hello 
Turkish: Marhaba 
Italian: Ciao 
German: Hallo 
Spanish: Hola 
Latin: Salve 
Greek: chai-ray 
Welsh: Helo 
Finnish: Hei 
Breton: Demat 

Any suggestion ?

3
sed -n '1h;2,10H;11G;11,$p' 

First line, copy h because of new line, then append H until 10.

At line 11, get the hold space

From 11 to end, print.


]#  sed -n '1h;2,10H;11G;11,$p' bonj 
French: Bonjour 
English: Hello 
Turkish: Marhaba 
Italian: Ciao 
German: Hallo 
Spanish: Hola 
Latin: Salve 
Greek: chai-ray 
Welsh: Helo 
Finnish: Hei 
Breton: Demat 

This is nicer:

]# seq 20 | sed -n '1h;2,10H;11G;11,$p' 
11
1
2
3
4
5
6
7
8
9
10
12
13
14
15
16
17
18
19
20

I take your example:

]# sed -e 1p -e 11p -n bonj 
English: Hello 
French: Bonjour 

...with the -n switch at the end just to show it counts for both expressions.

I have also -n, and then 1h;2,10H, which should be just 1,10H, which is a range of line numbers and a "hold" (store) command. Nothing gets printed, yet.

11,$p is another range. On line 11 it prints what `11G' just got back from hold (ie 1-10) and appended to line 11.

Lines 12 until $ just print themselves, because of -n.


I should make two -e like you:

sed -n -e '1h;2,10H' -e '11G;11,$p'

From 1,10 is hold, from 11,$ is print.


Line 11 has the G first, then the p. It matters, because:

]# seq 20 | sed -n -e '1h;2,10H' -e '11,$p;11G'
11
12
13
14
15
16
17
18
19
20

Here line 12 wipes out what line 11 has gotten after printing.


As a function with params

Always line eleven is boring with a function "putfirst":

]# declare -f putfirst 
putfirst () 
{ 
    e="1h;2,$(($1-1))H;${1}G;${1},\$p";
    sed -ne "$e" $2
}

Two steps: string generation, then the sed call. "$" has two meanings: "p" is not a variable!

This is the lowest number that works:

]# seq 7 | putfirst 3 
3
1
2
4
5
6
7

Or with the original "bonj" file:

]# putfirst 4 bonj | putfirst 6 |head -4
Latin: Salve 
German: Hallo 
English: Hello 
Turkish: Marhaba 

This is two seds in a row, but now doing two operations.


Perl

perl -ne '$n++; $h.=$_ if $n<11; print $_.$h if $n==11; print if $n>11' <(seq 20)

And as some script, which takes a file name and needs no option:

$want=11 ;
while (<>) {
    $n++ ;
    if ($n < $want)            # before $want: store line
        { $lowlines .= $_ ; 
          next }               # next line (avoids 'else')  
    if ($n == $want)           # at line $want: append stored lines to $_    
        { $_ .= $lowlines }   
    print ;                    # print $_ for $n not less than $want
}

AWK (stolen from Ed (not the editor!))

NR < 11 { 
    buf[NR] = $0; next 
}
NR >=11 {
    print
    if (NR == 11) {
        for (i=1; i<11; i++) { print buf[i] }
    }
}

I used NR instead of incrementing n, and made the flow more explicit. Same "trick": the next simplifies downstream.


With perl -n

$n++ ;
$tmp = $tmp . $_  if $n < 11  ; 
print  $_ . $tmp  if $n == 11 ;
print  $_         if $n > 11  ;

This is the best format. Symmetrical.

|improve this answer|||||
9

Using ed (the line-editor that sed and grep are derived from):

printf '%s\n' '11m0' 'w bonjour2' 'q' | ed -s bonjour

This applies the editing command 11m0 to the file which moves line 11 to before the first line. It then writes the resulting document to the file bonjour2 and quits.

Alternatively:

printf '%s\n' '11m0' ',p' 'Q' | ed -s bonjour >bonjour2

... which instead of writing to a specific file using ed commands just prints the whole file to standard output. The result is then redirected to a new filename. The ,p command (short for 1,$p) will output the whole document to standard output and Q will force quit (even though the document has changed).

To make the change in the document in-place (i.e. change the original file), write the result back to the file itself:

printf '%s\n' '11m0' 'wq' | ed -s bonjour

Example run of one of the above variations:

$ printf '%s\n' '11m0' ',p' 'Q' | ed -s bonjour >bonjour2
$ cat bonjour2
French: Bonjour
English: Hello
Turkish: Marhaba
Italian: Ciao
German: Hallo
Spanish: Hola
Latin: Salve
Greek: chai-ray
Welsh: Helo
Finnish: Hei
Breton: Demat

To always move the last line to the top, use $m0 in place of 11m0. To always move the line that starts with the string French:, use /^French:/m0.

|improve this answer|||||
  • what does printf '%s\n' do ? – ForzaTekk Feb 22 at 20:35
  • 1
    @ForzaTekk printf prints all the arguments from its 2nd argument onward according the the formatting in its first argument. The format string %s\n specifies that each string should be printed as a string (duh) with a newline after it. The ed editor reads editing commands from standard input and expects these to be on separate lines, that's why. Run the printf command by itself (up to, but not including the |) to see what gets passed to ed. Also try printf 'name: %s\n' * (without the ed bit, as an educational exercise). – Kusalananda Feb 22 at 20:43
3

If I understand correctly, you just want to move the 11th line to the first line.

So if your original looked like

Original line 1
Original line 2
...
Original line 18
Original line 19
Original line 20

Then the result would be

Original line 11
Original line 1
Original line 2
...
Original line 9
Original line 10
Original line 12
Original line 13
...
Original line 19
Original line 20

This can be achieved with two passes through the file, but the first pass can be short-cutted so it's not too horrendous

( sed -n '11 {p;q}' file ; sed 11d file ) > newfile

Basically the first sed command only prints line 11 and then quits. The second sed command prints every line except line 11.

The result is that, effectively, line 11 is moved to the front of the new file.

|improve this answer|||||
2

After posting my original answer below, I realized now that having an array was overkill and all you really need is:

awk '
NR < 11 { buf = buf $0 ORS; next }
{
    print
    printf "%s", buf
    buf = ""
}
' file > new_file

e.g.:

$ seq 15 | awk '
NR < 11 { buf = buf $0 ORS; next }
{
    print
    printf "%s", buf
    buf = ""
}
'
11
1
2
3
4
5
6
7
8
9
10
12
13
14
15

Original answer using an array instead of a scalar variable for the buffer:

awk '
NR < 11 { buf[++bufSz] = $0; next }
{
    print
    for (i=1; i<=bufSz; i++) {
        print buf[i]
    }
    bufSz = 0
}
' file > new_file

e.g.:

$ seq 15 | awk '
NR < 11 { buf[++bufSz] = $0; next }
{
    print
    for (i=1; i<=bufSz; i++) {
        print buf[i]
    }
    bufSz = 0
}
'
11
1
2
3
4
5
6
7
8
9
10
12
13
14
15
|improve this answer|||||
  • I could only use the shell for the moment. I'm learning the basics of computer systems administration. – ForzaTekk Feb 22 at 20:36
  • 2
    I'm not sure what you're getting at. awk is a standard UNIX tool that comes on all UNIX boxes, just like sed, grep, ed, and cat. Trying to not use a standard UNIX tool like awk for this and use shell built-ins instead, if that's what you're hoping to do, would be completely inappropriate. An awk solution is no less a "shell" solution than one using sed, or ed or any other non-builtin but standard UNIX tool. – Ed Morton Feb 23 at 0:16
  • +1 browsing this on my phone, I originally thought: "no way; this prints 11, 22, 33 etc, out of order !" Then I noticed bufSz = 0. So it's a bit hacky compared to Kusalananda's solution, but hey,... it works... May I suggest you print a few lines of explanation to make your answer more accessible to begineers ? Just saying. – Cbhihe Feb 23 at 17:52
  • The main differences between this and the ed solution are that the ed solution will read the whole input file into memory before working on it while the awk solution just stores 11 lines. and the ed solution requires a file to work on while the awk one can work on a stream, just like sed can. I tweaked my answer just now to make it even simpler but honestly I don't think either script need any explanation, it's extremely obvious what they're doing. – Ed Morton Feb 23 at 21:38
0

With POSIX sed:

$ sed -e '1,11!b' -e '11!H;1h;11!d;G' file

Using perl:

$ perl -lne '
   push(@A,$_),next if $. < 11;
   print for $_, splice @A;
' file

Using bash:

$ (head -n 11 - | tee log | tail -n 1 -; head -n 10 log; cat -;) < file
|improve this answer|||||
0

A simpler sed than the ones posted (POSIX, no labels):

seq 20 | sed '1,10{H;1h;d;};11G'

That's: on lines 1 to 10 fill the hold space with those lines (don't print). On line 11, get the hold space back, and let sed do the default from then on: just print each line. Note that after line 11 the script is a no-op, just leave sed do the default, maybe faster.

In awk:

awk 'NR <11{ hold=hold RS $0 }
     NR==11{ print $0, hold  }
     NR >11
    ' file

As one liner:

$ seq 15 | awk 'NR<11{hold=hold RS $0}NR==11{print $0,hold}NR>11'
11 
1
2
3
4
5
6
7
8
9
10
12
13
14
15
|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.