views:

292

answers:

6

I find a lot of Perl one-liners online. Sometimes I want to convert these one-liners into a script, because otherwise I'll forget the syntax of the one-liner.

For example, I'm using the following command (from nagios.com):

tail -f /var/log/nagios/nagios.log | perl -pe 's/(\d+)/localtime($1)/e'

I'd to replace it with something like this:

tail -f /var/log/nagios/nagios.log | ~/bin/nagiostime.pl

However, I can't figure out the best way to quickly throw this stuff into a script. Does anyone have a quick way to throw these one-liners into a Bash or Perl script?

+3  A: 
#!/usr/bin/env perl

while(<>) {
    s/(\d+)/localtime($1)/e;
    print;
}

The while loop and the print is what -p does automatically for you.

jkramer
+8  A: 

Take a look at perlrun:

-p

causes Perl to assume the following loop around your program, which makes it iterate over filename arguments somewhat like sed:

LINE:
while (<>) {
    ... # your program goes here
} continue {
    print or die "-p destination: $!\n";
}

If a file named by an argument cannot be opened for some reason, Perl warns you about it, and moves on to the next file. Note that the lines are printed automatically. An error occurring during printing is treated as fatal. To suppress printing use the -n switch. A -p overrides a -n switch.

BEGIN and END blocks may be used to capture control before or after the implicit loop, just as in awk.

So, simply take this chunk of code, insertyour code at the "# your program goes here" line, and viola, your script is ready!

Thus, it would be:

#!/usr/bin/perl -w
use strict; # or use 5.012 if you've got newer perls

while (<>) {
    s/(\d+)/localtime($1)/e
} continue {
    print or die "-p destination: $!\n";
}
Robert P
+18  A: 

You can convert any Perl one-liner into a full script by passing it through the B::Deparse compiler backend that generates Perl source code:

perl -MO=Deparse -pe 's/(\d+)/localtime($1)/e'

outputs:

LINE: while (defined($_ = <ARGV>)) {
    s/(\d+)/localtime($1);/e;
}
continue {
    print $_;
}

The advantage of this approach over decoding the command line flags manually is that this is exactly the way Perl interprets your script, so there is no guesswork. B::Deparse is a core module, so there is nothing to install.

Eric Strom
Wow so many good and correct answers here. This post wins, because of the "B::Deparse" tip. Step 1 is to migrate a one-liner to a script. Step 2 is often to extend that script, and this 'B::Deparse' method makes sure we start with some good syntax.
Stefan Lasiewski
+7  A: 

That one's really easy to store in a script!

#! /usr/bin/perl -p
s/(\d+)/localtime($1)/e

The -e option introduces Perl code to be executed—which you might think of as a script on the command line—so drop it and stick the code in the body. Leave -p in the shebang (#!) line.

In general, it's safest to stick to at most one "clump" of options in the shebang line. If you need more, you could always throw their equivalents inside a BEGIN {} block.

Don't forget chmod +x ~/bin/nagiostime.pl

You could get a little fancier and embed the tail part too:

#! /usr/bin/perl -p

BEGIN {
  die "Usage: $0 [ nagios-log ]\n" if @ARGV > 1;
  my $log = @ARGV ? shift : "/var/log/nagios/nagios.log";
  @ARGV = ("tail -f '$log' |");
}

s/(\d+)/localtime($1)/e

This works because the code written for you by -p uses Perl's "magic" (2-argument) open that processes pipes specially.

With no arguments, it transforms nagios.log, but you can also specify a different log file, e.g.,

$ ~/bin/nagiostime.pl /tmp/other-nagios.log
Greg Bacon
+2  A: 

There are some good answers here if you want to keep the one-liner-turned-script around and possibly even expand upon it, but the simplest thing that could possibly work is just:

#!/usr/bin/perl -p
s/(\d+)/localtime($1)/e

Perl will recognize parameters on the hashbang line of the script, so instead of writing out the loop in full, you can just continue to do the implicit loop with -p.

But writing the loop explicitly and using -w and "use strict;" are good if plan to use it as a starting point for writing a longer script.

David Conrad
+4  A: 

Robert has the "real" answer above, but it's not very practical. The -p switch does a bit of magic, and other options have even more magic (e.g. check out the logic behind the -i flag). In practice, I'd simply just make a bash alias/function to wrap around the oneliner, rather than convert it to a script.

Alternatively, here's your oneliner as a script: :)

#!/usr/bin/bash
# takes any number of arguments: the filenames to pipe to the perl filter

tail -f $@ | perl -pe 's/(\d+)/localtime($1)/e'
Ether