Useful one-liners and other short Perl scripts

J

J. Romano

Dear Perl Community,

Over the years of using Perl I discovered some very useful
one-liners that I thought I'd share with the rest of the Perl
community. Some of these one-liners will only work in Unix, so I've
supplied the equivalent DOS one-liner (for Win32 systems) where
possible.

I realize that in many cases there are other ways of doing the same
thing. In some cases, I used one method for a long time before I
discovered a shorter way of doing the same thing. In such cases, I've
included both techniques here.

But if you think you have a better way of doing something I've
listed here, by all means, share it! And if you have any of your own
useful one-liners, feel free to share those as well.


This post is divided into three sections:

* USEFUL ONE-LINERS: One-liners that have been useful
to me in the past
* MORE USEFUL ONE-LINERS: One-liners that I've never
had the need to use, but might
be helpful to someone else
* SHORT FUN PROGRAMS: A couple of cute programs


SECTION 1: USEFUL ONE-LINERS

All of the one-liners in this section have been useful to me at one
time or another in the past.


Everyone should know this one, but just in case they don't, here's how
to find the current date in epoch seconds:

perl -le "print time"


These next few lines explain how to extract the date/time from a value
of epoch seconds (like 1234567890). Many times over the course of
debugging C/C++ programs I've used this technique to find out what
date was being used, making the following line the most useful Perl
one-liner I've ever used:

perl -le "print scalar gmtime 1234567890"

Here's how to find the current date and time:

perl -le "print scalar gmtime time"

Here's how to find the so-called "beginning of time":

perl -le "print scalar gmtime 0"

Here's how to find the date of the one-billionth second after time 0:

perl -le "print scalar gmtime 1e9"

This gives the date of the fabled "end of time":

perl -le "print scalar gmtime 0x7f_ff_ff_ff"


Many times I've wanted to convert a hexadecimal number (like 0xface)
into decimal:

perl -le "print 0xface"

And other times I needed to convert a decimal number (like 255) into
hexadecimal:

# Unix:
perl -e 'printf "0x%x\n", 255'
# DOS:
perl -e "printf qq/0x%x\n/, 255"


I do a lot of work with binary files. Sometimes I need to find the
value of a specific bit in a binary file. Here is how to convert a
binary file into its binary 0s and 1s:

# Unix:
perl -l -0777 -ne 'print unpack("B*", $_)' binary_file
# DOS (using ActiveState Perl 5.8 or later):
perl -l -Mopen=IO,:raw -0777 -ne "print unpack('B*', $_)" binary_file

(Note: Replace the 'B*' packstring with 'H*' to see the hexadecimal
equivalent.)


Here's a handy one for determining if your machine is little-endian or
big-endian:

perl -le "print((ord pack 's', 1) ? 'little' : 'big', '-endian')"

Note: "perldoc -f pack" describes another way for determining
endian-ness by using $Config{byteorder}. Here is a solution that uses
that method (note: I use '@' instead of '$' so that this one-liner
will run under both DOS and Unix):

perl -MConfig -le "print @Config{byteorder}"

The string "1234" or "12345678" signifies little-endian; "4321" or
"87654321" signifies big-endian. If you get something out of order,
it means that your machine is netiher big- nor little-endian!


Use this to find out the size of an integer (in bytes):

perl -le "print length pack 'i', 0"

Note: "perldoc -f pack" describes another way for determining this
using $Config{intsize}. Here is a solution that uses that method
(note: I use '@' instead of '$' so that this one-liner will run under
both DOS and Unix):

perl -MConfig -le "print @Config{intsize}"


Here is a small Perl interactive interpreter:

# Unix:
perl -ne 'eval; print "$@\n> "'
# DOS:
perl -ne "eval; print qq/$@\n> /"

After typing out this one-liner, hit ENTER twice to see the
interactive interpreter prompt. Type "exit" to quit the interpreter.

Note: Many readers know that "perl -de 1" will do just about the same
thing, and it's more flexible with more advantages (and quicker to
type). That's true, but remember that to exit "perl -de 1" you must
type "q", not "exit".


Use this to count number of characters in a string (not counting the
newline):

perl -lne "chomp; print tr///c"


This removes duplicate entries in the PATH environment variable:
(Note: this only works on Unix)

