See also: The Introduction | Part 1: The Path | Part 2: The Loop | Part 3: The Execution | Part 4: The Interrupt.
Our shell doesn't cope with programs added after it has been started (and has traversed the path). There are several ways this can be fixed:
If the program isn't in our list, update the list of programs and see if that does the trick.
The downside is that typing a non-existing program causes a lot of file system access.
Add an explicit «rehash» command that does a manual update of the list of programs.
This is easy, just add this line to the gather
block:
when "rehash" { %programs = (); do-rehash; }
Update the list of commands:
%commands{$_} = True for <cd exit help pwd rehash version which6>;
The full program is available as «raku-rehash».
The alias keyword can be used to create real aliases. We can reuse the already written - and discarded - code for handling a configuration file (and the «allow» keyword).
We start with adding code to add aliases, and list them (with the same command), «alias»:
my %aliases;
multi sub do-alias ($command)
{
my ($alias, $full) = $command.split(/\s/, 2);
%aliases{$alias} = $full;
}
multi sub do-alias ()
{
say "$_ -> %aliases{$_}" for %aliases.keys.sort;
}
And these lines in the given
block:
when "alias" { do-alias; }
when /^alias\s+(.*)/ { do-alias $0; }
Update the list of commands:
%commands{$_} = True for <alias cd exit help pwd rehash version which6>;
Using the aliases doesn't work yet. Adding these lines just above the given
block fixes that:
my $cmd = $line.words[0]; # [1]
$line.= subst(/^$cmd/, %aliases{$cmd}) if %aliases{$cmd}; # [2]
[1] Get the first word of the command.
[2] If it is an alias (e.g. «e») expand it to the full name (e.g. «emacs»).
And we are good to go.
The full program is available as «rash-alias».
We can test it:
$ raku rash-alias
rash: Enter «exit» to exit
> alias e emacs
> alias m more
> alias
e -> emacs
m -> more
Run some of them, and check that command completion works as well.
Note that commands added with «alias» are not recognized by the «Linenoise» command completion. We can fix that, partially, by adding support for a configuration file with aliases.
We had support for a configuration file several version ago. «rash-commands» was the last one that had it. So we copy the code from there.
Add this line just above «do-rehash»:
read-conf;
Add back this line:
constant CONF_FILE = ( $*HOME.add: '.rashrc' ).Str;
Then we add the list of aliases to the «Linenoise» callback:
my @all = (|%commands.keys, |%programs.keys, |%aliases.keys);
Note that this adds aliases from the configuration file to the list of commands recognized by «Linenoise». Aliases added manually (on the command line) are not.
Copy the old «read-conf» and change it:
sub read-conf
{
return unless CONF_FILE.IO.r;
for CONF_FILE.IO.lines
{
when /^alias\s+(.*)/ { do-alias $0.Str }
}
}
Then update the configuration file with something like this:
File: ~/.rashrc
alias e emacs
alias m more
alias ll ls -l
alias t top
Check that it works:
$ raku rash-alias2
rash: Enter «exit» to exit
> alias
e -> emacs
ll -> ls -l
m -> more
t -> top
The full program looks like this:
File: rash-alias2
use Linenoise;
constant HIST_FILE = ( $*HOME.add: '.rash-hist' ).Str;
constant HIST_LEN = 25;
constant CONF_FILE = ( $*HOME.add: '.rashrc' ).Str;
linenoiseHistoryLoad(HIST_FILE);
linenoiseHistorySetMaxLen(HIST_LEN);
my %commands; %commands{$_} = True for <alias cd exit help pwd rehash version which6>;
my %programs;
my %aliases;
read-conf;
do-rehash;
linenoiseSetCompletionCallback(-> $line, $c
{
my @all = (|%commands.keys, |%programs.keys, |%aliases.keys);
for @all.grep(/^ $line /).sort -> $cmd
{
linenoiseAddCompletion($c, $cmd);
}
});
say 'rash: Enter «exit» to exit';
signal(SIGINT).tap();
while (my $line = linenoise '> ').defined
{
linenoiseHistoryAdd($line);
my $cmd = $line.words[0];
$line.= subst(/^$cmd/, %aliases{$cmd}) if %aliases{$cmd};
given $line
{
when "alias" { do-alias; }
when /^alias\s+(.*)/ { do-alias $0; }
when /^cd\s+(\S+)/ { do-chdir $0; }
when "exit" { last; }
when "help" { say "Built-in commands: { %commands.keys.sort }" }
when "pwd" { say $*CWD.Str; }
when "rehash" { %programs = (); do-rehash; }
when "version" { say "Version 0.17"; }
when /^which6\s+(\S+)/ { do-which $0; }
default
{
my ($cmd, @args) = $line.words;
%programs{$cmd}
?? do-run %programs{$cmd}, @args
!! say "Unknown command: \"$_\" (use \"help\" for a list of commands)";
}
}
}
linenoiseHistorySave(HIST_FILE);
sub do-run ($cmd, @args)
{
my $res = @args
?? run $cmd, @args
!! run $cmd;
if ! $res.pid
{
say "$cmd: command not found";
}
elsif $res.exitcode
{
say "$cmd: exit with code { $res.exitcode }";
}
}
sub do-chdir ($dir)
{
say "cd: $dir: No such file or directory" unless chdir $dir;
}
sub do-rehash
{
for %*ENV.split(":") -> $dir
{
next unless $dir.IO.d;
%programs{.Str} = "$dir/" ~ .Str for indir($dir, { dir(:x) }).sort
}
}
sub do-which ($command)
{
if %commands{$command}
{
say "$command is a rash built-in command.";
}
else
{
say %programs{$command} if %programs{$command};
}
}
multi sub do-alias ($command)
{
my ($alias, $full) = $command.split(/\s/, 2);
%aliases{$alias} = $full;
}
multi sub do-alias ()
{
say "$_ -> %aliases{$_}" for %aliases.keys.sort;
}
sub read-conf
{
return unless CONF_FILE.IO.r;
for CONF_FILE.IO.lines
{
when /^alias\s+(.*)/ { do-alias $0.Str }
}
}
Make it illegal to add an alias with the same name as a built-in command:
$ raku rash-safe
> alias version /usr/bin/gnats
Unable to redefine internal command: version
See The solution.
It isn't possible to remove an alias, only replace it with a new definition. Fix this with a new «unalias» command. It takes one argument, which is the alias to remove.
Tip: Use the :exist
adverb to check if a key is
present in a hash, and the :delete
adverb to remove it (the key/value
pair). See
docs.perl6.org/language/subscripts#:exists and
docs.perl6.org/language/subscripts#index-entry-:delete_(subscript_adverb)
for details.
Note that removing an alias that came from the configuration file leaves the name in the Linenoise command completion list. Fix this by setting up the callback again after removal or addition of an alias.
See The solution.
See the next part; Part 6: The Process.