Wednesday, January 26, 2011

Consistent Usage of Relative Paths in .bat Scripts

Seeing as I haven't posted anything in a while, I figured I'd post this example script demonstrating a simple to implement feature that makes it a lot easier to manage paths to your script's files (assuming you're using a script that depends on other files ;).

I find it quite annoying when I'm one directory away from a script I need to run, but calling `..\myScript.bat` results in tons of "..\callingDir\someScriptFolder not found" errors.

For this reason, I make it a point to cd to the script's directory when messing around with relative paths. The only problem with a straightforward cd is that once the script has finished running, you're left stranded in a folder in the middle of nowhere (assuming it's not just up a single directory).

The following example demonstrates storing the directory the script was called from, cd'ing to the script's directory, doing some stuff, and switching back to the original directory. It's nothing too special, but I find it quite useful.

REM Example script - changes to script directory then back to calling dir
@echo off
setlocal

REM Check if no arg was given
if "%1"=="" goto :printUsage

REM Check if copy-to directory isn't a valid dir (see usage)
if not exist %1\ goto :badArg

REM Store current directory and cd to script dir
set CALLING_DIRECTORY=%cd%
cd /d %~dp0

REM Copy files
copy copyTheseFiles\* %1

REM cd back to directory script was called from
cd /d %CALLING_DIRECTORY%

REM Exit successfully
goto :EOF

REM Print an error message
:badArg
echo Error locating folder^: %1
goto :EOF

REM Print the script's usage
:printUsage
echo Usage^: %0 [dir to copy stuff to]

:EOF
endlocal



One of my earlier posts: 'Listing Hidden Files in Bash, provides a more specialized example of doing the same thing in a bash script.

If you've read this far past the example, you may be interested in the setlocal and endlocal calls I've included. These prevent the environment variable CALLING_DIRECTORY from leaving the scope of the script and polluting your command prompt's environment.

I should probably also note that the %~dp0 uses the tilde argument d and p on variable 0 (the first argument (or name of the script)). The tilde 'operator' (if it can rightly be called that) can be used to perform expansions on the variable (in this case d (drive) and p (path)). More information on this can be found at the 'help' page for 'for' (`help for`). I've found it annoying that you can only use this with single letter variables (ie. command line arguments or variables in for loops), but what can you expect from the backwards-ass batch scripting language if not it's inconsistency?
Anyway...

That's all for now,
Happy scripting!

Tuesday, August 3, 2010

Running Cygwin in Your Current Directory

Most of the time I'm on the command line, I'm using the Windows command prompt. When I need a Bash shell (cygwin), it's really annoying to have to cd all the way back to the directory I was in before calling cygwin.

After locating Martin Gramatke's email to the Cygwin mailing list (Thanks Martin!), I put together an updated cygwin.bat, and .bash_profile in order to let Cygwin remember my working directory.

The first part is a modified cygwin.bat file. I've added c:\cygwin\bin to my PATH variable already, so it is no longer necessary to cd to the c:\cygwin\bin directory just to invoke bash. The below cygwin.bat file no longer changes directories on you, and also stores your current working directory in an environment variable so bash can tell if/where you would like to pick up working once bash has started.

@echo off
setlocal

for /f "usebackq delims=" %%i in (`cygpath "%cd%"`) do set BASHHERE=%%i

bash -li %*

endlocal
Note the handy 'for-piping' of the cygpath command into the BASHHERE variable.

The second part of the process involves telling bash to switch to your 'preferred' directory when your profile is loaded. Adding the following lines to the end of your .bash_profile (~/.bash_profile) will instruct bash to cd to the directory stored in the updated cygwin.bat script so you can seamlessly resume working in the same directory - using Cygwin!
if [ "$BASHHERE" != "" ]; then
cd "$BASHHERE"
fi
I normally put this at the end of the file.

Using both of these changes will mean that whenever you run cygwin using the cygwin.bat script, your currrent working directory will be preserved. If you wish to start bash in your home directory, don't make the call to cygwin.bat, and simply call bash.exe directly (with any options you may want)

Cheers!

Opening a Command Prompt in a Specific Folder

Hi everyone!

I was recently asked about opening a DOS prompt to a given folder and figured I would share the .reg file I keep around for this purpose.

Here it is:
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Folder\shell\Command Here...]

