Manipulate %PATH% in the current Windows cmd.exe window with help from Perl A. Sinan Unur June 11, 2016

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 = ( SUCCESS => 0, HELP_REQUESTED => 1, INSERT_POSITION_NOT_FOUND => 2, INSERT_LOCATION_UNKNOWN => 255, ); BEGIN { if ($^O eq 'MSWin32') { $SEP = ';'; } else { $SEP = ':'; } } run(@ARGV); sub run { my ($cmd, @args) = @_; my %dispatch = ( ia => sub { insert_dir(after => @_) }, ib => sub { insert_dir(before => @_) }, 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) = @_; validate_pattern($pattern); validate_dir($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'

"; exit $EXIT{INSERT_LOCATION_UNKNOWN}; } } sub remove { my ($path, $pattern) = @_; validate_pattern($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

"; } sub validate_pattern { defined($_[0]) or die "Need a pattern

"; } 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 = ( [ remove($path, 'b'), 'cd' ], [ remove($path, 'z'), $path ], [ insert_dir(after => $path, '^a', 'z'), 'ab;z;bc;cd' ], [ insert_dir(after => $path, 'c', 'z'), 'ab;bc;z;cd' ], [ insert_dir(before => $path, 'c\z', 'z'), 'ab;z;bc;cd' ], [ insert_dir(before => $path, '^c', 'z'), 'ab;bc;z;cd' ], ); 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.