1

I have backups script that removes backups older than x days.

On Linux machine this is working well:

find /backups/dummy-prod-db -name "*_D_dummy-prod-db*" -type f -mtime +7 -exec rm -v {} \;

But if I run this on Windows machine with Cygwin:

c:\cygwin64\bin\find.exe "C:\\backups\\dummy-prod-db\\" -name "*_D_dummy-prod-db*" -type f -mtime +7 -exec rm -v {} \;

it returns

/usr/bin/find: missing argument to `-exec'

How can I fix it?

Edit:

I'm using CMD shell on Windows.

Kamil
  • 2,656
  • 1
  • 19
  • 28
  • I don't have a cygwin to test on at the moment, but try quoting the braces: `-exec rm -v "{}" ";"` – glenn jackman Jan 12 '23 at 15:24
  • @glennjackman `-exec rm "{}" \ ";"` `-exec rm "{}" "\;"` not working. I found working solution, last backslash should be removed, see my answer below. – Kamil Jan 12 '23 at 15:37

2 Answers2

3

As you have already found out, the fix is to use ; instead of \;. In my answer I will try to explain the mechanics behind this.

At first it's good to know why you need \; on Linux. It's not obvious, but find on Linux expects ; (or +) as an argument somewhere after -exec, not \;. The backslash is for the shell. The problem is ; is special for the shell, it's a command terminator. If you used ;, the shell would interpret it as such, find wouldn't see it at all. To tell the shell ; should not be treated specially we can escape it with a backslash (\;) or we can quote it (";" or ';'). The shell will interpret and remove the backslash or the quotes and find will see sole ; it expects.

The path c:\cygwin64\bin\find.exe you used makes me believe you were not working in Bash (or similar shell) in Cygwin, but rather in cmd.exe. In cmd.exe ; is not special, so there is no need to escape it. Additionally \ is not an escape character (^ is). \; you used got to find.exe literally as \;. The tool did not find ; (nor +) it expected, hence the error. Sole ; gets to find.exe as ; and this is what the tool expects.

AFAIK ";" in cmd.exe should ultimately get to find.exe as ;. ";" works on Linux as well, so we can call ";" "universal syntax" in the context of this problem. Note ';' is not universal, single-quotes are not special in cmd.exe*.


* The situation is somewhat more complicated.

In Linux, executables like find get their arguments as an array (in your case: find, /backups/dummy-prod-db, -name, *_D_dummy-prod-db*, -type, f, -mtime, +7, -exec, rm, -v, {}, ;). The shell handles word splitting and globbing (in your case there were no unquoted wildcards though), it removes quotes, backslashes.

In Windows, executables like find.exe get their arguments as a single string. AFAIK technically before the Windows system calls main(), it calls _setargv() to parse the command line into the standard C argc and argv[] arguments that it passes to the main() function of the program. The program can provide its own _setargv(). This means interpretation of quotes and wildcards like *, and splitting to words (separate arguments) are really the job of each executable alone, not of cmd.exe. The executable may use some standard _setargv(), then it's almost like cmd.exe did the job, a situation similar to what is in Linux; or it may provide its own _setargv().

Where I wrote

";" in cmd.exe should ultimately get to find.exe as ;

"ultimately" means "after _setargv()". Before _setargv() the quotes were there along with the rest of the command string; if find.exe provided a custom _setargv(), it could interpret these quotes in any way the author desired.

And where I wrote

single-quotes are not special in cmd.exe

I really meant "not special for commonly used _setargv()". If find.exe provided a custom _setargv(), it could interpret single-quotes in any way the author wanted, e.g. like single-quotes in Bash. I don't know, maybe your find.exe does interpret single-quotes like this.

In general a phrase "something works (or doesn't work) in cmd.exe" includes cmd.exe and some standard _setargv().

For comparison: FIND in Windows (the native FIND, not a port of *nix find) has its own peculiar syntax because it interprets the string it gets differently than any standard _setargv() would. You can find more information here: Tcler's Wiki / exec quotes problem (note some parts of the article are in the context of Tcl which has nothing to do with our problem, still the fragment about FIND is educative).

Kamil Maciorowski
  • 69,815
  • 22
  • 136
  • 202
  • Thanks for such deep and clear explanation. Part about how executables get their arguments in Windows and Linux was very interesting. – Kamil Jan 13 '23 at 00:43
0

I have found answer here:

https://stackoverflow.com/a/32377808/1215291

It says that backslash at the end should be removed. I changed this:

... -exec rm -v {} \;

To this:

... -exec rm -v {} ;

and it worked, but I don't understand why.

I would appreciate if someone could explain it.

Kamil
  • 2,656
  • 1
  • 19
  • 28