[HKEY_CLASSES_ROOT\Folder\shell\Command Here...\command]
@="cmd /K \"cd %1\""
(Copy, paste, save with .reg extension, run) ^_^

Note that the "Command Here..." option only appears if you right click on the folder you want the prompt to open in. This may mean you have to back out of the directory first so you can right click on it. If anyone's come up with a way around this, I'd be interested in hearing it - comments are welcome!

Sunday, July 25, 2010

Using Cygwin Emacs from DOS prompt

When I'm working in a command line environment in Windows, I normally use notepad for my text editing. On lunix, I prefer emacs. Earlier today I decided it would be nice if I could use emacs in my DOS prompt if I'm in the mood.

By taking advantage of bash's -c flag, it is possible to tell bash to open emacs on the given file(s). After converting the file paths to cygwin appropriate paths, the call is a piece of cake.

I put all this in a bat file on my path so I can call emacs from my DOS prompt at any time:

@echo off
setlocal

set TOEMACS_FILEPATHS=

REM Convert files to full cygwin paths and store in variable
:loopOnInputs
if "%1"=="" goto :callEmacs

REM Handy line for storing the output of a command in a variable
for /f "usebackq delims=" %%i in (`cygpath "%~dpnx1"`) do set ARG_CYGPATH=%%i

REM Store/append the file path in the variable we'll pass to emacs
if "%TOEMACS_FILEPATHS%"=="" (
set TOEMACS_FILEPATHS=%ARG_CYGPATH%
) else (
set TOEMACS_FILEPATHS=%TOEMACS_FILEPATHS% %ARG_CYGPATH%
)

REM Pop the argument we just read
shift
goto :loopOnInputs

REM Run emacs in a new bash shell
:callEmacs
bash -lic "emacs $*" dummy %TOEMACS_FILEPATHS%

:EOF
endlocal


While looping through the input arguments, you'll notice an interesting for loop that I'm using to pipe the output of the cygpath command to a variable. I spent a while trying to figure this out a while ago, and it's been working well for me so far. In short, it loops over every line in the command's output and sets the variable to that line. The delims option changes the delimiter used for separating the command output from spaces and tabs to the end of a line, and since the output of the cygpath command is only one line, we don't have to worry about overwriting the path when there are multiple lines.

Finally, I believe $* is interpreting my dummy argument as $0 or something similar, so that's what the dummy argument is doing there.

Enjoy!

Saturday, July 24, 2010

Listing Hidden Files in Bash

I decided it would be useful to have an alias to display all the hidden files in a directory, but wanted a bit more than just a simple alias to 'ls -Ad .**' as this only allows you to list the current directory. Aliasing 'ls -Ad $*.**' worked, but didn't provide any room for error checking regarding trailing directory slashes. There was also the grep option, but that messes with the nice column formatting ls provides so I scrapped that idea.

In short, I decided to write up a function with a short name that I could use as an alias:

# list only hidden files in the given directory(s)
function lh() {

# if directories were given
if [ $# -gt 0 ]; then

# store current dir so we can come back
startingPath=`pwd`

# loop through input dirs
for i in $*; do

# only list contents for directories
if [ -d $i ]; then
cd $i # cd to the directory
echo $i: # print a header
ls -Ad .** # list hidden files
else
echo Ignoring directory: $i
fi

echo # blank line for readability

done

# cd back to your original directory
cd $startingPath

else

# just list hidden files for current directory
ls -Ad .**

fi
}

First post!

First post!
Hi all! I've decided to put up this blog to share some of the handy tricks I've come across while working on scripts that you guys might find useful as well. I'm not sure what I'll do about posting some of the scripts I've already come up with, but as I write up new ones, I'll try to post them here. Keep an eye out for interesting coding tidbits as well!