exec perl -e'$ENV{PATH}=join":",grep{not$seen{$_}++}split/:/,$ENV{PATH};exec$ENV{SHELL}'

Note that this spawns a child shell (and so has whatever advantages
and disadvantages that go with it). This one-liner is meant to be run
from an interactive shell and will NOT work from inside another
script. Therefore, this may not be suitable for all purposes.


SECTION 2: MORE USEFUL ONE-LINERS

The one-liners in this section might be useful to someone, but I can't
say I've ever really needed them very badly myself.


Convert tabs into spaces (assuming a tabstop is 8 spaces):

# Unix:
perl -MText::Tabs -ne '$tabstop=8; print expand($_)'
# DOS:
perl -MText::Tabs -ne "$tabstop=8; print expand($_)"


Sort a list of items (such as songs, foods, or books) in order of
preference:

# Unix:
perl -e 'print sort{print "1. ${a}2. ${b}Preference: ";<>*2-3}<>'
# DOS:
perl -e "print sort{print qq/1. ${a}2. ${b}Preference: /;<>*2-3}<>"

To try this out, run it and type:
chocolate
brussel sprouts
bread
and hit CTRL-D (or CTRL-Z and ENTER if you're using DOS). You will be
asked a series of questions asking what you prefer. (Hint: to say
that you like both the same, you can type a value of "1.5".)

This sort works provided that you are consistent (no changing your
mind halfway through the sort) and that you don't have any circular
preferences (an example of a circular preference is: I like book A
more than book B, book B more than book C, and book C more than book
A).


SECTION 3: SHORT FUN PROGRAMS

Here are some fun short programs and one-liners that do nothing
particularly useful, but are just for fun.


Here's a program that I like to call the "Spooky Writer":

# Unix:
perl -e'$|=1;for(split//,join"",<>){print;select(undef,undef,undef,rand(.5))}'
# DOS:
perl -e"$|=1;for(split//,join'',<>){print;select(undef,undef,undef,rand(.5))}"

Try and figure out what it does. If you have trouble figuring it out,
try running the one-liner, and type a few lines from your favorite
poem. Then hit CTRL-D (or CTRL-Z and ENTER if you're using DOS), and
watch what happens! Spooky, eh? You can run the JAPH at the bottom
of this post to see a variation of this one-liner.

If you're still having trouble figuring this code out, try the
following short script which does the same thing but is easier to
understand:

#!/usr/bin/perl -w
use strict;

# Set the maximum wait in seconds.
# The average wait will be half that:
my $maxWait = 0.50; # in seconds

$| = 1; # autoflush

# Get all input:
my @inputLines = <>;
my $input = join('', @inputLines);
my @charList = split(//, $input);

# @charList now holds an array of characters of all the input

# Loop through each character, printing the character and
# then waiting for a short time:
foreach my $char (@charList)
{
print $char;

# Pause for a short (and random) time:
my $pauseTime = rand($maxWait);
select(undef, undef, undef, $pauseTime);
}
__END__


And finally, for the last program I present a self-replicating
program! This entire program is six lines long:

#!/usr/bin/perl
print((@d=<DATA>)x2);
__END__
#!/usr/bin/perl
print((@d=<DATA>)x2);
__END__

A self-replicating program is a program that, when run, produces its
code as output. These programs are tricky to write, because if one
character is changed, usually one or more other characters have to be
changed as well.

In perl, you can run a self-replicating program like this:

perl repeat.pl | perl | perl | perl | perl

and no matter how many "| perl" instances you write, the output will
still be the same!

Unfortunately, I couldn't come up with a self-replicating Perl
one-liner, but if someone knows of one, I'd like to see it!

If you think the above six-line program is difficult to understand,
here's essentially the same program, but easier to follow (note: the
entire program is 12 lines long):

#!/usr/bin/perl -w
use strict;
my @d = <DATA>; # get half of this script
print @d; # print the top half of this script
print @d; # print the bottom half of this script
__END__
#!/usr/bin/perl -w
use strict;
my @d = <DATA>; # get half of this script
print @d; # print the top half of this script
print @d; # print the bottom half of this script
__END__


Well, that's it for useful one-liners and small Perl programs. I
hope you enjoyed them. If you have any one-liners that have been
useful to you, feel free to share them. I'd love to see them.

Happy Perling,

Jean-Luc Romano

--
# JAPH (for Unix):
perl -e'$|=1;select($J,$L,$R,rand.7*print)for split//,"Just another
Perl hacker\n"'
# JAPH (for DOS):
perl -e"$|=1;select($J,$L,$R,rand.7*print)for split//,qq/Just another
Perl hacker\n/"
 
J

John W. Krahn

J. Romano said:
Use this to count number of characters in a string (not counting the
newline):

perl -lne "chomp; print tr///c"

The chomp is superfluous as the -l switch does a chomp for you:

perl -lne "print y///c"


And if you want to include the newline in the count:

perl -ne "print y///c,$/"



John
 
J

John W. Krahn

J. Romano said:
Everyone should know this one, but just in case they don't, here's how
to find the current date in epoch seconds:

perl -le "print time"

If you have GNU date:

date +%s


John
 
J

John W. Krahn

J. Romano said:
This removes duplicate entries in the PATH environment variable:
(Note: this only works on Unix)

exec perl -e'$ENV{PATH}=join":",grep{not$seen{$_}++}split/:/,$ENV{PATH};exec$ENV{SHELL}'

Note that won't work with csh as it uses ' ' instead of ':' as a
separator.


John
 
J

John W. Krahn

J. Romano said:
Here's a program that I like to call the "Spooky Writer":

# Unix:
perl -e'$|=1;for(split//,join"",<>){print;select(undef,undef,undef,rand(.5))}'

You can save a few key strokes like this:

perl -e'$|=$/=\1;print,select undef,undef,undef,rand.5for<>'


:)

John
 
J

Joe Smith

John said:
Note that won't work with csh

It most certainly does work with csh.
as it uses ' ' instead of ':' as aseparator.

The non-exported internal variable $path uses spaces.
The $PATH environment variable uses colons.
In perl, $ENV{PATH} is the latter.
-Joe
 
J

J. Romano

John W. Krahn said:
The chomp is superfluous as the -l switch does a chomp for you:

perl -lne "print y///c"

Oh, I didn't know that the -l switch did a chomp automatically.
Thanks for pointing that out to me. (It's a good thing I didn't use
"chop"! :)
And if you want to include the newline in the count:

perl -ne "print y///c,$/"

That was for DOS, of course. And for the sake of completeness, the
Unix equivalent is:

perl -ne 'print y///c,$/'

Thanks for pointing these out, John.

-- Jean-Luc
 
J

J. Romano

John W. Krahn said:
You can save a few key strokes like this:

perl -e'$|=$/=\1;print,select undef,undef,undef,rand.5for<>'


Wow! That's impressive! And I thought I had squeezed it as small
as it was going to go.

Actually, if you examine my JAPH, you'll see another trick I use to
make it even smaller. I do this by using undefined variables in the
place of undef. Applying this to your modified version of my
one-liner, we would get:

# Unix:
perl -e'$|=$/=\1;print,select $J,$L,$R,rand.5for<>'
# DOS:
perl -e"$|=$/=\1;print,select $J,$L,$R,rand.5for<>"

Thanks for pointing that out, John.

-- Jean-Luc
 
J

J. Romano

Purl Gurl said:
A quick glance indicates this will remove the first
letter of the first entry.

How do you figure? All the letters print out fine for me.
Flush is not needed at a DOS command line.

You're right -- if I want line-buffering (which is often the case).
But here I want each letter to appear as soon as I give the print()
command (and not when a newline is printed), giving the illusion that
someone else is typing. In order to do that, I have to set Perl to
autoflush so that each letter will print out separately.

Sorry... I guess I didn't make that part clear in my original post.

-- Jean-Luc
 
M

Michele Dondi

Unfortunately, I couldn't come up with a self-replicating Perl
one-liner, but if someone knows of one, I'd like to see it!

It depends of what you mean with self-replicating Perl one-liner:

perl -e '' | perl | perl | perl [...]


Michele
 
J

John W. Krahn

J. Romano said:
That was for DOS, of course. And for the sake of completeness, the
Unix equivalent is:

perl -ne 'print y///c,$/'

Both versions will work in Unix, only DOS requires double quotes.


John
 
J

John W. Krahn

Joe said:
It most certainly does work with csh.


The non-exported internal variable $path uses spaces.
The $PATH environment variable uses colons.
In perl, $ENV{PATH} is the latter.

Thanks, I did not know that. I don't use csh. :)


John
 
J

John W. Krahn

J. Romano said:
Wow! That's impressive! And I thought I had squeezed it as small
as it was going to go.

Actually, if you examine my JAPH, you'll see another trick I use to
make it even smaller. I do this by using undefined variables in the
place of undef. Applying this to your modified version of my
one-liner, we would get:

# Unix:
perl -e'$|=$/=\1;print,select $J,$L,$R,rand.5for<>'
# DOS:
perl -e"$|=$/=\1;print,select $J,$L,$R,rand.5for<>"

If you want it smaller also remove the space between select and $J:

perl -e'$|=$/=\1;print,select$J,$L,$R,rand.5for<>'


:)

