Camelia

Most popular shell commands using Perl 6

A few weeks ago brian d foy posted a challenge to create a list of most popular shell commands. Assuming of course a Linux/Unix system.

He was also expecting it in Perl 5, but I thought showing a solution in Perl 6 would be also interesting.

While of course this specific situation does not come up on a Windows system, the more generic question can be asked with any kind of log-file. On Windows, Mac, Linux, Unix or any other operating system.

Note! This site is about Perl 6, the future version of Perl.
If you are looking for a solution for the current production version of Perl 5, please check out the Perl 5 tutorial.

The problem

When using a Linux or Unix system, every command you type in the shell will be saved in a history file. That file is a simple text file, in which each line is a command you typed in. It makes it easy for you to search for a command you typed in recently, and execute it again. Maybe with slight modification.

An environment variable controls the size of this file.

The challenge was to show the commands based on popularity.

My understanding of the problem was to count exact duplicates of lines in the file, and then show the lines with the most repetition. This means the following are 3 different commands:

git commit -m "taking the code apart"
git status
git commit -m "putting everything back again"

Another interpretation would be to use only the first element of each line when comparing lines. In that case the above 3 lines would all count to be git commands.

You could also have other interpretations, that will require more complex ways to compare the lines. You could, for example, think about the above 3 lines as being 2 git commit commands and one git status. That might be interesting, though then you could also think abot git st which, at least in my personal configuration, is an alias for git status. Should those be counted as the same command or two different ones?

History on Windows

On MS Windows, if you are using the cmd window you can see the history of your commands by pressing F7. You can even list the history by running doskey /history and then you can redirect the output of that command to a text file.

doskey /history > history.txt

Once you do that, you can use the same script to list the most popular commands.

The solution

Let' see the most simple case. Basically we have a file and we would like to check how many times each line appears and show them in a descending order.

#!/usr/bin/env perl6
use v6;

my $file = %*ENV<HOME> ~ '/.bash_history';
my %count;

my $fh = open $file;
for $fh.lines -> $line {
	%count{$line}++;
}
for %count.keys.sort({ %count{ $_ } }).reverse -> $k {
	say "$k {%count{$k}}";
}

Let's take it apart.

The first line, #!/usr/bin/env perl6 is just the sh-bang. Interesting only for Unix-ish system, but making no harm on Windows either.

use v6; is an important line. It prevents you from accidentally running this script with perl 5, and then spending valuable time trying to figure out the "syntax errors".

%*ENV is the hash containing all the environment variables of your system. The percentage sign is familiar from Perl 5. The * means this is a system variable. The name of this prefix is twigil.

%*ENV<HOME>

fetches the value of the HOME environment variable and tilde ~ concatenates it with the next string, forming the full path to the history file. We assign it to a lexical variable called $file, that we just declared using the my keyword.

BTW instead of the above, you might want to write the following:

my $file = @*ARGS[0];

That will let you provide the name of the history file on the command line.

Then we declare a hash called %count

As you can see variables you declare have single prefixes ($ for scalars and % for hashes) called sigils.

my $fh = open $file;

opens the file for reading and assigns the file-handle to $fh.

$fh.lines returns all the lines in the file.

for $fh.lines -> $line {
}

The for loop iterates over all the values returned by $fh.lines, all the lines, assigns each value to $line, the variable we put on the right hand side of the arrow, and executes the block. There is no need to declare the $line variable. It is basically a parameter of the block after it, and it is only visible in that block.

%count{$line}++;

We access a single element of the hash and increment it by one.

As you might have noticed, when accessing an element of a hash with a key that is a string literal, we use < and > signs around the hash key. We use curly braces { and } when we are using a variable, or any other expression as the key.

Sorting

The next part of the code is a bit more complex. Let's see it step by step:

%count.keys returns the keys of the hash. sort({ %count{ $_ } }) takes all the elements and sorts them by the value of each element. The only problem is, that by default it will sort them in ascending order. So we call .reverse to turn the list around to descendng order.

Inside the for loop we print $k which is the loop varible holding the key (the current line) and the respective value %count{$k}. Perl 6 allows embedding any perl expression inside a double-quoted string. Scalars (as $k) can be embedded without any ceremony. For any other expression you only need to put it within curly braces. The result of the expression will be embedded in the string. The usual name of this code embedding is interpolation.

for %count.keys.sort({ %count{ $_ } }).reverse -> $k {
	say "$k {%count{$k}}";
}

That's it about this example.

The result

If you are interested, on my system these are the 10 most popular commands:

git st 649
ll 359
alpine 277
svn st 235
git add . 234
git push 151
cd 150
git diff 147
cd .. 130
fetchmail  128

In another article we'll try to see how the more complex interpretation of the original challenge can be implemented.


The Perl 6 Tricks and Treats newsletter has been around for a while. If you are interested to get special notification when there is new content on this site, it is the best way to keep track:
Email:
Full name:
This is a newsletter temporarily running on my personal site (szabgab.com) using Mailman, till I implement an alternative system in Perl 6.
Gabor Szabo
Written by Gabor Szabo

Published on 2012-07-12



Comments

In the comments, please wrap your code snippets within <pre> </pre> tags and use spaces for indentation.
comments powered by Disqus

Perl 6 Tricks and Treats newsletter

Register to the free newsletter now, and get updates and news.
Email:
Name: