7
\$\begingroup\$

I have written a script that improves the Linux terminal experience. It basically displays the content of the terminal's current folder in a nicer way than the ls command does. I use it all the time myself.

#!/usr/bin/perl
###########################################################################
# role:  this program shows all files and directories in current location.
# about: its a replacement to the 'ls' command.
# about: different from the ls command, this program is easily cutomize-able and improve-able.
# about: the code is available, readable and change-able on site.
# tofix: the 'length' function fails when there are icelandic letters. have to find another solution.
# tofix: could figure out beforehand what the largest filename is, and then position the file-size category accordingly.
# tofix: also, the header should be either singular or plural, not both.
# tofix: sorting week1 .. week12 does not work correctly.
# tofix: add: type(eg .pm) and line_quantity.
###########################################################################


#get all the pathfiles at current path.
chdir("$ENV{PWD}") or die "$!";
opendir(CDIR, ".") or die "$!";
@pathfiles_and_directories_here=grep {/.*/} readdir CDIR;
foreach(@pathfiles_and_directories_here)  {$_="$ENV{PWD}"."/$_";}
close CDIR;
@pathfiles_and_directories_here=grep { !/\/\.{1,2}/ }
@pathfiles_and_directories_here;#dont want '.' and '..'.

#establish pathfiles, paths and files.
@pathfiles_here =grep(! -d,@pathfiles_and_directories_here);
@files_here     =grep(! -d,@pathfiles_and_directories_here);
foreach(@files_here)        {s/$ENV{PWD}\///g;}
@directories_here= grep(  -d,@pathfiles_and_directories_here);     
foreach(@directories_here)  {s/$ENV{PWD}\///g;}

#sort the pathfiles, files and directories.. alphabetically.
@files_here=sort_alphabetically(@files_here);
$file_quantity=($#files_here+1);
@pathfiles_here=sort_alphabetically(@pathfiles_here);

#seperate directories into if they start with a number or a letter.
my @directories_starting_with_letter= grep /\A\D.*/i, @directories_here;
my @directories_starting_with_number= grep /\A\d.*/, @directories_here;
#sort them accordingly
@directories_starting_with_number=sort_numerically(@directories_starting_with_number);
@directories_starting_with_letter=sort_alphabetically(@directories_starting_with_letter);
#put them back into the directory list.
undef @directories_here;
@directories_here=(@directories_starting_with_number,@directories_starting_with_letter);

$directory_quantity=($#directories_here+1);

#get file_sizes_byte_quantity and file_sizes_human_readable.
map($file_sizes_byte_quantity{$_}=-s ,@pathfiles_here);
%file_sizes_human_readable=make_values_human_readable(%file_sizes_byte_quantity);
#get total file size.
$total_file_size_bytes=get_total_file_size(%file_sizes_byte_quantity);
$total_file_size=make_byte_quantity_human_readable($total_file_size_bytes);

#get type with the most lines.
if($#files_here>=$#directories_here)  {$column_with_most_lines=$#files_here;}
if($#files_here<$#directories_here)   {$column_with_most_lines=$#directories_here;}
$column_with_most_lines+=2;

system "clear";
#spaces become underscored.
print STDOUT "\033\[32\;4mDirectories             \| Files                         Size                                           \n\033[0m";
for(my $cline=0; $cline<=$column_with_most_lines; $cline++){
    my $cpathfile=$pathfiles_here[$cline];
    my $cdirectory=$directories_here[$cline];
    my $cfilename=$files_here[$cline];$cfilename =~ s/$ENV{PWD}\///g;
    my $csize=$file_sizes_human_readable{$cpathfile};
    
    #fs=fillup_string.
    my $csize_fs=             get_fillup_string($csize,               10);
    my $cdirectory_fs=        get_fillup_string($cdirectory,          24);
    my $directory_quantity_fs=get_fillup_string($directory_quantity,  24);
    my $cfilename_fs=         get_fillup_string($cfilename,           30);
    my $file_size_fs=         get_fillup_string($total_file_size,     10);
    my $file_quantity_fs=     get_fillup_string($file_quantity,       30);
    
    #write the folder.
    if($cline<=($#directories_here+0))  {print STDOUT "\033\[1\;33m$cdirectory$cdirectory_fs\033[0m";}
    if($cline==($#directories_here+1))  {print STDOUT "\033\[62\;32m------------------------\033[0m"; $cdirectory_fs='';}
    if($cline==($#directories_here+2))  {print STDOUT "\033\[62\;32m$directory_quantity"."$directory_quantity_fs\033[0m";}
    if($cline>=($#directories_here+3))  {print STDOUT "\033\[62\;32m                        \033[0m";}
     
    #if at least one of them is still in their listing, then print the seperator, otherwise just spacebar.
    if($cline<=$#directories_here || $cline<=$#files_here)  {print STDOUT "\033\[32\;3m\|\033[0m";}
    else  {print STDOUT "\ ";}
    
    #write the file.
    if($cline<=($#files_here+0))  {print STDOUT " $cfilename$cfilename_fs$csize$csize_fs\n";}
    if($cline==($#files_here+1))  {print STDOUT "\033\[62\;32m------------------------------------------------------------------------------\n\033[0m";}
    if($cline==($#files_here+2))  {print STDOUT "\033\[62\;32m    $file_quantity$file_quantity_fs$total_file_size$file_size_fs\n\033[0m";}
    if($cline>=($#files_here+3))  {print STDOUT "\033\[62\;32m                        \n\033[0m";}
    }
undef $column_with_most_lines;
print STDOUT "\n";












###############################################
#this function has already been copied to modules.
sub get_fillup_string{
my $string=shift;
my $wanted_string_length=shift;

#function 'length' fails when there are icelandic letters. have to find another solution.
my $string_length=length $string;
my $missing_string_length=($wanted_string_length-$string_length);
while($missing_string_length>0)  {$fillup_string.=' '; $missing_string_length--;}

my $temp=$fillup_string; undef $fillup_string;
return $temp;
}
###############################################
#this function has already been copied to modules.
#arg: returns the array sent in, sorted alhpabetically.
sub sort_alphabetically{
my @sorted=sort{lc($a) cmp lc($b)} @_;
return @sorted;
}
###############################################
#this function has already been copied to modules.
#arg: returns the array sent in, sorted alhpabetically.
sub sort_numerically{
#my @sorted=sort @_;
#print STDOUT "";
#sleep 1;
my @sorted= sort {$a <=> $b} @_;
return @sorted;
}
##############################################
#this functino will never be copied to modules.
sub make_values_human_readable{
my %file_sizes_byte_quantity=@_;

foreach my $ckey (keys %file_sizes_byte_quantity){
    my $cvalue=$file_sizes_byte_quantity{$ckey};
    $file_sizes_human_readable{$ckey}=make_byte_quantity_human_readable($cvalue);
    }
return %file_sizes_human_readable;
}
####################################################
#this function has not been copied to modules.
sub make_byte_quantity_human_readable{
my $cvalue=shift;

#the size must use sensible units.
if($cvalue<=100000 && $cvalue>0){
    $cvalue/=1000;
    $cvalue=sprintf("%.1f", $cvalue);
    $cvalue="$cvalue".'kb';
    }
if($cvalue>100000){
    $cvalue/=1000000;
    $cvalue=sprintf("%.1f", $cvalue);
    $cvalue="$cvalue".'mb';
    }
#gb next time.
return $cvalue;
}
###############################################
#this function will never be copied to modules.
sub get_total_file_size{
my %file_sizes_byte_quantity=@_;

$total_file_size=0;
foreach my $ckey (keys %file_sizes_byte_quantity){
    $total_file_size+=$file_sizes_byte_quantity{$ckey};
    }
my $temp=$total_file_size; undef $temp;
return $total_file_size;
}
\$\endgroup\$

2 Answers 2

7
\$\begingroup\$

use strict; and use warnings; are missing. Add them and declare all your variables.

opendir(CDIR, ".") or die "$!";

I believe that it’s advised to use variables in modern Perl:

opendir(my $cdir, '.') or die $!;

@pathfiles_and_directories_here=grep {/.*/} readdir CDIR;

grep {/.*/} is meaningless since it accepts everything.

@pathfiles_and_directories_here=grep { !/\/\.{1,2}/ }
@pathfiles_and_directories_here;#dont want '.' and '..'.

Why didn’t you grep for this pattern right at the beginning? I also dislike the line break here, it makes the lines look like unrelated statements =>

my @files_and_dirs = grep { !/^\.{1,2}/ } readdir $cdir;

I’ve also shortened the variable name. The previous name was too long, and prevented, rather than helped, readability. In general, the long variable names make it really hard to discern any structure when looking over your code. Furthmore, the suffix _here after each variable name isn’t conveying useful information.

Next,

foreach(@pathfiles_and_directories_here)  {$_="$ENV{PWD}"."/$_";}

The string concatenation is redundant. Furthermore, this look looks like a place for map.

@files_and_dirs = map { "$ENV{PWD}/$_" } @files_and_dirs;

But why are you doing this anyway? Further down the road, you remove the file path again from the files and paths.

#establish pathfiles, paths and files.

You should explain what the difference between pathfiles and regular files is. The comment, as it stands, doesn’t tell the reader anything interesting.

I’m not convinced that sort_alphabetically and sort_numerically help readability. Just replace the calls by sort with the appropriate comparer, that should be readable enough, and is just as short.

Furthermore, a note on formatting: you need to indent your code properly and remove dead code. This:

sub sort_numerically{
#my @sorted=sort @_;
#print STDOUT "";
#sleep 1;
my @sorted= sort {$a <=> $b} @_;
return @sorted;
}

cries “sloppy”. Writing clean code is a fundamental part of maintainability.

\$\endgroup\$
2
\$\begingroup\$

In addition to the points raised by the previous answer, here are some further suggestions.


As already noted, the code is hard to read due to inconsistent indentation. The perltidy program can automatically reformat the code in a consistent manner.


There is no need for the following line, and it can be deleted:

chdir("$ENV{PWD}") or die "$!";

There may be other opportunities to simplify the code wherever it uses PWD.


foreach is identical to for. I recommend for: less to type, less to read.


It is good that you added header comments, but consider formatting those as plain old documentation (POD)

This gives you manpage-like help with perldoc.


Escape sequences like \033\[1\;33m are difficult to understand. Consider using Term::ANSIColor to colorize the output. This module is distributed with Perl by default.


It is simpler to omit STDOUT in all the print lines. For example, change:

print STDOUT "\n";

to:

print "\n";

The 4 if statements for $cline should be combined into an if/elsif statement because the 4 conditions are dependent on each other. This better conveys the intent of the code, and it is more efficient since it is unnecessary to check all conditions. Also use separate lines to eliminate long lines of code:

if ( $cline <= ( $#directories_here + 0 ) ) {
    print "\033\[1\;33m$cdirectory$cdirectory_fs\033[0m";
}
elsif ( $cline == ( $#directories_here + 1 ) ) {
    print "\033\[62\;32m------------------------\033[0m";
    $cdirectory_fs = '';
}

The same applies to the other group of if $cline lines.


These types of comments can be removed since they don't describe the code that follows:

#this function has already been copied to modules.

There is no need for this line; it can be removed:

undef $column_with_most_lines;

Improve readability of large integers using underscore separators. Change:

100000

to:

100_000
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.