164 lines
4.4 KiB
Perl
Executable file
164 lines
4.4 KiB
Perl
Executable file
#!/usr/bin/env perl
|
|
|
|
use lib 'lib';
|
|
use History::SQLite;
|
|
use Environment::SQLite;
|
|
use Term::ReadLine;
|
|
use Cwd;
|
|
use File::HomeDir;
|
|
use CustomCommands;
|
|
use POSIX qw(getlogin getuid setsid tcsetpgrp);
|
|
|
|
# there is only one
|
|
|
|
setsid(); # Start a new session
|
|
tcsetpgrp(0, getpgrp()); # Set TTY foreground process group
|
|
|
|
# some env vars
|
|
|
|
$ENV{HOME} ||= File::HomeDir->my_home;
|
|
$ENV{USER} ||= getlogin() || (getpwuid(getuid()))[0] || 'user';
|
|
$ENV{LOGNAME} ||= $ENV{USER};
|
|
$ENV{SHELL} ||= $0; # Path to pshell.pl or compiled binary
|
|
$ENV{PATH} ||= '/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin';
|
|
$ENV{TERM} ||= 'xterm-256color';
|
|
|
|
# we a login shell?
|
|
|
|
my $is_login_shell = ($0 =~ /^-/ || $ARGV[0] eq '-l');
|
|
if ($is_login_shell) {
|
|
# Clear inherited environment for security, except essentials
|
|
%ENV = (
|
|
HOME => $ENV{HOME},
|
|
USER => $ENV{USER},
|
|
LOGNAME => $ENV{LOGNAME},
|
|
SHELL => $ENV{SHELL},
|
|
TERM => $ENV{TERM},
|
|
);
|
|
# Reload system and user configs
|
|
}
|
|
|
|
# system wide env vars
|
|
|
|
# Parse /etc/environment silently
|
|
if (-r '/etc/environment') {
|
|
open my $fh, '<', '/etc/environment' or do {
|
|
warn "Could not open /etc/environment: $!" if $is_login_shell;
|
|
next;
|
|
};
|
|
while (my $line = <$fh>) {
|
|
chomp $line;
|
|
next unless $line =~ /^(\w+)=(.*)$/;
|
|
$ENV{$1} = $2 unless exists $ENV{$1};
|
|
}
|
|
close $fh;
|
|
}
|
|
|
|
# Source /etc/profile silently
|
|
if (-r '/etc/profile') {
|
|
# Run /etc/profile in a subshell, capture env output, and suppress other prints
|
|
my $env_output = qx(/bin/sh -c '. /etc/profile >/dev/null 2>&1 && env');
|
|
if ($? == 0) {
|
|
while ($env_output =~ /^(\w+)=(.*)$/mg) {
|
|
$ENV{$1} = $2 unless exists $ENV{$1};
|
|
}
|
|
} else {
|
|
warn "Could not source /etc/profile: $!" if $is_login_shell;
|
|
}
|
|
}
|
|
|
|
|
|
# Initialize command history and environment
|
|
my $history = History::SQLite->new(
|
|
db_path => 'pshell_history.db'
|
|
);
|
|
my $env = Environment::SQLite->new(
|
|
db_path => 'pshell_env.db'
|
|
);
|
|
|
|
# Signal handling setup
|
|
$SIG{INT} = sub { print "\n"; };
|
|
$SIG{CHLD} = 'IGNORE';
|
|
|
|
print "Perl Shell (pshell) - Type 'exit' to quit\n";
|
|
|
|
my $term = Term::ReadLine->new('pshell');
|
|
$term->ornaments(0);
|
|
|
|
# Workaround for Ctrl-R binding with Term::ReadLine::Gnu
|
|
$term->add_defun('history-search', sub {
|
|
my ($count, $key) = @_;
|
|
my $search_term = $term->readline('Search history: ');
|
|
return unless defined $search_term && length $search_term;
|
|
|
|
my @matches = grep { $_ =~ /\Q$search_term\E/i } $history->get_all;
|
|
if (@matches) {
|
|
print "\nMatching commands:\n";
|
|
for my $i (0..$#matches) {
|
|
print sprintf("%2d: %s\n", $i+1, $matches[$i]);
|
|
}
|
|
|
|
my $choice = $term->readline('\nSelect command to run (number or Enter to cancel): ');
|
|
if ($choice =~ /^\d+$/ && $choice > 0 && $choice <= @matches) {
|
|
$term->addhistory($matches[$choice-1]);
|
|
$term->replace_line($matches[$choice-1]);
|
|
$term->redisplay;
|
|
}
|
|
} else {
|
|
print "\nNo matching commands found\n";
|
|
}
|
|
|
|
return 0;
|
|
});
|
|
$term->parse_and_bind('"\C-r": history-search');
|
|
|
|
# Load previous history
|
|
my @history = $history->get_all;
|
|
$term->addhistory($_) for @history;
|
|
|
|
# Load environment variables
|
|
my %env_vars = $env->get_all;
|
|
$ENV{$_} = $env_vars{$_} for keys %env_vars;
|
|
|
|
# Load and execute .pshellrc if it exists
|
|
my $rc_file = File::HomeDir->my_home . '/.pshellrc';
|
|
if (-e $rc_file && -r $rc_file) {
|
|
open my $fh, '<', $rc_file or warn "Could not open $rc_file: $!";
|
|
while (my $line = <$fh>) {
|
|
chomp $line;
|
|
next unless length $line;
|
|
if ($line =~ /^(\w+)=(.*)$/) {
|
|
$ENV{$1} = $2;
|
|
$env->set($1, $2);
|
|
} else {
|
|
system($line);
|
|
}
|
|
}
|
|
close $fh;
|
|
}
|
|
|
|
while (1) {
|
|
my $cwd = getcwd();
|
|
my $prompt = "pshell:$cwd> ";
|
|
my $command = $term->readline($prompt);
|
|
|
|
last unless defined $command;
|
|
chomp $command;
|
|
last if $command =~ /^exit$/i;
|
|
|
|
# Skip empty commands
|
|
next unless length $command;
|
|
|
|
# Handle command
|
|
my $cmd_status = CustomCommands::handle($command, $env);
|
|
if ($cmd_status >= 0) { # 1=handled, 2=system command
|
|
# Save valid commands to history
|
|
$history->add($command);
|
|
$term->addhistory($command);
|
|
next;
|
|
}
|
|
|
|
# Command not found
|
|
print "Command not found: $command\n";
|
|
next;
|
|
}
|