John
 
J

J. Romano

"J. Romano" replied:

John W. Krahn said:
Both versions will work in Unix, only DOS requires double quotes.

No, the double-quoted version won't work in Unix. If you try it,
your shell will probably complain about an "illegal variable name" due
to the shell trying to interpolate the "$/" variable.

That's a problem between DOS and Unix shells: they complain about
different things. I wish they were more consistent.

-- Jean-Luc
 
P

Paul Lalli

No, the double-quoted version won't work in Unix. If you try it,
your shell will probably complain about an "illegal variable name" due
to the shell trying to interpolate the "$/" variable.

That's a problem between DOS and Unix shells: they complain about
different things. I wish they were more consistent.

Works just fine using bash on a Solaris box. Perhaps you should actually
try it before asserting it won't work.

Paul Lalli
 
J

J. Romano

Purl Gurl said:
It is your use of join with split.

I still don't see how the first letter will be removed. The join()
function never removes any characters. It does add characters, but
since I'm adding an empty string, no characters should be added or
removed.
perl -e" for(split//,<>){print;select(undef,undef,undef,rand(.5))}"

That will cure removal of the first character in standard input
and will eliminate a need for Control Z to finish.

Hey, that's neat! I didn't realize that <> could be used in a
split statement for applying split() just on the first input line.

