Skip the first line by using the flip-flop operator
(this was an answer to a StackOverflow question that I didn’t post because it didn’t quite fit, but I use too keen on showing off the flip-flop operator to get rid of it all)
I also put this in Reddit
A few other places:
- perlfaq5: How do I change, delete, or insert a line in a file, or append to the beginning of a file?
- Respect the global state of the flip flop operator
- Make exclusive flip-flop operators
Here’s some text that has a mix of written (“first”) and numeric (“4th”) ordinals, and our task is to change them all to the written form except for the first line:
$ cat > file.txt
first line has the numeric ordinal 4th
second line
third is before 4th
4th
next to last
last line is not 4thLet’s start with a program that simply adds line numbers to the input:
$ perl -n -e 'print "$. $_"' file.txt
1 first line has the numeric ordinal 4th
2 second line
3 third is before 4th
4 4th
5 next to last
6 last line is not 4thWe can skip the first line, which means that we only get lines that have a line before it. The $. is the input line number:
$ perl -n -e 'print "$. $_" unless $. == 1' file.txt
2 second line
3 third is before 4th
4 4th
5 next to last
6 last line is not 4thBut we don’t want to skip the first line. We simply don’t want to change it. We can use the same $. comparison for the part that changes the line. Notice the first line does not change but the fifth line does:
$ perl -n -e 's/4th/fourth/ unless $. == 1; print "$. $_"' file.txt
1 first line has the numeric ordinal 4th
2 second line
3 third is before fourth
4 fourth
5 next to last
6 last line is not fourthThe -p switch is different than -n because it outputs the value of the line at the end of the loop (if you change it or not):
$ perl -pe 's/4th/fourth/ unless $. == 1' file.txt
first line has the numeric ordinal 4th
second line
third is before fourth
fourth
next to last
last line is not fourthOnce you know about $., you can select lines however you like, such as every other line:
$ perl -pe '$_ = uc($_) unless $. % 2' file.txt
first line has the numeric ordinal 4th
SECOND LINE
third is before 4th
4TH
next to last
LAST LINE IS NOT 4THOr, even a range (window) of lines:
$ perl -pe '$_ = uc($_) unless( $. < 2 or $. > 5)' file.txt
SECOND LINE
THIRD IS BEFORE 4TH
4TH
NEXT TO LAST
last line is not 4thThere’s a special, and sometimes mind-bending, operator that handles this. The .. range operator in scalar context, as it would be in a condition, doesn’t produce a list. It’s the “flip-flop” variant. That returns false until the lefthand argument is true, and then keeps returning true until the righthand argument is true, after which it returns false. It takes many people a few minutes to unravel that if they haven’t used it before:
$ perl -pe '$_ = uc($_) if $. == 2 .. $. == 5' file.txt
first line has the numeric ordinal 4th
SECOND LINE
THIRD IS BEFORE 4TH
4TH
NEXT TO LAST
last line is not 4thPerl, being the language that it is, has special cases for the common cases. You can leave off the explicit comparison because comparing the number to $. is the default:
$ perl -pe '$_ = uc($_) if 2 .. 5' file.txt
first line is not 4th
SECOND LINE
THIRD IS BEFORE 4TH
4TH
NEXT TO LAST
last line is not 4thAs an aside, compare this to sed’s 2,5 ... syntax for a line range.