5

I have a bunch of files that are in the following format

filename-!#.ext

where # is an incrementing number to prevent collisions. I would like to just remove the ! from the filename so it reads as

filename-#.ext

It seems like I could do this with the ren command and wildcards but having a hard time getting it to work. I tried running this:

ren *!?.ext *?.ext

My thought is the * should match the filename- part, then !, then the ? for the numeric. However, the resulting file is named this:

filename-!#.ext.ext

and I can't quite figure out why.

The filename part can be dynamic, but won't contain any !'s. Any ideas?

aschipfl
  • 28,946
  • 10
  • 45
  • 77
user3334986
  • 149
  • 11

3 Answers3

6

I think you'll have to resort to a small batch file for this:

@echo off
    setlocal
    for %%a in (*!*.ext) do call :remove "%%~a"
    goto :eof

:remove
    set "FROM=%~1"
    set "TO=%FROM:!=%"
    ren "%FROM%" "%TO%"
    goto :eof

The above will – for all files containing an exclamation mark and extension ext – call the remove function. This takes the original name (FROM) and determines the new name (TO) by using the %var:find=replace% syntax to remove any exclamation mark (replaces it with an empty string).

Notes

  • You cannot use find/replace with either %%a or %0 type variables, so you have to assign it to a named variable first.

  • I originally tried doing this "inline" with the for statement (e.g. for ... ( ending )) but to do that, you would have to enable delayed-expansion (because you would need to access a named variable in a loop). However, delayed-expansion uses ! (instead of %) to reference variables and this got in the way of the ! we were trying to remove. There may be a way of doing this, but I haven't found it. Using call will be slightly slower, but not significantly unless you've got thousands of files.

  • You don't really need to create TO (you could perform the replacement on the ren command-line) but I used it for clarity.

  • This will work for all files with an exclamation mark: it doesn't check the bit after is numeric.

TripeHound
  • 2,169
  • 17
  • 30
  • 1
    good answer. I was trying to make a very short version using delayedExpansion to automatically remove the single ```!``` but when renaming, cmd ignored all ```"``` and "detected" a variable over the arguments, like ```ren "arg!var" "!arg2!"```. So I ended up with the inline-call-version which I would prefer in this case, because you don't need any external label to jump to (and you have less code). – timlg07 Aug 15 '18 at 16:02
  • 1
    @timlg07 Yes, I think I had the same problem. I'll have to try and remember your `call set` trick (I have seen it before, but obviously not often enough). – TripeHound Aug 15 '18 at 16:33
  • 1
    Also nice, but this also would double `^`-signs in the original file name; you could avoid that by not using argument `%~1` but assigning `set "FROM=%%~a"` in the `for` loop and reading variable `FROM` in the sub-routine... – aschipfl Aug 15 '18 at 20:08
5

I think it is not a good idea to let rename select the files, but instead do it with a for-loop and then execute the rename for every file:

for %%F in (*!*) do (
    set "nxF=%%~nxF"
    call ren "%%nxF%%" "%%nxF:!=%%"
)

You really have to use call here instead of delayedExpansion, because delayedExpansion would destroy the rename-arguments (because they contain !)

timlg07
  • 460
  • 4
  • 9
  • 2
    You beat me to it. But you can do this with one less `CALL`. Just do the replacement and rename at the same time: `call ren "%%F" "%%newF:!=%%"` – Squashman Aug 15 '18 at 15:56
  • yes, I forgot to shorten that at the end. Thank you ;) – timlg07 Aug 15 '18 at 16:04
  • 1
    Great answer, but there is one small problem: any `^` in the original file name would become doubled; to avoid that use another interim variable like `set "oldF=%%~fF"` and use `%%oldF%%` rather than `%%~fF` within `call`... – aschipfl Aug 15 '18 at 20:01
  • who puts ```^``` in a file name? ;D Thanks for the comment, I updated my code. – timlg07 Aug 16 '18 at 20:44
3

Based upon your provided information and just for the sake of offering something a little different, you could let delayed expansion do the work for you, (as it will remove the unwanted exclamation mark for you).!

@For %%A In ("*-!?.ext") Do @Set "_=%%A" & SetLocal EnableDelayedExpansion & Ren "!_!" "%%A" & EndLocal

You could probably also do it with a nested for loop:

@For %%A In ("*-!?.ext") Do @For /F "Tokens=1*Delims=!" %%B In ("%%A") Do @Ren "%%A" "%%B%%C"

…and from the Command Prompt:

For %A In ("*-!?.ext") Do @For /F "Tokens=1*Delims=!" %B In ("%A") Do @Ren "%A" "%B%C"
Compo
  • 30,301
  • 4
  • 20
  • 32
  • great solution, I was also planning to do this, but forgot to save the old file name ^^ – timlg07 Aug 15 '18 at 17:16
  • 2
    Very nice! I think it's worth to mention that this relies on the fact that there is a single exclamation mark in each file name... – aschipfl Aug 15 '18 at 20:03