However, my original intention was to apply split on all the input
lines, so that I could include a multi-line text file (which would
hold all the input) as an argument to the one-liner. But if I ever
want to pull out just one line, I'll remember your technique. Thanks
for showing that to me, Purl Gurl!
Perhaps you are not testing this at a DOS command line?
Many confuse a NT5 GUI command interface with DOS.

That could very well be the case. I know I'm guilty of saying
"DOS" when I really mean a Win32 command shell, when they might not
technically be the same.
Incidently, "ghost" typing is an old technique used to fool
a portion of the Turing test.

Cor! I didn't know that, but I have no trouble believing that it
was used to fool a lot of people taking the Turing test. It's
tempting for a programmer to employ little tricks for the express
purpose of fooling Turing testers.

-- Jean-Luc
 
D

David H. Adler

Works just fine using bash on a Solaris box. Perhaps you should actually
try it before asserting it won't work.

How about we all just agree to say 'It depends on what shell you're
using', huh? :)

dha
 
J

J. Romano

"J. Romano" said:
And then Paul Lalli said:
And then "David H. Adler said:
How about we all just agree to say 'It depends on what shell you're
using', huh? :)

David has a point there. I just tried:

perl -ne "print y///c,$/"

on csh and tcsh and I got the "Illegal variable name" error. Then I
tried it with bash and it worked just fine. Apparently csh and tcsh
complain about things that bash doesn't.

That's a problem between some Unix shells: they complain about
different things. I wish they were more consistent.

-- J.
 
J

J. Romano

Purl Gurl said:
Playing the parrot, you should test your code before commenting.

That's sound advice, and believe it or not, I did test my code
before commenting. When I entered the command:

perl -e"$|=1;for(split//,join'',<>){print;select(undef,undef,undef,rand(.5))}"

and then typed "hello", then typed ENTER, then typed CTRL-Z and then
another ENTER, all five letters of "hello" (plus the newline) were
printed. No character was missing, not even the first one.

Neither by testing this one-liner nor by examining its code can I
see anything wrong with it.

-- J.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,787
Messages
2,569,630
Members
45,334
Latest member
66Zeinab9

Latest Threads

Top