So yess, wonderful readers, I just wrote a batch script, not bash script!
Batch is the language in which scripts for windows command prompt(cmd.exe) are written.
What is special about this script?
That is what I'm going to discuss.
Why I Needed The Script (AKA The Problem)
While WSL is a powerful tool for Windows users who want to leverage Linux commands and environment, it's often cumbersome to open the WSL terminal every time you want to run only one command, while you are actually working on powershell or command prompt.
I know what u will say now. You'll say, "Use the wsl cli bro, that's it!"
But actually it's not just the cli that will always get you covered!
Suppose you are used to with some commands in your linux environment that are not any linux binary, they're some functions and aliases that are sourced when your shell is initialized.
For example this bash function (very convenient)
extract ()
{
local file="$1";
if [ -z "$file" ]; then
printf "\033[1;31mNo file specified\n\033[0m" 1>&2;
return 1;
fi;
if [ ! -f "$file" ]; then
printf "\033[1;31mFile '%s' not found\n\033[0m" "$file" 1>&2;
return 1;
fi;
local ext="${file#*.}";
local extractor;
local options="";
case "$ext" in
tar.bz2 | tbz2 | tar)
extractor="tar";
options="xvf"
;;
tar.gz | tgz)
extractor="tar";
options="xzvf"
;;
tar.xz)
extractor="tar";
options="Jxvf"
;;
bz2)
extractor="bunzip2"
;;
rar)
extractor="unar";
options="-d"
;;
gz)
extractor="gunzip"
;;
zip)
extractor="unzip"
;;
xz)
extractor="unxz"
;;
7z)
extractor="7z";
options="x"
;;
Z)
extractor="uncompress"
;;
*)
printf "\033[1;31mUnsupported file type: %s\n\033[0m" "$file" 1>&2;
return 1
;;
esac;
if ! "$extractor" $options "$file"; then
printf "\033[1;31mError extracting '%s'\n\033[0m" "$file" 1>&2;
return 1;
fi
}
I can extract any kind of (almost) archive file with this extract
function, combining all necessary tool. One function to extract them all. This and one other function is in my .bash_functions
file in my $HOME
directory of WSL.
or these aliases -
alias gla='git log --oneline --graph --all'
alias la='exa -a --icons --group-directories-first' # exa needs to be preinstalled
alias ll='exa -alhF --icons' # exa needs to be preinstalled
alias cdf='cd $(fd -t d | fzf)' # fd-find and fzf needs to be preinstalled
alias fvim='nvim $(fzf --preview="bat --color=always {}" --bind shift-up:preview-page-up,shift-down:preview-page-down)' # nvim, bat and fzf needs to be preinstalled
alias ff='fzf --preview=less --bind shift-up:preview-page-up,shift-down:preview-page-down)' # fzf and less needs to be preinstalled
These are the most notable aliases of mine ( I guess so ), which exist in the .bash_aliases
of my $HOME
directory of WSL.
The wsl cli is great for running linux binaries installed in the WSL system, outside the linux environment(WSL) for example in command prompt.
But none of provided options can make you run a custom command outside it.
The Solution
1st Approach
I thought, "Let's try to replicate the commands in windows..."
But soon I realized that this is highly inconvenient because though powershell has better syntax and configuration option, although it has functions that almost written the same way as in bash, replicating the custom commands( those functions and aliases ) is going to be really difficult & it would need to install the required softwares in windows.
And for cmd, just don't even think about it.
Also powershell and cmd doesn't support aliases like bash/zsh. Lastly, I couldn't find any init script like .bashrc
for command prompt (probably no such thing exists, if does then please let me know).
Now you may again say, "Hey if you want aliases, why just not use doskey in cmd?"
Again, DOSKEY is another highly inconvenient invention of windows devs. It is a command that can make macros for the interactive cli. It is inconvenient Because -
- The DOSKEY macros cannot be used on either side of a pipe: Both
someMacro|findstr '^'
anddir|someMacro
fail. - They cannot be used within a FOR /F commands:
for /f %A in ('someMacro') do ...
fails. - Macros written with DOSKEY, must be wrapped with % ( e.g. - if the name of the macro is
grep
then I would need to run it as %grep% ). For more info refer to this StackExchange answer.
So yeah I stopped there after researching about it.
2nd Approach
Here comes the good part. I realized, replicating commands will be waste of time. So let me try some way I can access every single command of the wsl I have, in command prompt working the exact same way. If there is no init script, I must make a command which can execute the bash functions and aliases along with all binaries.
So, I decided to write a batch script.
Why not powershell script?
Because powershell is slower than cmd. Even if it will be easier to write the script in powershell, as long as I know I need to write only 1 script & probably only 1 function, I think writing script for cmd is worth the try. And, along the way I will learn something new!
Yoohoo!
Steps
- Created a directory 'bin' in my user home directory. Added the path to bin in the environment variables.
- Created a file
runbash.bat
inside bin. This will be the script. - The syntax of batch is otherworldly to me. Hold on. Let's see the code of the script step by step. I am trying to explain the best I can.
- 1st line -
@echo off
. It will prevent command output from cluttering the console, while the script runs. - 2nd line -
setlocal enabledelayedexpansion
. This is for handling variables dynamically within loops. It enables delayed expansion, which allows the use of variables within the same command line (using !VAR! instead of %VAR%). -
set CMDLINE=
initializes an empty variable namedCMDLINE
, which will be used to accumulate the command line arguments. -
:loop
marks the beginning of a loop. -
if "%~1"=="" goto end
checks if the first argument (%~1
) is empty. If it is, the script jumps to the:end
label and stops processing arguments. -
set CMDLINE=!CMDLINE! %~1
appends the current argument (%~1
) to theCMDLINE
variable, building the command line. -
shift
shifts the command line arguments to the left, so%2
becomes%1
,%3
becomes%2
, and so on. This effectively removes the first argument and allows the loop to process the next one. -
goto loop
repeats the loop to process the next argument. -
:end:
marks the end of the loop, where all arguments have been processed. -
bash -ic "!CMDLINE!"
executes the accumulated command (CMDLINE
) in the default Bash shell, which the bash of the default wsl. Using the-i
(interactive) option to ensure it runs in an interactive shell. The-c
option tells Bash to execute the command string.
Finally the script would be this.
The Script
@echo off
setlocal enabledelayedexpansion
set CMDLINE=
:loop
if "%~1"=="" goto end
set CMDLINE=!CMDLINE! %~1
shift
goto loop
:end
bash -ic "!CMDLINE!"
So, that's it! Now I have a new command in my Windows environment which enables me to run any custom command in my WSL environment right within cmd! How cool is that!
Look at that, works like a charm!
Final thoughts
So the second approach was actually a good decision. I saved a lot of time and effort.
Now this command turbocharges my productivity.
Also, my default wsl distro is Void linux, not Ubuntu. So the bash shell initialization doesn't take much time and all the commands work really fast with runbash
.
Also the bonus is I don't need to open a separate git-bash
instance or wsl
to just run some bash files. I can write bash on windows and can execute them from the command prompt without opening wsl.
If you found this POST helpful, if this blog added some value to your time and energy, please show some love by giving the article some likes and share it with your friends.
Feel free to connect with me at - Twitter, LinkedIn or GitHub :)
Happy Coding 🧑🏽💻👩🏽💻! Have a nice day ahead! 🚀