blog · git · desktop · images · contact & privacy · gopher

Moving the first line to the end of the file


Consider a very simple file:

foo 1
bar 2
baz 3
yadda 4
whatever 5

You know want to edit this file in-place, moving the first line to the end of the file. The expected result is this:

bar 2
baz 3
yadda 4
whatever 5
foo 1

How do you do this in a shell script?

My first idea was using sed:

sed -ni '1{h;n}; $!p; ${p;x;p}' data

It goes like this:

It works, but it’s not so easy to understand. You have to know about the hold space and what not. I dropped this solution, because I didn’t want to “impose” it on my colleagues.

Alright, let’s try a very explicit approach. How about this one:

lines=$(wc -l <data)
if (( lines > 1 ))
    new_tail=$(head -n 1 data)
    all_but_first=$(tail -n +2 data)

    cat >data <<EOF

That’s easy to understand, isn’t it? It also shows a flaw in my sed command above: It fails if there is only one line in data. Hence the if.

Since I still had the discussion about bad taste in mind, I wasn’t happy with this solution, either. That if shouldn’t be needed.

Isn’t there something nicer?

Let’s try awk.

awk 'NR == 1 { s = $0 }; NR > 1 { print }; END { print s }' data

That’s better. You can make it shorter by removing the { print } block, because that’s the default action.

Problem is, awk cannot edit files in place. So, the actual call looks like this:

awk '...' <data >"$tmpfile"
mv "$tmpfile" data

Ugh, that’s pretty verbose. Plus, if the script was to fail for whatever reason at the awk step, there’d be a dangling temporary file. To avoid this, we would have to install a trap

Still not happy.

It bothered me that this task is so easy to do in vi, so why has it to be hard in a shell script? Wait a second. Why not use ed?

ed -s data <<END

That’s not bad. It has the same problem as sed: Very unusual commands. Of course, they are related. But there is no corner case. And it’s just one command, really. Pretty much anyone will understand wq.

I like that.