Strip drive letter - batch-file

By using the command from a folder in D: drive
for /f "delims=" %%d in ('cd') do set pathdrv=%%d
echo %pathdrv%
I get "d:\some folder".I want to write batch commands to create autorun file in the root of the drive. Please help me to strip the drive letter "d:" so the output is "\some folder" and what extra change do i do to strip "\".

Short answer: Use the substring syntax to strip the first two characters from the %cd% pseudo-variable:
%cd:~2%
To remove the first backslash too:
%cd:~3%
This reliably works even with Unicode paths when the console window is set to raster fonts.
Longer answer, detailing some more options (none of which work well enough):
For arguments to the batch file you can use the special syntax %p1, which gives you the path of the first argument given to a batch file (see this answer).
This doesn't work the same way with environment variables but there are two tricks you can employ:
Use a subroutine:
call :foo "%cd%"
...
goto :eof
:foo
set result=%~p1
goto :eof
Subroutines can have arguments, just like batch files.
Use for:
for %%d in ("%cd%") do set mypath=%%~pd
However, both variants don't work when
The console is set to "Raster fonts" instead of a TrueType font such as Lucida Console or Consolas.
The current directory contains Unicode characters
That have no representation in the current legacy codepage (for Western cultures, CJK is a good choice that doesn't fit). Remember that in this case you'll only get question marks instead of the characters.
The problem with this is that whil environment variables can hold Unicode just fine you'll get into problems once you try setting up a command line which sets them. Every option detailed above relies on output of some kind before the commands are executed. This has the problem that Unicode isn't preserved but replaced by ?. The only exception is the substring variant at the very start of this answer which retains Unicode characters in the path even with raster fonts.

Drive letter:
%CD:~0,1%
Full drive name (incl. colon):
%CD:~0,2%

Related

Use content of a file in a string

I have a file (let's call it version.txt) that contains a version number and some text:
v5.02
Some text explaining
where and how this
number is used
Based on this answer, I use
set /p version=<version.txt
to store the first line of the file in the version variable. Now I'm trying to write a batch script that operates on folders that contain this version number in their name. However, I get unexpected results because something seems to go wrong when I insert the variable in a path. For example, this script
#set /p version=<version.txt
#echo C:\some\folder\%version%\some\file.exe
prints
C:\some\folder\v5.02
instead of
C:\some\folder\v5.02\some\file.exe
What's going on? I have a feeling there are hidden characters of some sort at the end of the text in the variable, because setting the variable by hand to a constant in the script works.
Edit: I'm using Windows 10 with Notepad++ as my editor, if it helps.
I can only replicate your issue, when version.txt uses Unix line endings (LF) instead of Windows (CRLF). for /f is immune to this issue:
for /f "delims=" %%a in (version.txt) do set "verion=%%a" & goto :skip
:skip
echo C:\some\folder\%version%\some\file.exe
goto :skip breaks the loop after reading the first line.
Since everything I tried didn't seem to work, the solution I found in the end is to call the batch script from a Python script. The Python script reads the first line of the version file and passes it as an argument to the batch script. Out of context, it is a bit of an inelegant solution, but in my case the batch script was already called by a Python script, so it's not that terrible.
Here is a minimal example:
version.txt
v5.02
Some text explaining
where and how this
number is used
script.bat
#echo C:\some\folder\release\%1\some\file.exe
script.py
import os
with open("version.txt") as f:
version = f.readline().rstrip()
os.system("cmd /c script.bat %s" % version)
Edit: Following Stephan's comment, I tried to change the line ending in the text file from LF to CRLF and it indeed solves the problem. However, since I don't really have control over everything that writes in that file, the solution above remains the most feasible in my case.
Edit 2: Stephan's answer (with the for loop) is actually a better solution than this one since it avoids having to transfer part of the work to the calling Python script.

How do I pass the full file name, including spaces, from the windows registry as a parameter to a batch file?

What I'd like to do: Add an entry to a Windows 10 context menu for specific file types (e.g. .mp4) that allows me to search for the file name on a website (in my case, IMDB). I got the entry to show up fine, but file names cut off after any space character.
Question: How do I pass the full file name, including spaces, from the windows registry as a parameter to a batch file?
.reg
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\VLC.mp4\shell\Search IMDB\command]
#="\"C:\\test.bat\" \"%1\""
test.bat
SET a="%~n1"
SET z=https://www.imdb.com/search/title/?title=
SET zz=%z%%a%
start "" %zz%
For a file name like movie.mp4 this works. However, file name cool movie.mp4 will in this case open a search page for "cool" which I'm afraid does not help me.
Any help is greatly appreciated!
Replace the spaces with + signs.
According to https://ss64.com/nt/syntax-replace.html that should be something like
SET zzz=%zz: =+%
The batch file should be:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "Name=%~n1"
setlocal EnableDelayedExpansion
set "Name=!Name:%%=%%25!"
set "Name=!Name: =%%20!"
start "" "https://www.imdb.com/search/title/?title=!Name!"
endlocal
endlocal
First, read my answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? It explains in detail what is the difference between set variable="value" and set "variable=value". The latter is the recommended syntax.
Second, a URL must be percent-encoded. A normal space character must be encoded with %20 in a URL. The batch file above does that. It percent-encodes first also % in name by %25.
Note: The entire URL is not correct percent-encoded if the string assigned to the environment variable Name contains other characters than and % which need to be percent-encoded for a valid URL too.
Third, the character % must be escaped in a batch file with one more % to be interpreted as literal character even within a double quoted argument string.

Escape characters of a file path argument for a batch file

I was making a batch file to take dragged-and-dropped folders for program input. Everything was working fine until I passed a folder, which for the sake of this post, called foo&bar.
Checking what %1 contained inside the batch file looked like C:\path\to\foo or C:\path\to\foo\foo. If the file path were in quotes it would work, so the only working code that slightly takes this into effect is :
set arg1=%1
cd %arg1%*
set arg1="%CD%"
Which changes directory to the passed argument using wildcards. However this only works once for if there is another folder with un-escaped characters inside the parent folder, passing the child folder would result in the parent folders' value.
I tried the answer of this post, which suggests to output the argument using a remark and redirection statement during an #echo on sequence. However no progress occurred in rectifying the problem. Any suggestions?
To recap, I am looking for ways to pass folders with un-escaped characters as arguments to a batch file. The implementation should preferably be in a batch file, but answers using VBScript are welcome. However the starting program must be in batch as this is the only program of the 3 that accepts files as arguments.
To test this, create a batch file with following code:
#echo off
set "arg1=%~1"
echo "the passed path was %arg1%"
pause
Then create folders called foobar and foo&bar. Drag them onto the batch file to see their output. foo&bar will only return C:\path\to\foo.
OK, so the problem is that Explorer is passing this as the command line to cmd.exe:
C:\Windows\system32\cmd.exe /c ""C:\path\test.bat" C:\path\foo&bar"
The outermost quotes get stripped, and the command becomes
"C:\working\so46635563\test.bat" C:\path\foo&bar
which cmd.exe interprets similarly to
("C:\working\so46635563\test.bat" C:\path\foo) & bar
i.e., bar is considered to be a separate command, to be run after the batch file.
The best solution would be to drag-and-drop not directly onto the batch file but onto, say, a vbscript or a Powershell script or a plain old executable. That script could then run the batch file, either quoting the argument appropriately or putting the directory path into an environment variable rather than on the command line.
Alternatively, you can retrieve the original command string from %CMDCMDLINE% like this:
setlocal EnableDelayedExpansion
set "dirname=!CMDCMDLINE!"
set "dirname=%dirname:&=?%"
set "dirname=%dirname:" =*%"
set "dirname=%dirname:"=*%"
set "dirname=%dirname: =/%"
for /F "tokens=3 delims=*" %%i in ("%dirname%") do set dirname=%%i
set "dirname=%dirname:/= %"
set "dirname=%dirname:?=&%"
set dirname
pause
exit
Note the exit at the end; that is necessary so that cmd.exe doesn't try to run bar when it reaches the end of the script. Otherwise, if the part of the directory name after the & happens to be a valid command, it could cause trouble.
NB: I'm not sure how robust this script is.
I've tested it with the most obvious combinations, but YMMV. [It might be more sensible to use delayed expansion exclusively, I'm not sure. It doesn't seem to be necessary except in the first set command. Jeb's answer here might be a better choice if you're going this route.]
For the curious, the script works like this:
Load the original command line into dirname [necessary for the reason pointed out by jeb]
Replace all the & characters with ?
Replace all the quote marks with *
If a quote mark is followed by a space, suppress the space.
NB: it is necessary to suppress the space to deal with both the case where the path contains a space (in which case Explorer adds quote marks around it) and the case where it doesn't.
Replace all remaining spaces with /
NB: ? * and / are illegal in file names, so these replacements are safe.
At this point the string looks like this:
C:\Windows\system32\cmd.exe//c/**C:\path\test.bat**C:\path\foo?bar**
So we just need to pull out the third asterisk-delimited element, turn any forward slashes back into spaces and any question marks back into ampersands, and we're done. Phew!

Last index of a character in an input string in a batch file

I have a batch script running in windows. Every time the batch script is invoked an input path is provided as a parameter. I need to extract the folder name from the path.For that i need to get the last index of "/" from the path and then take a substring from that path until the end of the string.
Suppose I have a string /home/home1/home2/home3
The output I require is home3. Is there any way to extract the same.
For plain DOS/Windows batch files, this should work:
set FILE="c:\foo\bar.txt"
for /F %%i in ("%FILE%") do #echo %%~ni
That comes from DOS BAT file equivalent to Unix basename command?
Windows PowerShell and the Unix shell offer other alternatives.
So you have a string representing a folder path, and you want the name of the right most folder within the path. CMD.EXE provides convenient tools to work with file/folder paths, so there is no need to search for the last \.
You don't state where the string resides. Most likely it is either 1) in a batch parameter from a CALL to a batch script or :function, or 2) within an environment variable. I will assume a parameter, and show a succession of solutions, each one better (more robust) than the prior one. At the very end I will show a slight twist to adapt to working with an environment variable.
So if parameter %1 contains /home/home1/home2/home3, then all you need is %~n1.
But folder names can include dots, and the text after the last dot is considered to be an extension. So you need the x modifier as well to include the extension. For example, if %1 contains /home.1/home.2/home.3, then %~nx will yield home.3. It works just as well if there is no extension.
But folder names can include a poison character like &, so you should enclose the string in quotes: "%~nx1". If you are assigning the result to a variable, then you should enclose the entire assignment in quotes: set "folder=%~nx1". The value will not include the quotes, so be sure to add quotes when expanding the variable: "%folder%", or else use delayed expansion !folder!.
But sometimes people include a trailing backslash when passing a folder path, as in \home1\home2\home3\. The trailing backslash positively indicates that the path is to a folder, but %~nx1 will yield nothing. There is a simple trick using a FOR loop and an appended dot to solve this.
for %%F in (%1.) do set "folder=%%~nxF"
Note that FOR variables use the exact same modifiers as parameters. The appended dot elegantly solves the problem in a tricky way. If there is a trailing backslash, then the dot represents the "current" directory from that postion. The expansion modifiers automatically normalize the result into a canonical form, which yields the correct answer. But it also works if there is not a trailing backslash. File and folder names are not allowed to end with a dot or a space, and the expansion normalization will strip the trailing dot.
There are a few other esoteric cases dealing with UNC paths and long paths that are solved by adding the ~f modifier to convert %1 into the full canonical path.
Here is the ultimate solution that should "always" work with batch parameter %1.
for %%F in ("%~f1.") do set "folder=%%~nxF"
I put the word "always" in quotes because it is possible for the code to fail, but it would require the end user doing something stupid like passing "c:\This"^&"that\". In this case, the correct thing to do is simply pass "c:\This&that\".
Note that the passed path could be the root of a volume, like "c:\". In this case the result is an empty string, which is correct - there is no folder name.
How to work with an environment variable instead
Suppose you have variable folderPath that contains the path string. If you know that the value does not contain any quotes, and it is not a UNC or long path, then you can simply use:
for %%F in ("%folderPath%.") do set "folder=%%~nxF"
But that can fail in multiple ways if the value already contains quotes, or if it is a UNC or long path. The solution is to add an extra FOR /F loop coupled with delayed expansion to safely transfer the value into a FOR variable. Then the ~f modifier can be used as before. But paths can include the ! character, and FOR variable expansion will strip any ! if delayed expansion is enabled. So delayed expansion must be strategically toggled on and off.
Here is the ultimate solution for working with a variable
I'm assuming that delayed expansion is currently off.
setlocal enableDelayedExpansion
for /f "eol=: delims=" %%A in ("!folderPath!") do (
endlocal
for %%F in ("%%~fA.") do set "folder=%%~nxF"
)
I'm not aware of any scenarios where this last code can fail.

How can i delete files with only 4 characters?

I use Windows 7, and I a want to build a batch-file with to complete the following task:
Delete all files with 4 characters in name in the C:\images directory.
E.g. 1234.jpg 7123.jpg, 8923.jpg, 7812.jpg, 1245.jpg, 0067.jpg, 0001.jpg, 0010.jpg, 0060.jpg etc.
Is that possible?
This solution will erase all files whose name part before the dot have 4 characters, and possibly those that have a shorter part before the dot.
ERASE C:\images\????.*
? stands for any character (or sometimes no character at all, see below)
* stands for any sequence of characters.
What different Windows versions ?
Under Windows 98 (and I have a gut feeling that this is also valid for "pure" DOS versions without full support for long FAT names, which would include win95 and older, and maybe Windows Millennium), the ? stands for exactly one character.
Based on #dbenham's comment, I get that in later versions of Windows, ? stands for 0 or 1 characters.
What about FAT short names ?
Now for the tricky parts : What about names with a leading dot (i.e. 0 characters before the first dot) ? What about files with multiple dots ?
Trailing dots are stripped before anything is performed, so the extension of abc.def..... is def, and its base is abc.
The dot is at a fixed position in the short name, and is present even for files without an extension. That's why *.* matches all files, even those without an extension.
Leading dots, multiple dots, extensions of more than 3 characters and base (before the dot) parts of more than 8 characters are invalid in short FAT names (also called DOS names), so a file whose long name (displayed under Windows) is one of those will have a made-up short name, looking like POBASE~N.POE, where :
POBASE is a part of the base (usually the beginning, stripped of unusual characters),
~ is a literal tilde character,
N is a single or multiple-digit number, and is usually the smallest number that doesn't clash with existing names,
POE is a part of the extension (usually the beginning, stripped of unusual characters), where the extension is the part after the last dot,
and all the parts are capital letters or digits.
I created the following files, in that order :
.ab had the short name AB~1
a.b.c had the short name AB~1.C
a.bcde had the short name A~1.BCD
a.x y (notice the space) had the short name a.xy
..ab had the short name AB~2, since AB~1 was already there (first example)
a.b, a.b., a.b.., a.b... are all equivalent names for the same file (i.e. to create a.b... will overwrite a.b if it exists).
When you run the command dir ????.* on an old Windows 98 system, the following files will be matched :
AB~1, with long name .ab
AB~1.C, with long name a.b.c
AB~2, with long name ..ab
Now, while Vista and 7 have tried to throw most DOS legacy out of the window (pun intended), there are still some remnants : if you type C:\PROGRA~1 in the address bar of an explorer window, you'll land into (the half-baked localized variant of) C:\Program files, and if you plug in a FAT-formatted USB key, the files will have short names. I'm not sure if the wildcards will match against these short names, but here are some "fun" facts, that show it's not even worth investigating :
Fun facts
In a french version of Windows 7, C:\Program files will show up in explorer as C:\Programmes, but if you try to cd C:\Programmes, it will fail, so the translated names don't seem to be used by the command-line.
On the same Windows 7, ????????.? (8.1 question marks) will match with a.b.c (which is only 5 characters), but anything shorter (like ???????.? with 7.1 question marks) won't match it.
On the same Windows 7, ????????.? will match foo, although there is no dot in foo.
Conclusion
To conclude, if you want to write a program that reliably deletes files with exactly four characters before the first (or last) dot, use some of the other solutions, don't use wildcards.
However, be warned that older windows versions don't have all those fancy-dancy scripting capabilities for getting the length of a string with awkward syntax (I mean, seriously, if "!n:~3,1!" neq "" echo del "%%F", it's nearly as bad as zsh globbing modifiers.
All this would be so much simpler if windows didn't have three layers of file naming (FAT short names, "actual" file name, and translated file name), and if they hadn't chosen to change the meaning of ?.
Sigh
This will delete any file with exactly 4 characters name (excluding extension) and with any extension.
e.g.: del4chrs "C:\DATA\Junk Folder"
#echo off
setlocal enabledelayedexpansion
if "%1" == "" (
echo Usage del4chrs {folder}
goto :eof
)
if not exist "%~1\nul" (
echo Folder not found.
goto :eof
)
pushd "%~1"
for %%F in (????.*) do (
set n=%%~nF
if "!n:~3,1!" neq "" echo del "%%F"
)
I believe this is the simplest solution to delete all files whose base name disregarding extension is exactly 4 characters: EDIT - I removed the mask from the DIR command since FINDSTR is doing the real filtering anyway
for /f "eol=: delims=" %%F in ('dir /b /a-d^|findstr /rx "....\.[^.]* ...."') do del "%%F"
It is easy to modify the solution to only delete files that consist of 4 numeric digits (disregarding extension)
for /f "eol=: delims=" %%F in ('dir /b /a-d^|findstr /rx "[0-9][0-9][0-9][0-9]\.[^.]* [0-9][0-9][0-9][0-9]"') do del "%%F"

Resources