Use Relative Paths In Your Batch Files
Reader Paul writes in with an interesting tip for using relative paths in a batch file: you can use a special code to represent the current path—useful for batch files on a Flash drive.
We’ve previously covered a similiar way to create shortcuts that run off a USB stick—but if you need the full path to the batch file regardless of the machine you plugged it into, you can substitute %~dp0 anywhere you want to use the current path of the batch file. Paul explains:
If you use “%~dp0″ (sans quotes) in a batch file, this will point to the batch file’s path. For example :
SET MAC=00:00:00:00:00:00
%~dp0mc-wol.exe %MAC%MC-WOL.EXE is a wake-on-lan program, and this script wakes up the PC ready for the boss to remotely access his PC. I can get him to wake up his PC remotely without confusing the poor thing too much.
It’s one of those tips that might not be useful for everybody, but it could really come in handy as part of your flash drive toolkit. For more, you can check out how FAST is a geeky command-line database, or how to kill multiple applications from the command line with a batch file. Thanks, Paul!
Got any useful batch-scripting wisdom to share? Tell us in the comments.
- Next Post: Speed Up Your Downloads By Choosing The Fastest Torrents »
- « Previous Post: Cameras Improves The Way OS X Deals With Connected Cameras
Comments (AU Comments | US Comments)
PowerShell isn't so bad. I haven't delved too deeply into it, but it's much more powerful than cmd.
However, the setback to PowerShell is that it's not installed by default on existing OS's. When we roll out Windows 7 at my company, things will get much easier in terms of scripting workstations.
Slightly off topic, but
I used to create my personal unattended install disk using a 30+ printed page batch file called HFSLIP.
And for anyone who is serious about writing batch files, Timo Salmi's Batch File FAQ is a must have.
@lostarchitect:
Then you'd be surprised at all the new features they keep adding.
I think it's also worth noting that %~dpn1 gives the path of te first file passed in as a parameter. I used this to write a batch file to convert video files to iPod-compatible videos.
@echo off
cd /d %~dp0
start "iPod Conversion: Normal Aspect Ratio" /BELOWNORMAL "ffmpeg.exe" -vcodec xvid -b 500 -qmin 3 -qmax 5 -bufsize 4096 -g 300 -acodec aac -ab 96 -threads 2 -i "%~dpnx1" -s 320x240 "%~dpn1.ipod.mp4"
This way, you can drag and drop any video file supported by ffmpeg onto the batch file, and the converted file will be created in the same folder as the original file, but with a .ipod.mp4 extension.
There's a great explanation of how to get the drive letter, path, file name and extension from a parameter here.
krn
@eagles500: Some people might agree, but your comment is hardly helpful unless you provide some actual code.
peanut_butter
@nicoco: sorry, I missed the first bit:
In addition, substitution of FOR variable references has been enhanced.
You can now use the following optional syntax:
%~I - expands %I removing any surrounding quotes (")
%~fI - expands %I to a fully qualified path name
%~dI - expands %I to a drive letter only
%~pI - expands %I to a path only
%~nI - expands %I to a file name only
%~xI - expands %I to a file extension only
%~sI - expanded path contains short names only
%~aI - expands %I to file attributes of file
%~tI - expands %I to date/time of file
%~zI - expands %I to size of file
%~$PATH:I - searches the directories listed in the PATH
environment variable and expands %I to the
fully qualified name of the first one found.
If the environment variable name is not
defined or the file is not found by the
search, then this modifier expands to the
empty string
nicoco
There are other interesting %~ things you can do in batch.
Type "help for" on the command line to find out:
The modifiers can be combined to get compound results:
%~dpI - expands %I to a drive letter and path only
%~nxI - expands %I to a file name and extension only
%~fsI - expands %I to a full path name with short names only
%~dp$PATH:i - searches the directories listed in the PATH
environment variable for %I and expands to the
drive letter and path of the first one found.
%~ftzaI - expands %I to a DIR like output line
In the above examples %I and PATH can be replaced by other valid
values. The %~ syntax is terminated by a valid FOR variable name.
Picking upper case variable names like %I makes it more readable and
avoids confusion with the modifiers, which are not case sensitive.
nicoco
I am afraid. I am afraid because a bunch of people may yet pounce on this thread with Bash scripts! :)
paintbox
@eagles500: Comments like yours are like flies, inevitable yet predictable. Alas.
paintbox
@eagles500: Yeah, 'cause a portable USB drive used on Windows machines won't be rendered useless by the propensity of Windows to come without bash installed!
I dare you to follow that series of sarcastic negatives!
Or were you suggesting a portable version of bash with 50MB of Cygwin DLLs attached? Totally worth it!
@Brandon Smith:
Why not just run a command like this one (assuming your batch file is named test.cmd)
@for %%f in (*.*) do @if %%f neq test.cmd @ren %%f %%f.old
I assume you already have the rename command working, so just sub it with whatever you are using. Single command to do all of the magic (no perl and regex required).
@lamintak: I think the main difference is that the %~dp0 is the path to the batch file regardless of where the current directory (.) or parent directory (..) point to.
Imagine this scenario:
c:> f:\flash-files\some.bat
contents of some.bat
..\someother.bat
the batch file won't find someother.bat because the parent is still pointing to the c drive.
if the contents of some.bat were this:
%~dp0\..\someother.bat
It should succeed.
In my job we use many batch files to install programs so I've written a program based off of BatchRunner so that I have a nice little gui to run all the batch scripts. I do this because the batch files are written by all sorts of different people who have used different naming conventions and commands for each. Some rely on each other so I cannot rename them either. My program is customized for our work but I'd recommend checking out Batch Runner if you want an easy way to catalog your batch scripts.
[corz.org]
jimforcy
you know, i don't think i've written a batch file in... geeze. 10 years? wow.
@Brandon Smith: If you have a limited number of extensions to deal with, you can try something like this:
ren *.log *.csv
This will leave your .cmd file alone and you can have it clean up after itself.
You could use also use a FOR command, it's a bit ugly but works well (this is all on one line in your batch file):
FOR %%f in (*.*) DO @(IF NOT %%f==%~n0%~x0 REN %%f %%~nf.csv)
Where %~n0%~x0 expands to the name of the current batch file (you can use just %0 if you ALWAYS use the full name of the batch file when you run it) and %%~nf is the filename up to but not including the extension.
The FOR command is grossly underutilized in Windows command shell - it actually has 5 or 6 different functions with billions and billions of possibilities.
Jim
@kevin:
Sorry Kevin, I don't think this works. I got no change after running it. Is there something that I'm missing here? (havent had much exp with batch scripting)
Brandon Smith
This can be used if you want to be able to run batch files with relative paths as administrator easily in Vista/7. Just add "cd /d %~dp0" near the top of the script (below "@echo off" would be good). It can be used in a 'portable' batch file as well, I just thought that I would highlight its seemingly unusual effect.
It's not unusual at all, in fact. Vista/7/su(do) all reset all or most of the environment (including the working directory) when permissions are elevated.
LethAL
@Brandon Smith:
Why not just write the batch script at one level higher, and tell it to CD to the directory then do it, then your batch file you created won't be touched.
Just a suggestion!
Onaclov
Onaclov Nation
onaclov2000
@Brandon Smith: Here's a batch script that will rename every file in its folder from *.* to *.csv, and then deletes itself. All of the %~whatever magic is explained if you type "for /?" (sans quotes).
for %%i in (*.*) do call :SELECTIVERENAME %%i
del %~f0
goto :EOF
:SELECTIVERENAME
if %~f1 == %~f0 goto :EOF
ren %~f1 %~n1.csv
PUSHD and POPD have been part of Windows since XP. http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/pushd.mspx?mfr=true
MavisGadhero
@Brandon Smith: Good luck with that. I don't think it works the way you hope it does.
When it comes to renaming/moving files, you definitely want to create an exact list of files to work on.
If you are on *nix, here's a trivial perl script to rename files using regex notation:
#!/usr/bin/perl -w
$op = shift or die "
Usage: rename expr [files]
chomp(@ARGV = ) unless @ARGV;
for (@ARGV) {
$was = $_;
eval $op;
die $@ if $@;
rename($was,$_) unless $was eq $_;
}
TheFu
Be careful about trusting the current PATH when you run scripts. Almost every external program called should be completely, fully specified inside the script (or via an initialization `source`) to limit the probability of running the wrong program in your scripts. That's why root accounts should never have '.' in the PATH. Simply too dangerous.
Scripting in Windows is painful when compared to almost any other system. Perhaps wsh will change that. BAT/CMD scripts are a joke when compared to the alternatives. Heck, even an IBM mainframe running MVS has had REXX for 15+ years.
In *nix systems, the `whence` command can be used to determine which directory your script is being run from.
TheFu
So I am currently trying to make a batch file that change the extension of all the files within a folder. This is done fairly easily with the REN function but this causes the actual batch file to get renamed (considering its in the same directory when I run it). I would most prefer that it gets deleted after being used but unfortunately it gets renamed mid process so the deletion doesnt work. Is there a better way to do this by referencing the path but making it so that no batch code would have to be changed each time its used? This is the current code (only one line). Anyone have any thoughts?
ren "*.*" "*.csv"
Brandon Smith
@P_Smith:
Whoops, I neglected to include the URL of the main Garbo archive:
[garbo.uwasa.fi]
P_Smith
If you need to run directories in specific places but where you are running them from changes, you can use the PUSHDIR and POPDIR utilities from the "batpower" utility package at the Garbo DOS archive:
ftp://garbo.uwasa.fi/pc/batchutil/batpower.zip
The Garbo site is also worth checking for many other classic DOS programs.
PUSHDIR saves the current directory into a text file, and the directories can be "stacked" if you run PUSHDIR more than once. POPDIR does the reverse, removing the "top" of the stack and changing to that directory. The text file for the stack is put on the root directory of the drive that COMMAND.COM was run from.
If you wanted to copy a directory listing of DLLs from your windoze directory, you could run this batch file. Its design with use of PUSHDIR and POPDIR guarantees that no matter where you run it from, that is where the "DLL.txt" file will end up, not lost amongst a maze of directories.
rem ----------------------------------------...-">----------------------------------------...
rem --- Save the current directory
PUSHDIR
rem --- Change to the windoze directory
rem --- (D: on my dual-boot system)
C:
CD\winnt
rem --- Copy directory listing of DLLs to a text file
DIR *.dll > dll.txt
rem --- Pop the stack, return to starting directory
POPDIR
rem --- Move the DLL text file to the current directory
MOVE C:\winnt\dll.txt .
rem --- The dot makes the current directory
rem --- (where you started the batch file)
rem --- into the destination of the move or copy
rem ----------------------------------------...-">----------------------------------------...
P_Smith
Here's a useful trick for cycling through a group of files dragged on to a batch file:
:CHECKARG
if "%~f1"=="" (
echo No more args given. Done.
exit /b 0
)
:: Operations to perform on each file
shift
goto CHECKARG
chaws
@eagles500: or Cygwin, powershell, or a set of personal .exe files. The default cmd is pretty lacking compared the the *nix counterparts, yeah :(
@Nosgoroth: Ah yes, I forgot about that. Normally I run my batch files in the directory they're in.
lamintak
@lamintak: From my experience, %dp0 is the path of the batch file, which might not be the same as %cd%, for example if you call the batch file as "somedir/batchfile.bat" or if it is in the path.
ah it just so much eaiser with a proper shell like bash
eagles500
Excuse my last comment!
After some brief testing, it looks like the difference between %~dp0 and %CD% is that %~dp0 has a trailing backslash whereas %CD% does not. One awesome benefit of %~dp0 is that it apparently supports UNC paths! It can take a while to translate, though.
lamintak
After some brief testing, @AmphetamineCrown: Yes, but it's sometimes nice to know the exact path
lamintak
Can't most relative scripting be done with "." (= current directory) and ".." (= parent directory)?
AmphetamineCrown