Sunday, March 29, 2015

some minor windows .bat scripting

I think it's really cool that you can make some simple scripts to ease your life. It's just a matter of knowing how to search for it online, maybe code, and thinking of it!

Alright, so I had this friend who didn't have a java compiler, and the internet was shaky, so she didn't want to go for the install. So she asks me if I can simply compile for her, and tell her the error messages. Simple enough! (obviously a bit tedious for her debugging, but hey)

I started by manually typing
>>javac Yosi.java
and just reading out the first error message. Not very efficient.

Next step, I manually typed
>>javac Yosi.java > log.txt
but that just output nothing to log.txt and everything to the console.

So, search engine, and
>>javac Yosi.java > log.txt 2>&1
works like a charm. I don't know exactly what the 2>&1 does, But according to this, it has at least something to do with having two output streams? Anyway.

Then each time I did this, I had to delete the Yosi.java file before I downloaded it again (from my friend), to avoid the Yosi (1).java filenaming.. so I thought, why not put this in a batch script, that would do the compiling and then deleting the file? Simple enough. So my first file just looked like this
#runyosi.bat 
javac Yosi.java > log.txt 2>&1
rm Yosi.java
and worked like a charm. Soon enough, I got some error messages relating to the Yosi.java not existing (when running the script without the file present), so naturally, I updated the script to account for this
#runyosi.bat 
if exist Yosi.java ( 
  javac Yosi.java > log.txt 2>&1
  rm Yosi.java 
)
Great! Indeed. Then soon enough, I got a file with a new name, Rugl.java, so instead of just changing the Yosi.java in my .bat file, I decided to add another if clause. (had to rename the log file, to account for being able to compile them both at the same time)
#runyosi.bat 
if exist Yosi.java (
  javac Yosi.java > log.txt 2>&1
) 

if exist Rugl.java (
  javac Rugl.java > log2.txt 2>&1
  rm Rugl.java 
)
Now, quickly I realized, this could be generalized! And of course (all of this takes a lot longer than simply doing everything manually, but that's just because I have to search for every other thing) that's what I set out to do.

I don't remember my steps exactly, but I had to search for many many things, here is the final file, I'll explain it a bit then.
#runyosi.bat
@echo off
setlocal enabledelayedexpansion

echo starting .java compilation check..
set list=Yosi.java Rugl.java View.java
echo going to test file list: %list%
for %%x in (%list%) do (
  echo checking file %%x ..
  if exist %%x (
    echo compiling file %%x ..
    set name=%%~nx
    javac %%x > log!name!.txt 2>&1
    echo removing file %%x ..
    rm %%x
    echo file %%x removed
  )
)
The @echo off command doesn't turn off the stuff I put in echo commands as you might think, but the automatic echoing of commands, that is, for example, without the @echo off, this code would run like this
#test.bat 
mkdir yeah
rm Foo.java 

>>test
>>mkdir yeah
>>rm Foo.java
that is, I run the test.bat by typing test, and then it echoes what it's doing, similar to if I had typed it myself.

I'll get into the setlocal... later. I added a bunch of simple print status commands like echo starting .java compilation check.. which just print exactly what you'd think. 
set list=Yosi.java Rugl.java View.java
Here, I make a list of all the files I'm going to check for. Notice how there are no spaces between the equal sign and "set list". This is very important, without this, it would not work. The non-space after the equal sign I don't think is needed, but I kept it anyway.
for %%x in (%list%) do ( 
  code
)
I loop over each element in the list variable and execute the code each time on the element x.
set name=%%~nx
javac %%x > log!name!.txt 2>&1
Now things are getting more interesting, alright. So I wanted to be able to output something like "logYosi.txt" and "logRugl.txt" depending on the file name. I started by doing only
javac %%x > log%%x.txt 2>&1
but as you can see, this gives us e.g. logYosi.java.txt. Not quite what we want. Then I set out to find how to eliminate the .java. I figured something like a substring should work, leading me to do something like
javac %%x > log%%x:~0,-5.txt 2>&1
which has the -5 to omit the last 5 letters (the .java). However, I wasn't sure that would work, since the syntax examples had the notation like this
%var:~start,end%. And yes, it indeed did not work. So then I thought of making a variable like thus
set name=%%x
javac %%x > log%name:~0,-5%.txt 2>&1
all done? Not quite. In fact, I thought this worked for some time, because it did output the correct filename when checking the FIRST file in the list. It was not until I tried the second file, realizing then it still named the log file "logYosi.txt". This is because using the set name as is, simply sets it once and then doesn't change it. So google away! And of course, a solution was found! That's where the
setlocal enabledelayedexpansion
line comes from. Apparently you have to enable delayed expansion, whatever that means, to use variables like this, and you have to use !var! instead of %var%. And in searching for that solution, I quite conveniently found that if you do
set name=%%~nx
it sets name as e.g. Yosi. So it takes away the extension. I assume the ~n stands for name, that is getting only the filename, not extension.

Then nothing left but rm %%x where rm stands for remove. I think you can also use del, but I remembered rm worked (rm like in linux).

So there you have it! A simple batch script that compiles a predefined set of java files and outputs compile errors to a log and subsequently deletes the .java files.


Batch .bat windows script shell if syntax: http://www.robvanderwoude.com/ntif.php
Comments in batch scripts: http://www.robvanderwoude.com/comments.php (none of those are in the script right now, but I used them in development of the script, even successfully within the for block)