I often find myself working in one ConEmu window for extended periods of time. Sometimes, I end up adding a bunch of directories to the %PATH%
during a session. And, more often than not, I end up wanting to just remove one or two directories from the %PATH%
and continue working. For example, I might want to switch to using Cygwin’s git
instead of the MinGW git installed alongside Visual Studio 2015.
So, I wrote a quick Perl script to do that. It outputs a string so I can grab that and set the path in the current shell.
But, of course, that felt incomplete. I thought I should also be able to insert a directory before or after another directory in the %PATH%
. So, I added that. The script below manipulates the %PATH%
and writes a replacement string to STDOUT
. It is intended to be called from a batch file to set %PATH%
in the current command line window. It includes some rudimentary tests to help me avoid obvious mistakes.
#!/usr/bin/env perl
use 5.024; # why not?!
use warnings;
my $SEP;
my %EXIT = (
0,
SUCCESS => 1,
HELP_REQUESTED => 2,
INSERT_POSITION_NOT_FOUND => 255,
INSERT_LOCATION_UNKNOWN =>
);
BEGIN {
if ($^O eq 'MSWin32') {
$SEP = ';';
}else {
$SEP = ':';
}
}
@ARGV);
run(
sub run {
my ($cmd, @args) = @_;
my %dispatch = (
sub { insert_dir(after => @_) },
ia => sub { insert_dir(before => @_) },
ib =>
help => \&help,
rm => \&remove,
test => \&test,
);
unless (defined($cmd) and exists $dispatch{$cmd}) {
$cmd = 'help';
}
my $handler = $dispatch{$cmd};
my $output = $handler->($ENV{PATH}, @args);
say $output;
exit $EXIT{SUCCESS};
}
sub insert_dir {
my ($where, $path, $pattern, $dir) = @_;
$pattern);
validate_pattern($dir);
validate_dir(
my ($before, $pivot, $after) = bisect_path($path, $pattern)->@*;
if (not defined($pivot)) {
exit $EXIT{INSERT_POSITION_NOT_FOUND};
}elsif ($where eq 'after') {
return join($SEP => $before->@*, $pivot, $dir, $after->@*);
}elsif ($where eq 'before') {
return join($SEP => $before->@*, $dir, $pivot, $after->@*);
}else {
warn "Unknown position '$where'\n";
exit $EXIT{INSERT_LOCATION_UNKNOWN};
}
}
sub remove {
my ($path, $pattern) = @_;
$pattern);
validate_pattern(
join(';', grep ! /$pattern/i, split /\Q$SEP/, $path);
}
sub bisect_path {
my ($path, $pattern) = @_;
my @dirs = split /\Q$SEP/, $path;
my (@before, $pivot);
while ($pivot = shift @dirs) {
last if $pivot =~ /$pattern/i;
push @before, $pivot;
}
return [\@before, $pivot, \@dirs];
}
sub validate_dir {
defined($_[0]) or die "Need a directory\n";
}
sub validate_pattern {
defined($_[0]) or die "Need a pattern\n";
}
sub help {
print STDERR <<EO_HELP;
Remove directories matching pattern from \$PATH
pathmanip rm /pattern/
Insert directory before entry matching pattern in \$PATH
pathmanip ib /pattern/ dir
Insert directory after entry matching pattern in \$PATH
pathmanip ia /pattern/ dir
EO_HELP
exit $EXIT{HELP_REQUESTED};
}
sub test {
my $path = 'ab;bc;cd';
my @cases = (
$path, 'b'), 'cd' ],
[ remove($path, 'z'), $path ],
[ remove($path, '^a', 'z'), 'ab;z;bc;cd' ],
[ insert_dir(after => $path, 'c', 'z'), 'ab;bc;z;cd' ],
[ insert_dir(after => $path, 'c\z', 'z'), 'ab;z;bc;cd' ],
[ insert_dir(before => $path, '^c', 'z'), 'ab;bc;z;cd' ],
[ insert_dir(before =>
);require Test::More;
Test::More->import(tests => scalar @cases);
exit grep !$_, map is($_->@*), @cases;
}
Here is the batch file that drives it:
@echo off
set sinan_savedpath=%path%
if /i "%1" EQU "test" goto TEST
for /f "usebackq delims=|" %%f in (`perl c:\opt\bin\pathmanip.pl "%1" "%2" "%3"`) do (
if %ERRORLEVEL% NEQ 0 goto END
path=%%f
)
goto END
:TEST
perl c:\opt\bin\pathmanip.pl test
:END
I called the batch file cpath.bat
. I am a wuss, so I save the current path in case I want to quickly restore it after an unintentional change.
Can I do this using cmd.exe
builtins only?
Originally, I had wanted to this using only built in cmd.exe
facilities, but I haven’t been able to figure out how to get findstr
to terminate in the following:
for /f "usebackq delims=|" %%I in (`findstr /r/i "%pattern%" ^| echo "%adir%"`) do (
...
)
Or, similarly, from the command line, I would be grateful if there is a way to have
findstr test | echo test
terminate without user input. I don’t want to create temporary files.
PS: I am sure you can do this using some Powershell feature, but that’s not where I spend most of my time.
PPS: You can discuss this post on r/perl.
PPPS: Sure, this would probably also work in *nix shells, but I don’t have to fiddle with paths as much there and I haven’t tried it.