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 \n033円[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_fs033円[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_fs033円[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------------------------------------------------------------------------------\n033円[0m";}
if($cline==($#files_here+2)) {print STDOUT "033円\[62\;32m $file_quantity$file_quantity_fs$total_file_size$file_size_fs\n033円[0m";}
if($cline>=($#files_here+3)) {print STDOUT "033円\[62\;32m \n033円[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;
}
2 Answers 2
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.
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_fs033円[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