A massive expansion on previous trials, this is an Perl implementation of an entire basic shell. It’s also on Github.
Brief overview of non-standard features:
‘!’ executes last command, ‘!#’ executes a specific command from history, specified by #.
‘* newprompt’ changes prompt to newprompt.
‘file filename.leshell’ executes the semicolon separated commands in the file.
There are two main issues and I’m offering an immediate 10 bonus points for an explanation!
‘bye’ doesn’t exit immediately if any error occurred.
catching ^c (interupt) only works once.
#!/opt/local/bin/perl
# Brief overview of features:
# - '!' executes last command, '!#' executes a specific number command from history
# - '* prompt' changes prompt to prompt
# - 'file filename.leshell' executes the semicolon separated commands in the file
# KNOWN ISSUES:
# bye doesn't exit if any error occurred
# catching ^c only works once
use strict ;
# use warnings;
use Term:: ReadLine ;
use Term::ReadLine:: Gnu ;
use Cwd ;
use String:: Similarity ;
use POSIX ;
use File:: Glob ;
use Env ; # Makes environment vars work automagically
#$SIG{INT} = \&hiya;
#$SIG{CHLD} = "IGNORE";
my $debug = 0 ;
my $prompt = ' orig ';
my $term = new Term:: ReadLine ' leShell ';
my $pid ;
$term -> Attribs -> ornaments ( 0 );
sub generatePrompt {
if ( $prompt eq ' orig ' ) {
return getlogin () . ' @ ' . getcwd . ' > ';
} else {
return $prompt ;
}
}
# Return an array of array refs. Each subarray will be a command separated on words and
# stripped of extra whitespace.
sub cmdToArrayOfArrays {
my @cmd = split ( /;/ , shift );
my @commands ;
foreach ( @cmd ) {
$_ =~ s/^\s*// ;
$_ =~ s/\s*$// ;
my @tempCmdSplitOnWhite = split ( /\s+/ , $_ );
if ( @tempCmdSplitOnWhite ) {
push ( @commands , \ @tempCmdSplitOnWhite );
}
}
return @commands ;
}
# Passed a reference to an array of references to arrays. Trippy.
# Look for *'s in the commands that aren't changing prompt and substitute the appropriate expansion.
# Returns false if there was supposed to be an expansion and nothing was found.
sub expandGlob {
my $bigRef = shift ;
foreach ( @$bigRef ) {
my $littleRef = $_ ;
my $index = 0 ;
foreach ( @$littleRef ) {
if ( $_ =~ qr/\*/ and $index > 0 ) {
my @blob = glob ( $_ );
unless ( @blob ) {
print " Nothing found on expansion. \n ";
return 0 ;
}
splice ( @$littleRef , $index , 1 , @blob );
last ;
}
$index ++ ;
}
}
return 1 ;
}
# Pass app, returns directory/app_name on success. Prints failed otherwise.
sub searchPathForApp {
my $token = shift ;
my @path = split ( /:/ , $ENV {' PATH '} );
my $bestFit = 0 ;
my $closestApp = " wtf ", my $closestDir = " not/even/close ";
unless ( @path ) {
print " Broken path! Quitting. \n ";
exit 0 ;
}
foreach ( @path ) {
if ( opendir ( my $dir , $_ ) ) {
my $currentDir = $_ ;
while ( readdir ( $dir ) ) {
if ( $_ eq $token ) {
closedir ( $dir );
return $currentDir . " / " . $_ ;
} elsif ( similarity ( $_ , $token ) > $bestFit ) {
$bestFit = similarity ( $_ , $token );
$closestApp = $_ ;
$closestDir = $currentDir ;
}
}
closedir ( $dir );
}
}
unless ( $debug ) {
print ( " $token not found. Did you mean $closestApp at $closestDir / $closestApp ? \n " );
}
return 0 ;
}
# Pass an array of the command. Comes out with app name as arg[1] and dir as arg[0]
sub makeBeginningOfArrayExecName {
my $ref = shift ;
if ( @$ref [ 0 ] !~ qr/\/ / ) { # Not a dir, find dir and put on @cmd
my $dir = searchPathForApp ( @$ref [ 0 ] );
if ( $dir ) {
unshift ( @$ref , $dir );
}
} else { # 0 is executable, parse for name and add at 1
@$ref [ 0 ] =~ qr/\/ ?([[: alpha: ]] * ) $/ ;
splice ( @$ref , 1 , 0 , $ 1 );
}
}
# Takes a reference to an array of a command.
sub frkExec {
my $cmdRef = shift ;
my @cmd = @$cmdRef ;
$pid = fork ();
if ( $pid ) {
# Parent
return waitpid ( $pid , 0 );
} elsif ( $pid == 0 ) {
# Child
exec { $cmd [ 0 ] } splice ( @cmd , 1 ) or print STDERR " Couldn't execute " . $cmd [ 0 ] . " - " . $! . " \n ";
} else {
die " Error forking. \n ";
}
}
sub runCommands {
my $commandsRef = shift ;
COMMAND: foreach ( @$commandsRef ) {
# Here check for special chars separately. Prolly glob too.
if ( @ $_ ~~ qr/^\$/ ) {
subEnvVariable ( \ @ $_ );
}
if ( @ $_ ~~ qr/.*~.*/ ) {
subSquiggly ( \ @ $_ );
}
if ( @$_ [ 0 ] eq ' bye ' ) {
byebye ();
} elsif ( @$_ [ 0 ] eq ' * ' ) {
setPrompt ( splice ( @ $_ , 1 ) );
next COMMAND ;
} elsif ( @$_ [ 0 ] =~ qw/!/ ) {
next COMMAND unless substituteHistory ( \ @ $_ );
my @tempCmd = cmdToArrayOfArrays ( " @ $_ " );
runCommands ( \ @tempCmd );
next COMMAND ;
} elsif ( @$_ [ 0 ] eq ' file ' ) {
runFile ( @$_ [ 1 ] );
next COMMAND ;
} elsif ( @$_ [ 0 ] eq ' cd ' ) {
my @dir = splice ( @ $_ , 1 );
my $string = " @dir ";
$string =~ s/^\s*// ;
$string =~ s/\s*$// ;
if ( $string eq "" ) { print " No dir? \n "; next COMMAND ; }
print " That's not a directory. \n " unless chdir ( $string );
next COMMAND ;
}
makeBeginningOfArrayExecName ( \ @ $_ );
chomp ( @ $_ );
frkExec ( \ @ $_ );
}
}
sub subSquiggly {
my $cmdRef = shift ;
my $index = 0 ;
foreach ( @$cmdRef ) {
@$cmdRef [ $index ] =~ s/~/$ENV{'HOME'}/g ;
$index ++ ;
}
}
sub subEnvVariable {
my $cmdRef = shift ;
my $index = 0 ;
foreach ( @$cmdRef ) {
if ( $_ =~ qr/^\$([[:alpha:]]+)/ ) {
@$cmdRef [ $index ] =~ s/\$(([A-Z]|_)+)/$ENV{$1}/g ;
}
$index ++ ;
}
}
sub runFile {
my $fileName = shift ;
$fileName =~ qr/\.([[:alpha:]]+)$/ ;
if ( $ 1 eq ' leshell ' ) {
my @command ;
my $file ;
if ( ! open ( $file , " < ", $fileName ) ) {
print " Cannot open file. \n ";
return ;
}
foreach ( < $file > ) {
push ( @command , $_ );
}
@command = cmdToArrayOfArrays ( " @command " );
runCommands ( \ @command );
} else {
print " file command needs an argument file with '.leshell' extension. \n ";
}
}
sub byebye {
print " See ya. \n ";
POSIX:_exit ( 0 );
}
sub getPrompt {
return $prompt ;
}
sub setPrompt {
$prompt = " @_ " . " "; # Mandatory space..
}
sub substituteHistory {
my $cmdRef = shift ;
$term -> remove_history ( $term -> where_history );
if ( $term -> where_history == 0 ) {
print " No history. \n ";
return 0 ;
}
if ( length ( @$cmdRef [ 0 ] ) == 1 ) {
return 0 unless getLastHistory ( \ @$cmdRef );
} else {
return 0 unless getSpecificHistory ( \ @$cmdRef );
}
return 1 ;
}
sub getLastHistory {
my $cmdRef = shift ;
my @history = $term -> GetHistory ();
my $size = @history ;
@$cmdRef = split ( /\s/ , $history [ $size - 1 ] );
return " america ";
}
sub getSpecificHistory {
my $cmdRef = shift ;
my $requestNumber = substr ( @$cmdRef [ 0 ], 1 );
if ( $requestNumber > $term -> where_history () ) {
print " Too big. \n ";
return 0 ;
}
@$cmdRef = split ( /\s/ , $term -> history_get ( $requestNumber ) );
return " awesome ";
}
sub hiya {
print " Got to hiya \n ";
kill ( 9 , $pid );
showPrompt ();
}
sub showPrompt {
if ( ! $debug ) {
PROMPT: while ( defined ( $_ = $term -> readline ( generatePrompt () ) ) ) {
my @commands = cmdToArrayOfArrays ( $_ );
next PROMPT unless expandGlob ( \ @commands );
runCommands ( \ @commands );
}
}
}
showPrompt ();
1
Leave a Comment